Files
giglez/docs/CLEANUP_AND_OPTIMIZATION_PLAN.md
Trilltechnician 4237c4bdb8 Phase 1 & 2: Cleanup redundant code and integrate pattern decoder
## Phase 1: Code Cleanup (~1,859 lines removed)

**Deleted Redundant Matchers:**
-  strategies_orm.py (356 lines) - Old ORM-based strategies
-  simple_matcher.py (351 lines) - Replaced by strategies.py
-  rtl433_matcher.py (352 lines) - Replaced by strategies.py

**Archived Old Scripts:**
- Moved 11 one-time analysis/import scripts to scripts/archive/
- Scripts: analyze_flipper_signatures, analyze_tembed_files, identify_tembed_devices,
  import_flipper_sqlite, import_tembed_signatures, match_tembed_with_db,
  match_with_flipper_db, rematch_captures, test_gps_extraction,
  test_tembed_matching, test_wardriving_import

**Consolidated API:**
- Renamed main.py → main_orm_legacy.py (archived old ORM-based API)
- main_simple.py is now the primary production API

## Phase 2: Pattern Decoder Integration 

**CRITICAL FIX: Pattern decoder now integrated into production API!**

**Changes:**
1. Updated main_simple.py to use unified SignatureMatcher
2. Added 6 strategies to matcher pipeline:
   - ExactMatcher (protocol + frequency)
   - FrequencyMatcher (frequency-based)
   - BitPatternMatcher (data patterns)
   - TimingMatcher (timing-based)
   - RTL433DecoderStrategy (RTL_433 decoder)
   - PatternBasedStrategy (NEW! Pattern decoder for short captures)

3. Created MockDB class for simplified mode (no real database)
4. Replaced old get_matcher() with get_matcher_engine()
5. Updated upload handler to use MatchResult format
6. All matches now include confidence scores and match methods

**Result:**
- Pattern decoder is NOW ACTIVE in production 🎉
- Unified matching pipeline with 6 strategies
- Cleaner codebase (-1,859 lines)
- Single source of truth for matching logic

🎉 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-14 22:33:32 -08:00

30 KiB

GigLez Cleanup & Optimization Plan

Date: 2026-01-14 Purpose: Identify bloat, unused code, and plan optimizations + JavaScript implementation


Part 1: Code Bloat Analysis

A. Duplicate/Redundant Matcher Implementations

🔴 HIGH PRIORITY - Consolidate Matchers

Current State:

src/matcher/
├── engine.py                    # Base MatchStrategy interface (3.4 KB)
├── strategies.py                # NEW unified strategy system (610 lines) ✅
├── strategies_orm.py            # OLD ORM-based strategies (356 lines) ❌ REDUNDANT
├── simple_matcher.py            # Standalone matcher (351 lines) ❌ REDUNDANT
├── rtl433_matcher.py            # Standalone RTL_433 (352 lines) ❌ REDUNDANT
├── rtl433_decoder.py            # RTL_433 decoder (385 lines) ✅ USED
├── pattern_decoder.py           # Pattern decoder (461 lines) ✅ USED
└── protocol_database.py         # Protocol DB (435 lines) ✅ USED

Problem:

  • 3 different matcher implementations doing similar things
  • strategies.py is the NEW unified system (includes all strategies)
  • strategies_orm.py, simple_matcher.py, rtl433_matcher.py are OLD and redundant

Evidence:

# main_simple.py uses:
from src.matcher.simple_matcher import get_matcher  # OLD
from src.matcher.rtl433_decoder import get_decoder  # OK

# strategies.py contains:
class ExactMatcher(MatchStrategy)           # Replaces simple_matcher
class FrequencyMatcher(MatchStrategy)       # Replaces simple_matcher
class BitPatternMatcher(MatchStrategy)      # Replaces simple_matcher
class TimingMatcher(MatchStrategy)          # Replaces simple_matcher
class RTL433DecoderStrategy(MatchStrategy)  # Wraps rtl433_decoder
class PatternBasedStrategy(MatchStrategy)   # NEW - pattern_decoder

Action Plan:

  1. Keep: strategies.py (unified system)
  2. Keep: rtl433_decoder.py (used by RTL433DecoderStrategy)
  3. Keep: pattern_decoder.py (used by PatternBasedStrategy)
  4. Keep: protocol_database.py (used by pattern_decoder)
  5. Keep: engine.py (base interfaces)
  6. DELETE: strategies_orm.py (old, unused)
  7. DELETE: simple_matcher.py (replaced by strategies.py)
  8. DELETE: rtl433_matcher.py (replaced by strategies.py)

Impact:

  • Remove ~1,059 lines of redundant code
  • Eliminate maintenance burden of 3 parallel implementations
  • Simplify API by using only strategies.py

B. Dual API Implementations

🟡 MEDIUM PRIORITY - Consolidate APIs

Current State:

src/api/
├── main.py                # Full ORM-based API (280 lines)
└── main_simple.py         # Simplified API (536 lines) ✅ CURRENTLY USED

Problem:

  • Two FastAPI implementations
  • main_simple.py is actively developed and used
  • main.py uses old ORM approach (SQLAlchemy)
  • Confusion about which is "production"

Recommendation:

  1. Keep: main_simple.py (rename to main.py or app.py)
  2. Archive: Current main.pymain_orm_legacy.py or delete

Reason:

  • main_simple.py has RTL_433 integration
  • main_simple.py uses modern psycopg2 approach
  • Less abstraction = easier debugging

C. Unused/Redundant Scripts

🟢 LOW PRIORITY - Archive Old Scripts

Current State: 19 Python scripts in scripts/

Categories:

1. Active/Essential (Keep):

  • test_pattern_decoder.py - New pattern decoder tests
  • test_real_weather_sensors.py - RTL_433 validation
  • test_rtl433_with_known_devices.py - RTL_433 tests
  • parse_rtl433_devices.py - RTL_433 device catalog builder
  • download_rf_test_datasets.sh - Test data fetcher

2. Utility/Maintenance (Keep):

  • cleanup_database.py - Database maintenance
  • import_wardriving_data.py - CSV import for wardriving
  • create_test_dataset.py - Test data generator

3. One-Time/Experimental (Archive):

  • 🔄 analyze_flipper_signatures.py - One-time analysis
  • 🔄 analyze_tembed_files.py - One-time analysis
  • 🔄 identify_tembed_devices.py - One-time analysis
  • 🔄 import_flipper_sqlite.py - One-time import
  • 🔄 import_tembed_signatures.py - One-time import
  • 🔄 match_tembed_with_db.py - Exploratory
  • 🔄 match_with_flipper_db.py - Exploratory
  • 🔄 rematch_captures.py - One-time rematch
  • 🔄 test_gps_extraction.py - Early prototype
  • 🔄 test_tembed_matching.py - Early prototype
  • 🔄 test_wardriving_import.py - Import test

Action Plan: Create scripts/archive/ directory and move 11 one-time scripts there.

Impact:

  • Cleaner scripts/ directory (8 files vs 19)
  • Keep history but reduce clutter
  • Easy to restore if needed

D. Unused Parser Modules

🟢 LOW PRIORITY - Review Parsers

Current State:

src/parser/
├── sub_parser.py              # Main .sub parser ✅ USED
├── rtl433_converter.py        # RAW → RTL_433 converter ✅ USED
├── metadata.py                # SignalMetadata class ✅ USED
├── raw_parser.py              # RAW signal analyzer (326 lines) ?
├── gps_extractor.py           # GPS from filenames (241 lines) ?
└── wardriving_importer.py     # CSV wardriving import (333 lines) ?

Questions:

  1. Is raw_parser.py still used? (Seems superseded by pattern_decoder.py)
  2. Is gps_extractor.py actively used?
  3. Is wardriving_importer.py production-ready?

Recommendation:

  • Check imports/usage in API
  • If unused, move to deprecated/ folder
  • Document if they're experimental

E. Database Connection Complexity

🟡 MEDIUM PRIORITY - Simplify Database Layer

Current State:

Database layer has 3 approaches:
1. config/database.py          # Psycopg2 connection pooling
2. src/database/connection.py  # SQLAlchemy ORM
3. src/database/models.py      # SQLAlchemy models (564 lines)

Problem:

  • main_simple.py uses psycopg2 approach (simpler)
  • main.py uses SQLAlchemy ORM approach (complex)
  • strategies_orm.py uses SQLAlchemy models
  • Dual maintenance burden

Recommendation: Since we're keeping main_simple.py:

  1. Keep: config/database.py (psycopg2 pooling)
  2. 🔄 Archive: src/database/connection.py (if not used elsewhere)
  3. 🔄 Archive: src/database/models.py (if only used by old main.py)

Part 2: Optimization Opportunities

A. Pattern Decoder Enhancements

🔵 ENHANCEMENT - Improve Decode Rate

Current Performance:

  • 44.4% decode rate (4/9 files)
  • Confidence range: 33-76%

Optimization Ideas:

1. Lower Confidence Thresholds (Easy - 30 min)

# Current
proto.min_confidence = 0.6  # Too strict?

# Proposed
proto.min_confidence = 0.4  # Allow more matches

2. Add More Protocols (Medium - 2 hours)

  • Currently: 18 protocols
  • Target: 50+ protocols
  • Source: Flipper Zero firmware protocol definitions

3. Improve Timing Tolerance (Easy - 1 hour)

# Current
timing_tolerance = 0.2  # ±20%

# Proposed: Adaptive tolerance based on pulse count
if pulse_count < 150:
    timing_tolerance = 0.3  # ±30% for short captures
else:
    timing_tolerance = 0.2  # ±20% for longer captures

4. Add Preamble Pattern Matching (Medium - 2 hours)

# Currently not fully utilized
if proto.preamble_pattern and proto.preamble_pattern in bit_pattern:
    pattern_confidence = 1.0

Enhance to use fuzzy pattern matching (Levenshtein distance).


B. API Integration of Pattern Decoder

🔴 CRITICAL - Pattern Decoder Not Integrated!

Problem: Pattern decoder exists in strategies.py but is NOT used by API!

Current API Flow:

# main_simple.py
from src.matcher.simple_matcher import get_matcher  # OLD
from src.matcher.rtl433_decoder import get_decoder  # OK

# Upload handler uses:
matcher = get_matcher()  # Returns OLD simple_matcher
matches = matcher.match_device(metadata)  # DOESN'T use strategies.py!

decoder = get_rtl433_decoder()  # Separate call
rtl433_devices = decoder.decode(metadata)

What's Missing: The new unified strategies.py system with PatternBasedStrategy is NOT CONNECTED to the API!

Fix Required:

# NEW approach (use strategies.py):
from src.matcher.engine import MatchEngine
from src.matcher.strategies import (
    ExactMatcher,
    FrequencyMatcher,
    BitPatternMatcher,
    TimingMatcher,
    RTL433DecoderStrategy,
    PatternBasedStrategy  # NEW!
)

# Initialize engine
engine = MatchEngine()
engine.add_strategy(ExactMatcher())
engine.add_strategy(FrequencyMatcher())
engine.add_strategy(BitPatternMatcher())
engine.add_strategy(TimingMatcher())
engine.add_strategy(RTL433DecoderStrategy())  # Uses rtl433_decoder
engine.add_strategy(PatternBasedStrategy())   # Uses pattern_decoder

# In upload handler:
matches = engine.match(metadata, db)  # Returns all matches from all strategies

Impact:

  • Pattern decoder will finally be used in production!
  • Unified matching pipeline
  • Easier to add new strategies

C. Frontend JavaScript Implementation

🔵 NEW FEATURE - Client-Side Pattern Decoder

Why JavaScript?

  1. Decode signals client-side before upload (privacy)
  2. Real-time feedback during capture
  3. Reduce server load
  4. Works offline (PWA capability)

Use Cases:

  1. Web Interface: Decode .sub files in browser before upload
  2. Mobile App: Real-time decoding during T-Embed capture
  3. Offline Mode: Decode signals without server

Part 3: JavaScript Implementation Plan

Architecture: Port Pattern Decoder to JavaScript

Target: Node.js + Browser-Compatible Module

File Structure:

src/matcher/js/
├── protocol-database.js        # Protocol signatures (ES6 module)
├── pattern-decoder.js          # Core decoder logic
├── pulse-analyzer.js           # K-means clustering for pulse widths
├── fingerprint.js              # Statistical fingerprinting
└── index.js                    # Main export

JavaScript Implementation: Core Components

1. Protocol Database (protocol-database.js)

/**
 * RF Protocol Signature Database
 * Port of protocol_database.py
 */

export class ProtocolSignature {
  constructor({
    name,
    category,
    manufacturer = null,
    frequency = 433920000,
    shortPulseUs = 500,
    longPulseUs = 1000,
    timingTolerance = 0.2,
    preamblePattern = null,
    syncPattern = null,
    minBits = 24,
    maxBits = 64,
    typicalPulseCount = 100
  }) {
    this.name = name;
    this.category = category;
    this.manufacturer = manufacturer;
    this.frequency = frequency;
    this.shortPulseUs = shortPulseUs;
    this.longPulseUs = longPulseUs;
    this.timingTolerance = timingTolerance;
    this.preamblePattern = preamblePattern;
    this.syncPattern = syncPattern;
    this.minBits = minBits;
    this.maxBits = maxBits;
    this.typicalPulseCount = typicalPulseCount;
  }

  matchesTiming(shortUs, longUs) {
    const shortMin = this.shortPulseUs * (1 - this.timingTolerance);
    const shortMax = this.shortPulseUs * (1 + this.timingTolerance);
    const longMin = this.longPulseUs * (1 - this.timingTolerance);
    const longMax = this.longPulseUs * (1 + this.timingTolerance);

    return (shortMin <= shortUs && shortUs <= shortMax) &&
           (longMin <= longUs && longUs <= longMax);
  }

  matchesFrequency(freq) {
    const tolerance = 100000; // ±100 kHz
    return Math.abs(freq - this.frequency) <= tolerance;
  }
}

// Weather Sensors
export const WEATHER_SENSORS = [
  new ProtocolSignature({
    name: 'Oregon Scientific v2.1',
    category: 'Weather Sensor',
    manufacturer: 'Oregon Scientific',
    shortPulseUs: 488,
    longPulseUs: 976,
    preamblePattern: '10101010'.repeat(4),
    minBits: 64,
    maxBits: 128,
    typicalPulseCount: 200
  }),
  new ProtocolSignature({
    name: 'Acurite Tower Sensor',
    category: 'Weather Sensor',
    manufacturer: 'Acurite',
    shortPulseUs: 220,
    longPulseUs: 440,
    minBits: 56,
    maxBits: 64,
    typicalPulseCount: 130
  }),
  new ProtocolSignature({
    name: 'LaCrosse TX141TH-Bv2',
    category: 'Weather Sensor',
    manufacturer: 'LaCrosse',
    shortPulseUs: 500,
    longPulseUs: 1000,
    minBits: 40,
    maxBits: 48,
    typicalPulseCount: 100
  }),
  // ... add all 18 protocols
];

export const ALL_PROTOCOLS = [
  ...WEATHER_SENSORS,
  // ...GARAGE_DOOR_OPENERS,
  // ...DOORBELLS,
  // ...TIRE_PRESSURE,
  // ...SECURITY_SENSORS,
  // ...REMOTE_CONTROLS
];

export class ProtocolDatabase {
  constructor() {
    this.protocols = ALL_PROTOCOLS;
    this._indexProtocols();
  }

  _indexProtocols() {
    this.byCategory = {};
    this.byFrequency = {};

    for (const proto of this.protocols) {
      // Index by category
      if (!this.byCategory[proto.category]) {
        this.byCategory[proto.category] = [];
      }
      this.byCategory[proto.category].push(proto);

      // Index by frequency (MHz)
      const freqMHz = Math.round(proto.frequency / 1_000_000);
      if (!this.byFrequency[freqMHz]) {
        this.byFrequency[freqMHz] = [];
      }
      this.byFrequency[freqMHz].push(proto);
    }
  }

  findByTiming(shortUs, longUs, frequency = null) {
    const matches = [];
    let candidates = this.protocols;

    if (frequency) {
      const freqMHz = Math.round(frequency / 1_000_000);
      candidates = this.byFrequency[freqMHz] || this.protocols;
    }

    for (const proto of candidates) {
      if (proto.matchesTiming(shortUs, longUs)) {
        if (!frequency || proto.matchesFrequency(frequency)) {
          matches.push(proto);
        }
      }
    }

    return matches;
  }

  findByFrequency(frequency) {
    return this.protocols.filter(p => p.matchesFrequency(frequency));
  }
}

2. Pulse Analyzer (pulse-analyzer.js)

/**
 * Pulse Width Analysis using K-means clustering
 * Port of PatternDecoder._identify_pulse_widths()
 */

/**
 * Simple K-means clustering for 1D data
 */
export function kMeans(data, k = 2, maxIterations = 100) {
  if (data.length < k) {
    return data.map(v => [v]);
  }

  // Initialize centroids (use min and max)
  const sorted = [...data].sort((a, b) => a - b);
  let centroids = [sorted[0], sorted[sorted.length - 1]];

  for (let iter = 0; iter < maxIterations; iter++) {
    // Assign points to nearest centroid
    const clusters = Array.from({ length: k }, () => []);

    for (const point of data) {
      const distances = centroids.map(c => Math.abs(point - c));
      const nearestIdx = distances.indexOf(Math.min(...distances));
      clusters[nearestIdx].push(point);
    }

    // Update centroids
    const newCentroids = clusters.map(cluster => {
      if (cluster.length === 0) return centroids[0];
      return cluster.reduce((a, b) => a + b, 0) / cluster.length;
    });

    // Check convergence
    const converged = centroids.every((c, i) =>
      Math.abs(c - newCentroids[i]) < 1
    );

    centroids = newCentroids;

    if (converged) break;
  }

  return centroids.sort((a, b) => a - b);
}

/**
 * Identify SHORT and LONG pulse widths from pulse train
 */
export function identifyPulseWidths(pulses) {
  // Separate HIGH pulses (positive) and LOW gaps (negative)
  const highPulses = pulses.filter(p => p > 0).map(Math.abs);
  const lowPulses = pulses.filter(p => p < 0).map(Math.abs);

  // Cluster into SHORT/LONG
  const [shortPulse, longPulse] = highPulses.length >= 2
    ? kMeans(highPulses, 2)
    : [highPulses[0] || 0, highPulses[0] || 0];

  const [shortGap, longGap] = lowPulses.length >= 2
    ? kMeans(lowPulses, 2)
    : [lowPulses[0] || 0, lowPulses[0] || 0];

  return { shortPulse, longPulse, shortGap, longGap };
}

/**
 * Decode pulse train to binary string (PWM encoding)
 */
export function decodeToBits(pulses, shortPulse, longPulse) {
  const threshold = (shortPulse + longPulse) / 2;
  const bits = [];

  for (const pulse of pulses) {
    if (pulse > 0) {  // Only decode HIGH pulses
      bits.push(Math.abs(pulse) < threshold ? '0' : '1');
    }
  }

  return bits.join('');
}

3. Statistical Fingerprinting (fingerprint.js)

/**
 * Extract statistical fingerprint from pulse data
 */

export function extractFingerprint(pulses) {
  const highPulses = pulses.filter(p => p > 0);
  const lowPulses = pulses.filter(p => p < 0).map(Math.abs);

  // Calculate statistics
  const meanPulse = mean(highPulses);
  const stdPulse = std(highPulses);
  const meanGap = mean(lowPulses);
  const stdGap = std(lowPulses);

  const totalHigh = sum(highPulses);
  const totalLow = sum(lowPulses);
  const totalTime = totalHigh + totalLow;

  const pulseGapRatio = meanGap > 0 ? meanPulse / meanGap : 0;
  const dutyCycle = totalTime > 0 ? totalHigh / totalTime : 0;

  return {
    meanPulseWidth: meanPulse,
    stdPulseWidth: stdPulse,
    meanGapWidth: meanGap,
    stdGapWidth: stdGap,
    pulseGapRatio,
    dutyCycle,
    pulseCount: pulses.length,
    minPulse: Math.min(...highPulses),
    maxPulse: Math.max(...highPulses),
    minGap: Math.min(...lowPulses),
    maxGap: Math.max(...lowPulses)
  };
}

// Helper functions
function mean(arr) {
  return arr.length > 0 ? sum(arr) / arr.length : 0;
}

function sum(arr) {
  return arr.reduce((a, b) => a + b, 0);
}

function std(arr) {
  const m = mean(arr);
  const variance = arr.reduce((acc, val) => acc + Math.pow(val - m, 2), 0) / arr.length;
  return Math.sqrt(variance);
}

4. Pattern Decoder (pattern-decoder.js)

/**
 * Main Pattern Decoder
 * Port of PatternDecoder class
 */

import { ProtocolDatabase } from './protocol-database.js';
import { identifyPulseWidths, decodeToBits } from './pulse-analyzer.js';
import { extractFingerprint } from './fingerprint.js';

export class DeviceMatch {
  constructor(protocol, confidence, matchMethod, details) {
    this.protocol = protocol;
    this.confidence = confidence;
    this.matchMethod = matchMethod;
    this.details = details;
  }

  get name() { return this.protocol.name; }
  get manufacturer() { return this.protocol.manufacturer; }
  get category() { return this.protocol.category; }
}

export class PatternDecoder {
  constructor() {
    this.protocolDb = new ProtocolDatabase();
  }

  /**
   * Decode device from signal metadata
   * @param {Object} metadata - { frequency, raw_data: [] }
   * @returns {DeviceMatch[]}
   */
  decode(metadata) {
    if (!metadata.raw_data || metadata.raw_data.length === 0) {
      return [];
    }

    const pulses = metadata.raw_data;
    const frequency = metadata.frequency;

    const matches = [];

    // Strategy 1: Timing Pattern Analysis
    matches.push(...this._decodeTimingPatterns(pulses, frequency));

    // Strategy 2: Statistical Fingerprinting
    const fingerprint = extractFingerprint(pulses);
    matches.push(...this._matchFingerprint(fingerprint, frequency));

    // Deduplicate and rank
    return this._rankMatches(matches);
  }

  _decodeTimingPatterns(pulses, frequency) {
    const matches = [];

    // Identify pulse widths
    const { shortPulse, longPulse } = identifyPulseWidths(pulses);

    if (shortPulse === 0 || longPulse === 0) {
      return [];
    }

    // Decode to binary
    const bitPattern = decodeToBits(pulses, shortPulse, longPulse);

    // Find matching protocols
    const protocolMatches = this.protocolDb.findByTiming(
      shortPulse,
      longPulse,
      frequency
    );

    for (const proto of protocolMatches) {
      // Calculate confidence
      const timingError = Math.abs(proto.shortPulseUs - shortPulse) / proto.shortPulseUs;
      const timingConfidence = Math.max(0, 1.0 - timingError);

      const bitCount = bitPattern.length;
      const bitCountMatch = (proto.minBits <= bitCount && bitCount <= proto.maxBits);
      const bitConfidence = bitCountMatch ? 1.0 : 0.5;

      let patternConfidence = 0.7;
      if (proto.preamblePattern && bitPattern.includes(proto.preamblePattern)) {
        patternConfidence = 1.0;
      } else if (proto.syncPattern && bitPattern.includes(proto.syncPattern)) {
        patternConfidence = 0.9;
      }

      const overallConfidence = (
        timingConfidence * 0.4 +
        bitConfidence * 0.3 +
        patternConfidence * 0.3
      );

      if (overallConfidence >= 0.6) {
        matches.push(new DeviceMatch(
          proto,
          overallConfidence,
          'timing_pattern',
          {
            shortPulseUs: shortPulse,
            longPulseUs: longPulse,
            bitCount,
            bitPattern: bitPattern.substring(0, 64),
            timingError: `${(timingError * 100).toFixed(2)}%`
          }
        ));
      }
    }

    return matches;
  }

  _matchFingerprint(fingerprint, frequency) {
    const matches = [];
    const candidates = this.protocolDb.findByFrequency(frequency);

    for (const proto of candidates) {
      const expectedMeanPulse = (proto.shortPulseUs + proto.longPulseUs) / 2;
      const pulseError = Math.abs(expectedMeanPulse - fingerprint.meanPulseWidth) / expectedMeanPulse;

      const expectedCount = proto.typicalPulseCount;
      const countError = Math.abs(expectedCount - fingerprint.pulseCount) / expectedCount;

      const pulseSimilarity = Math.max(0, 1.0 - pulseError);
      const countSimilarity = Math.max(0, 1.0 - countError);

      const overallConfidence = (pulseSimilarity * 0.6 + countSimilarity * 0.4) * 0.8;

      if (overallConfidence >= 0.4) {
        matches.push(new DeviceMatch(
          proto,
          overallConfidence,
          'fingerprint',
          {
            meanPulseUs: fingerprint.meanPulseWidth.toFixed(1),
            pulseCount: fingerprint.pulseCount,
            dutyCycle: `${(fingerprint.dutyCycle * 100).toFixed(2)}%`,
            pulseError: `${(pulseError * 100).toFixed(2)}%`,
            countError: `${(countError * 100).toFixed(2)}%`
          }
        ));
      }
    }

    return matches;
  }

  _rankMatches(matches) {
    // Group by protocol name
    const byProtocol = {};

    for (const match of matches) {
      const name = match.protocol.name;
      if (!byProtocol[name]) {
        byProtocol[name] = [];
      }
      byProtocol[name].push(match);
    }

    // Keep best match per protocol
    const bestMatches = [];
    for (const protoMatches of Object.values(byProtocol)) {
      const best = protoMatches.reduce((a, b) =>
        a.confidence > b.confidence ? a : b
      );
      bestMatches.push(best);
    }

    // Sort by confidence
    return bestMatches.sort((a, b) => b.confidence - a.confidence);
  }
}

5. Main Export (index.js)

/**
 * GigLez Pattern Decoder - JavaScript Implementation
 * Browser and Node.js compatible
 */

export { PatternDecoder, DeviceMatch } from './pattern-decoder.js';
export { ProtocolDatabase, ProtocolSignature } from './protocol-database.js';
export { identifyPulseWidths, decodeToBits, kMeans } from './pulse-analyzer.js';
export { extractFingerprint } from './fingerprint.js';

// Default export for convenience
import { PatternDecoder } from './pattern-decoder.js';
export default PatternDecoder;

JavaScript Integration: Usage Examples

Example 1: Node.js (Backend)

import PatternDecoder from './src/matcher/js/index.js';

// Parse .sub file (assume you have this)
const metadata = {
  frequency: 433920000,
  raw_data: [788, -1004, 1194, -68, 3094, -162, 786, -66, 164, -200, ...]
};

// Decode
const decoder = new PatternDecoder();
const matches = decoder.decode(metadata);

console.log(`Found ${matches.length} matches:`);
for (const match of matches) {
  console.log(`  ${match.name} (${match.manufacturer}) - ${(match.confidence * 100).toFixed(1)}%`);
}

Example 2: Browser (Frontend)

<!DOCTYPE html>
<html>
<head>
  <title>GigLez Pattern Decoder</title>
</head>
<body>
  <h1>Decode .sub File</h1>
  <input type="file" id="fileInput" accept=".sub">
  <div id="results"></div>

  <script type="module">
    import PatternDecoder from './src/matcher/js/index.js';

    const decoder = new PatternDecoder();

    document.getElementById('fileInput').addEventListener('change', async (e) => {
      const file = e.target.files[0];
      const text = await file.text();

      // Parse .sub file (simple parser)
      const metadata = parseSubFile(text);

      // Decode
      const matches = decoder.decode(metadata);

      // Display results
      const resultsDiv = document.getElementById('results');
      resultsDiv.innerHTML = `
        <h2>Found ${matches.length} matches:</h2>
        <ul>
          ${matches.map(m => `
            <li>
              <strong>${m.name}</strong> (${m.manufacturer || 'Unknown'})
              <br>Confidence: ${(m.confidence * 100).toFixed(1)}%
              <br>Method: ${m.matchMethod}
            </li>
          `).join('')}
        </ul>
      `;
    });

    function parseSubFile(text) {
      // Simple .sub parser (extract frequency and RAW_Data)
      const frequencyMatch = text.match(/Frequency: (\d+)/);
      const rawDataMatch = text.match(/RAW_Data: ([\d\s-]+)/);

      if (!frequencyMatch || !rawDataMatch) {
        throw new Error('Invalid .sub file format');
      }

      const frequency = parseInt(frequencyMatch[1]);
      const raw_data = rawDataMatch[1].trim().split(/\s+/).map(Number);

      return { frequency, raw_data };
    }
  </script>
</body>
</html>

Example 3: React Component

import React, { useState } from 'react';
import PatternDecoder from '../matcher/js/index.js';

export function SubFileDecoder() {
  const [matches, setMatches] = useState([]);
  const decoder = new PatternDecoder();

  const handleFileUpload = async (e) => {
    const file = e.target.files[0];
    const text = await file.text();
    const metadata = parseSubFile(text);
    const results = decoder.decode(metadata);
    setMatches(results);
  };

  return (
    <div>
      <input type="file" onChange={handleFileUpload} accept=".sub" />
      {matches.length > 0 && (
        <div>
          <h2>Decoded Devices ({matches.length})</h2>
          {matches.map((match, i) => (
            <div key={i} className="match-card">
              <h3>{match.name}</h3>
              <p>Manufacturer: {match.manufacturer || 'Unknown'}</p>
              <p>Category: {match.category}</p>
              <p>Confidence: {(match.confidence * 100).toFixed(1)}%</p>
              <p>Method: {match.matchMethod}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Part 4: Implementation Roadmap

Phase 1: Cleanup (1 day)

Priority: Remove redundant code

  1. Delete redundant matchers (~2 hours)

    • Remove strategies_orm.py
    • Remove simple_matcher.py
    • Remove rtl433_matcher.py
    • Update imports across codebase
  2. Consolidate API (~2 hours)

    • Rename main_simple.pyapp.py (or keep as main_simple.py)
    • Archive old main.pymain_orm_legacy.py
    • Update start_web.sh to use correct file
  3. Archive old scripts (~1 hour)

    • Create scripts/archive/ directory
    • Move 11 one-time scripts
    • Update scripts/README.md
  4. Review parser usage (~1 hour)

    • Check if raw_parser.py, gps_extractor.py, wardriving_importer.py are used
    • Move to deprecated/ if unused

Outcome: ~1,500 lines of code removed, cleaner structure


Phase 2: Fix Pattern Decoder Integration (2 hours)

Priority: Make pattern decoder work in production

  1. Update API to use strategies.py (~1 hour)

    # Replace simple_matcher with MatchEngine
    from src.matcher.engine import MatchEngine
    from src.matcher.strategies import *
    
    engine = MatchEngine()
    engine.add_strategy(ExactMatcher())
    engine.add_strategy(FrequencyMatcher())
    engine.add_strategy(PatternBasedStrategy())  # NEW!
    engine.add_strategy(RTL433DecoderStrategy())
    
  2. Test pattern decoder in API (~1 hour)

    • Upload test .sub files
    • Verify pattern matches appear in results
    • Check confidence scores

Outcome: Pattern decoder fully integrated and working


Phase 3: JavaScript Implementation (3-5 days)

Priority: Port pattern decoder to JavaScript

  1. Core implementation (~2 days)

    • protocol-database.js (port 18 protocols)
    • pulse-analyzer.js (K-means, pulse detection)
    • fingerprint.js (statistics)
    • pattern-decoder.js (main logic)
    • index.js (exports)
  2. Testing (~1 day)

    • Unit tests (Jest or Mocha)
    • Test with same .sub files as Python
    • Verify decode rate matches (44.4%)
  3. Browser integration (~1-2 days)

    • Build .sub file parser
    • Create demo HTML page
    • Optimize for bundle size
    • Add to GigLez frontend

Outcome: Client-side decoding capability


Phase 4: Optimization (1-2 days)

Priority: Improve decode rate

  1. Lower confidence thresholds (~30 min)
  2. Add more protocols (~2 hours)
  3. Adaptive timing tolerance (~1 hour)
  4. Enhance pattern matching (~2 hours)

Target: 60%+ decode rate (vs current 44.4%)


Summary

Code to Remove

File Lines Reason
strategies_orm.py 356 Replaced by strategies.py
simple_matcher.py 351 Replaced by strategies.py
rtl433_matcher.py 352 Replaced by strategies.py
11 archived scripts ~800 One-time use, move to archive/
Total ~1,859 lines 19% reduction

Code to Add

Component Lines (estimate) Purpose
JavaScript protocol DB ~300 Client-side decoding
JavaScript decoder ~400 Core logic
JavaScript utilities ~200 K-means, fingerprinting
Total ~900 lines New capability

Net Result

  • Remove: 1,859 lines of redundant Python
  • Add: 900 lines of new JavaScript
  • Net reduction: 959 lines (-10%)
  • New capability: Client-side decoding
  • Improved maintainability: Single matcher system

Next Steps

Immediate (Today):

  1. Review this plan
  2. Get approval for deletions
  3. Start Phase 1: Cleanup

Short-term (This Week):

  1. Complete Phase 1 & 2
  2. Start Phase 3: JavaScript implementation

Medium-term (Next Week):

  1. Complete JavaScript implementation
  2. Test in browser
  3. Optimize decoder performance

Status: Plan Complete - Ready for Implementation Approval Needed: Yes (for deletions) Risk Level: Low (all changes are backwards-compatible or cleanup)