Repository navigation
refactor(contacts): deepen the contact-sighting module - #12
Merged
Merged
Conversation
This was referenced Oct 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Candidate #4 from the architecture review: deepen the contact-sighting module. Identity + dedup only — destination unchanged.
Inventory (pre-refactor, origin/main a673e07)
_emit_event, app.py:247-252)str(event.get("id", event.get("channel", ""))); info picks channel/battery_ok/temperature_C/humidity/freq/rssiism:{model}:{ident}_CONTACT_WRITE_DT, anchored at last write; suppressed attempts don't refresh); 3.0s globalcontactsemit throttlestore.record_contact→ sqlite contacts PK →contactssocket event.decodedemitted every event_emit_adsb, app.py:372-386)a["hex"], skipped if falsy); label = flight.strip() or registration or hex.upper(); info = registration/country/alt/gs (non-None)adsb:{hex}adsbsocket event every update_phase_rtl433, scan.py:252){model or '?'}/{id|hex|channel or '?'}(id > hex > channel)_keyon device dictsphase.findings["devices"]→phase_completed;scan_decodedlive-emitted for every event. Never the store_phase_adsb, scan.py:295-298)phase.findings["aircraft"]→phase_completed. Never the storeNo other protocols construct sighting identity: Wi-Fi/BLE bands appear only as sweep band labels (scan.py BAND_MAP), with no sighting/dedup code. Frontend keys ADS-B markers by raw hex for map rendering only (waterfall.js:1489) — unchanged, not a sighting store.
Design
One deep module,
backend/contacts.py(named in docs/architecture.md; architecture-names check passes):ContactSighting+ism_sighting(event, lat, lon)/adsb_sighting(aircraft, registration, country)— derivation of key/kind/label/ident/info/position in one place. Enrichment (readsbr,icao_to_registration/icao_country) stays in app.py; the module owns how it joins the sighting.inventory_key(ism:{model}:{ident},adsb:{hex}) vs ephemeralscan_key({model}/{id|hex|channel}, hex alone for ADS-B). Key-byte parity: both are byte-identical to the old strings — the inventory key is the sqlite contacts PRIMARY KEY and crosses to the frontend, so renaming would orphan stored history. No key changed; none flagged for change.SightingThrottle(window_s)(register/seen; window anchored at last passed sighting) for live writes,ScanSightings(phase-lifetime, no TTL; first-wins+count for ISM, latest-wins for ADS-B) for scan findings. app.py and scan.py both consume these; no key string is built outside the module anymore (grep ism:in app/scan: none).Parked question — untouched
"Should Auto-Scan sightings enter the persistent inventory or stay scan-only?" is NOT answered. Scan sightings go exactly where they went today (phase findings only); scan.py has no store reference. Inventory contents are unchanged: same keys, same rows, same throttle. This PR only gives sighting identity/dedup one home.
Parity evidence
_emit_eventrecords exactly("ism:M:7", "ism", "M", "7", {...})once for two events whiledecodedis emitted twice;_emit_adsbrecordsadsb:abc123/UAL1 with position, no-hex aircraft skipped._phase_rtl433/_phase_adsb): devices["M/7", "Other/7"]with_count=2, findings hold the deduped lists,scan_decodedemitted for every event.Tests + mutant
tests/test_contact_sighting.py: 11/11 (written failing-first —ModuleNotFoundError: backend.contacts— then green).backend/contacts.pyonly → 6/11, reddening dedup across both consumers (test_app_emit_event_records_through_module,test_scan_ism_dedup,test_scan_phases_consume_module_and_stay_scan_only, + key-byte tests). Restored → 11/11.CI snapshot (local, CI-way)
All
tests/test_*.pyscript-style suites pass (24 files incl. new one),check_html_templates.pypass,check_architecture_names.pypass,node --checkon all frontend JS pass. GitHubtestcheck: pass (run 39144786324, 22s).Candidates #5–#6, auth, and other parked questions untouched. Do not merge without review.