The per-device entries in an upload's matched_devices list were labelled
with the ML overall predicted_category, producing contradictions like
"Acurite Tower Sensor -> Fan Controller". The engine now preserves each
DeviceMatch's true catalog category in match_details["device_category"],
and the API surfaces that per device. The headline device_category still
reflects the ML overall call, so the honest gate-metric category is
unchanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reconstruct a signed pulse train from BinRAW Data_RAW demodulated bits
(run-length encoded at TE-µs steps) so BinRAW files populate raw_data and
flow through the existing RAW identification pipeline. Previously 0/120
BinRAW files yielded a predicted_category; now 118/120 categorize via the
live SignatureMatcher engine. RAW code path is unchanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds reproducible tooling to test whether genuinely-independent rtl_433 test
captures can relieve the statistical classifier's minority-class starvation
(train: Weather 12, Security 2). Local Flipper corpora are byte-identical
mirrors, so rtl_433 is the only independent on-disk source.
- rtl433_iq_demod.py: .cu8 (interleaved uint8 IQ) -> Flipper-style signed µs
pulse train via amplitude/OOK demod. A quality-gate self-rejects FSK/degenerate
captures. Recovered pulse widths match rtl_433's own -A analysis.
- experiment_rtl433_augment.py: conservative folder->category map + an HONEST
evaluation — train on Flipper (group-disjoint) +/- rtl_433, score on held-out
FLIPPER (regression guardrail) and held-out rtl_433 (new capability).
RESULT: adding rtl_433 to training DEGRADES the Flipper gate metric
(balanced 0.625 -> 0.412), collapsing Remote Control (20/26 -> 7-10/26) as
cross-domain samples bleed into Flipper's remote region and their volume swamps
it. Small capped adds keep Flipper within seed-noise while gaining large
rtl_433-domain Weather recognition — a coverage-vs-precision tradeoff, not a
clean win. Per gate-metric discipline the production model is NOT regenerated;
the tooling is kept to re-measure in-domain once real Flipper weather/security
captures arrive via the platform.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ships the production serving path (FastAPI upload/map UI + statistical
device-category identification) as a lean, reproducible container:
- Dockerfile: python:3.10-slim, non-root user, /health HEALTHCHECK, serves
uvicorn src.api.main_simple:app. Bakes in the joblib category model and the
static rtl_433 protocol table; excludes the 11 GB test corpora and the
benched Phase 3B CNN binaries. Built image is 417 MB.
- requirements-prod.txt: pins matched to the versions that actually train/serve
the model (scikit-learn 1.6.1 / numpy 2.2.6) so joblib.load() stays valid.
torch/onnx intentionally absent — the CNN is benched, not on the serving path.
- docker-compose.yml: single service, named volume seeds from baked data/ on
first run then persists captures_simple.json writes across restarts.
- .dockerignore: keeps data/, venv/, vendored firmware, docs out of the image.
Validated: image builds, container reports healthy, and the statistical
classifier loads + predicts inside the container.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Trains a PyTorch 1D CNN on encoded RAW pulse arrays as the Phase 3B leg
of the designed ensemble, scored on the SAME group-disjoint held-out test
set as the heuristic and statistical models (GroupShuffleSplit by device
sub-folder, no near-duplicate leakage).
RESULT — CNN is data-starved and loses decisively:
CNN balanced 0.338, top-1 0.795 (garage-inflated)
statistical balanced 0.625, top-1 0.868
heuristic top-1 0.300
Only 489 train samples with severe class imbalance (Garage 565, Security 2,
Weather 12). A blend sweep confirmed every non-zero CNN weight degrades the
statistical model (0.625 -> 0.613 at 15% CNN, worse beyond), so the CNN is
NOT wired into decode(). Production ensemble stays heuristic + statistical,
both already live.
Committing the trainer + shared pulse_encoder (trainer/inference parity) +
metrics.json to document the reproducible negative result. The benched
.pt/.onnx binaries are intentionally NOT committed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds src/matcher/category_classifier.py — a lazy-loading inference wrapper
around the Phase 3A model that builds the frozen-order feature vector (parity
with the trainer) and predicts a device category with probabilities. It
degrades to a no-op when the model/joblib/sklearn are absent, so the heuristic
path is untouched without the artifact.
pattern_decoder.decode() gains a Strategy-4 step (RAW only): when the model is
confident it (1) becomes the source of the user-facing predicted_category — the
field the live upload path surfaces — since the classifier is far more accurate
on RAW than the heuristic router (Phase 3A held-out 62.5% balanced vs ~30%);
(2) re-ranks candidate protocols toward that family (0.25 ensemble weight); and
(3) supplies a capped-confidence category-only match when no protocol matched,
so RAW device-match coverage goes 56%->100%.
Tradeoff: the synthetic phase-0 gate drops 67%->42% top-3 (still passing the
30% floor) because the model is tuned to the real capture distribution, not
fabricated signals. Per project policy real-world accuracy is the gate metric,
so this is accepted. KEY-file path unchanged; end-to-end verified through
SignatureMatcher.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Trains sklearn RandomForest + GradientBoosting on 14 timing/statistical
features from RAW-only UberGuidoZ captures, with a group-aware split
(GroupShuffleSplit by device sub-folder) so near-duplicate captures never
leak across train/test. The heuristic CategoryRouter is scored on the exact
same held-out files for a fair comparison.
Best model (gradient_boost): 86.8% top-1 accuracy, 62.5% balanced accuracy
vs heuristic 30.0% top-1 / 55.9% routed. Raw accuracy is inflated by the
70%-Garage RAW class balance; balanced accuracy (~2x the heuristic) is the
honest number and meets the Phase 3A 60-75% target. Not yet wired into the
decoder ensemble.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
KEY .sub files (~35% of real captures) returned zero identification
because every path bailed on `not has_raw_data`, despite the file
already carrying a decoded Protocol name. Route those by protocol name
instead:
- category_router: add route_by_protocol() — device-specific brands
(CAME/Nice/KeeLoq/Security+/Honeywell) map to one confident category;
generic shared encoders (Princeton/EV1527/Holtek/Intertechno) map to a
broad allowed family set, since the same silicon spans
remote/doorbell/fan/gate.
- pattern_decoder.decode: emit a decoded_key DeviceMatch (details carry
predicted_category) for KEY files instead of [].
- device_identifier.identify: only bail when there is neither RAW data
nor a protocol name; gate statistical scoring on has_raw_data.
Adds scripts/benchmark_realworld.py — validates the real pipeline
against the real UberGuidoZ corpus (folder = ground-truth device type),
reporting top-1, routed (truth in allowed set), and coverage.
Measured on n=344 (seed 42): coverage 58%→93%, routed 59.3%→65.2%;
generalizes on seed 7 (93%/63.6%). RAW path byte-identical (no
regression); synthetic phase-0 gate still passes (top-3 67%).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds src/api/ingest.py and refactors the live upload handler to auto-detect
and route each submission by format instead of assuming .sub:
- Flipper .sub -> existing parser + signature matcher (unchanged)
- rtl_433 .json/.ndjson -> decoded; model is the device (conf 1.0)
- Wigle-style .csv -> decoded; one observation per row (conf 0.9)
- .zip batch -> recursed; any mix of the above
Also adds stable dedup (SHA256 for whole files, composite key for decoded
records) so re-submissions are skipped rather than stored twice, optional
GPS privacy rounding via manifest privacy_gps_decimals, a session-level GPS
fallback, and helpful errors for unsupported formats. Documented in
docs/SUBMISSION_FORMAT.md with rtl_433/CSV sample fixtures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
map.js read capture.captured_at, but the backend stores the field as
`timestamp`, so every marker popup rendered "Invalid Date". Fall back to
timestamp and guard against unparseable values. Regenerated README popup
screenshot to reflect the fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hero screenshot of the live Leaflet device map plus a marker popup showing an
auto-identified device with confidence, and a Mermaid diagram of the
parse -> category-route -> match -> map/export pipeline. Documents simple-mode
run steps, API surface, and honest Phase-0 accuracy caveats.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dataset export (source data for model training):
- GET /api/v1/export?format=jsonl|csv|geojson with category/data_source
filters; streams a labeled dataset (signal params + identified device +
routed category) suitable for training a Sub-GHz classifier.
SQLite dev-mode (corrects FABLE brief: SQLite was NOT a drop-in swap):
- models.py made dialect-aware — JSONB->JSON, ARRAY(Text)->JSON, TSVECTOR
->Text via .with_variant(); PostGIS Geometry column + GiST index only
defined when not on SQLite (lat/lon + haversine bbox used instead).
- config/database.py honors DATABASE_URL / a full-URL override and builds
a SQLite engine (check_same_thread=False, no server pool) when the URL
is sqlite; PostgreSQL keeps pooling + UTC session.
Verified: create_all + Capture/CaptureMatch/Device CRUD + JSON round-trip
+ bbox query all work on sqlite; postgres mode still defines geom + gist
index; 52/52 unit tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- main_simple.py upload: map manifest entries by filename so each file
gets its own GPS/timestamp (previously every file was assigned
captures[0], breaking multi-file uploads)
- device_category now uses the category router's real category
(e.g. "Remote Control") instead of the mislabeled "manufacturer -
device_name" string; also surface category per matched device
- add PLAN_TO_PROD.md / FABLE.md development briefs
- add CONTEXT.md (leaked PAT redacted from remote URL)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a rules-based device category router that classifies a signal by
frequency band + timing ratio + pulse count BEFORE the per-protocol
scoring loop, restricting the candidate set. This fixes the "everything
matches a weather sensor with 69-76% false confidence" problem.
- src/matcher/category_router.py: frequency-band + timing routing
- pattern_decoder.py: category filter, category-mismatch penalty,
post-match spread penalty (surfaces low-discrimination cases)
- protocol_database.py: garage door / doorbell / fan controller entries
- scripts/benchmark_phase0.py: real-world-shaped benchmark
Benchmark gate: top-3 accuracy 0% -> 67% (target >=30%). 52/52 unit
tests pass. NOTE: benchmark .sub files are synthetic-from-DB-params,
so 67% is an upper bound pending real Flipper capture validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Created comprehensive onboarding document for experienced RF wardrivers to:
- Understand GigLez platform capabilities (6-component scoring, 299 protocols, GPS validation)
- Review current performance metrics (33% synthetic, 0% real-world accuracy)
- Identify data format integration needs (rtl_433 JSON, URH, GPX, etc.)
- Share their wardriving workflows and preferences
Document includes:
- What's working: Device ID system, protocol database, GPS privacy features
- Critical gaps: Real-world performance analysis (REAL_CAPTURE_ANALYSIS.md findings)
- Data format questions: Hardware, file formats, GPS association methods, batch uploads
- Integration roadmap: Prioritized format support based on community feedback
- Response template: Copy/paste survey for wardrivers to share their setup
Purpose: Gather requirements from experienced RF community to build seamless upload workflows for existing wardriving tools (RTL-SDR, HackRF, rtl_433, URH, etc.) beyond just Flipper Zero .sub files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Problem
npm test was failing with 'Missing script: test' error.
Project has Python tests but package.json only defined test:decoder script.
## Solution
Added test scripts to package.json:
- test: Runs pytest (default npm test command)
- test:python: Explicit Python test runner
- test:coverage: Runs tests with coverage report
## Verification
✓ npm test now runs successfully
✓ All 56 Python tests pass
✓ 2.54s execution time
Test suite includes:
- 4 RTL_433 integration tests
- 26 GPS validation tests
- 15 preamble/frequency tests
- 11 timing analyzer tests
🎯 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Key Improvements
### 1. Fixed Test Data Generator
- **Acurite 609TXC**: Corrected timing from 500/1000μs to 1000/2000μs
- **Oregon Scientific v2.1**: Corrected timing from 500/1000μs to 488/976μs
- Test signals now match actual protocol specifications
### 2. Enhanced Scoring Algorithm
**New Formula**: T:40% + P:25% + R:20% + F:10% + B:5%
**Timing (40% - increased from 35%)**:
- Dual timing validation (both SHORT and LONG pulses)
- Weighted average (60% SHORT, 40% LONG) for better discrimination
**Timing Ratio (20% - NEW)**:
- Compare LONG/SHORT pulse ratios
- Highly discriminative (2:1 vs 3:1 ratios separate protocol families)
- Catches timing relationship errors
**Preamble (25% - maintained high weight)**:
- Strong preamble match boost (+5% for >90% preamble + >80% overall)
- Alternating preambles highly discriminative
**Frequency (10% - tightened)**:
- Tighter tolerance: ±100kHz (was ±200kHz)
- Gradual falloff to 500kHz
**Bit Count (5% - reduced from 20%)**:
- Relaxed scoring (unreliable in synthetic signals)
- Flexible range matching
**Uniqueness Bonus**:
- +20% bonus for unique timing (only 1 similar protocol)
- +15% for 2 similar protocols
- +10% for 3 similar protocols
### 3. Results
**Top-K Accuracy**:
- Top-1: 33.3% (4/12 correct)
- Top-3: 50.0% (6/12 in top 3)
- **Family matches**: Acurite 609TXC ranks #2 (beaten by Acurite 896 - same timing)
- **Near misses**: Oregon Scientific v2.1 ranks #2 (beaten by LaCrosse - similar protocols)
**Confidence Distribution**:
- High (>80%): 66.7% (down from 75% - tighter scoring reduces overconfidence)
- Medium (50-80%): 25%
- Low (<50%): 8.3%
**Performance**:
- 95ms avg total time (parse + match)
- Faster than iteration 5 due to optimized scoring
### 4. Discrimination Improvements
**Before (Iteration 5)**:
- Wrong protocols scored 85-87% confidence
- Acurite 609TXC got "Clipsal CMR113" at 86.4% (rank 118)
- Princeton got "SimpliSafe" at 79.4% (not found in top results)
**After (Iteration 6)**:
- Acurite 609TXC gets "Acurite 896" at 87.3% (rank 2 - family match)
- Oregon Scientific v2.1 gets "Oregon Scientific v2.1" at 92.1% (rank 2)
- PT2262 now CORRECT at 91.7% (was rank 7)
### 5. Technical Changes
**pattern_decoder.py**:
- Added `_calculate_uniqueness_bonus()` method
- Removed encoding detection (too unreliable for synthetic data)
- Added timing ratio validation
- Tighter frequency tolerance
- Preamble match boost for strong matches
**test_data_generator.py**:
- Fixed Acurite 609TXC timing parameters
- Fixed Oregon Scientific v2.1 timing parameters
- Added encoding metadata to test cases
**TEST_RESULTS_SUMMARY.md**:
- Updated with iteration 6 results
- 50% top-3 accuracy (up from 33%)
## Conclusion
While top-1 accuracy remains 33%, **top-3 accuracy improved to 50%**, and the ranking quality is significantly better. Wrong matches (Acurite 896 vs Acurite 609TXC) are now **family matches** with identical timing signatures, which is acceptable behavior. The scoring now correctly discriminates between protocol families based on timing ratios.
The key insight: Many protocols in the database are variants of the same base protocol. Getting the right *family* is more important than exact model match for IoT device mapping.
🎯 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## 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>
- Created RF_TEST_DATABASES.md with 6 primary databases (30,000+ files)
- Added RTL433_TESTING_GUIDE.md explaining 0% decode rate
- Created download_rf_test_datasets.sh to fetch all test data
- Documented web search results from Google dorking
- Listed repositories: rtl_433_tests, FlipperZero-Subghz-DB, UberGuidoZ, etc
- Included testing strategy and expected success rates
Comprehensive analysis of how the system handles .sub files with
multiple frequencies.
Key findings:
- Standard Flipper Zero files contain ONE frequency per file
- Multiple .sub files in one upload: WORKS correctly
- Multiple Frequency: lines in one file: Only last used (silent loss)
- Recommended: Add validation warning for multi-frequency edge case
Analysis includes:
- Current parser behavior
- Flipper Zero file format specification
- 4 solution options with pros/cons
- Testing scenarios
- Impact assessment
- Short-term and long-term recommendations
Fixes issue where modal shows same data for all captures.
Changes:
- Add timestamp-based cache buster to API requests
- Add cache-control headers (no-cache, no-store, must-revalidate)
- Clear modal content when closing to prevent stale data
- Ensures each capture loads fresh, unique data
This resolves the issue where clicking 'View Data' on different
markers showed the same cached data instead of unique per-capture data.
Complete implementation summary including:
- Executive summary of achievements
- Implementation details for Phase 2 & 3
- Validation results (20% improved, +0.35 max improvement)
- Files changed and git history
- Deployment status and next steps
- Performance impact analysis
- Lessons learned and known limitations
Status: COMPLETE & READY FOR PRODUCTION
- Add Leaflet.heat plugin (leaflet-heat.js)
- Create renderHeatmap() function with confidence-based intensity
- Toggle between markers and heatmap view
- Support frequency filtering in heatmap mode
- Custom gradient: blue -> cyan -> lime -> yellow -> red
- Configurable radius (25px), blur (15px), and maxZoom (17)
- Console logging for debugging heatmap rendering
- Intensity based on device match confidence (0.5 default)
- Log function entry with captureId
- Log DOM element lookups
- Log fetch request URL and response status
- Log received data and rendered HTML
- Log error details to console.error
- This will help identify exact failure point in modal display
- Remove inline onclick attribute from close button
- Add ID-based event listener binding in DOMContentLoaded
- Add preventDefault and stopPropagation to close handler
- Add console logging for debugging
- Add null checks for modal elements
Added detailed analysis of GigLez's RF device attribution system with:
1. DEVICE_ATTRIBUTION_ANALYSIS.md enhancements:
- Expanded frequency-to-device mapping for all ISM bands
- Added comprehensive modulation type descriptions (OOK/ASK/FSK/PWM/PPM/Manchester/PCM)
- Implemented modulation detection algorithms
- Enhanced RTL_433 pulse analysis documentation
- Added Flipper Zero ProtoView features
- Extended Keeloq protocol support details
2. FREQUENCY_DEVICE_CHART.md (new):
- Visual frequency band mapping (300-928 MHz)
- Detailed device type categorization by frequency
- Regional frequency allocations (FCC/ETSI)
- Protocol prevalence statistics
- Signal strength and range data
- Device attribution confidence strategies
- Python categorization example code
Key Research Findings:
- 433MHz is most popular globally (weather stations, remotes, sensors)
- 315MHz primary in North America (TPMS, security, automotive)
- 868/915MHz for advanced IoT (smart meters, LoRa, industrial)
- Frequency + Modulation + Protocol = high-confidence identification
- RTL_433's 200+ protocol database as integration target
- Flipper Zero's 13,717 .sub files for training data
Recommendations prioritized for implementation:
1. RTL_433 protocol database integration
2. Pulse pattern analysis for RAW signals
3. Modulation detection algorithms
4. Frequency-based device categorization
5. Checksum validation
6. Machine learning classification (long-term)
This provides comprehensive foundation for improving GigLez's
device attribution accuracy from 60-70% to 85-90%.