Repository navigation
Coverage wedge: where the node is proven to see, from ADS-B-verified detections - #38
Conversation
…ions A node had no way to show where it can actually see. The only coverage picture available was a declared beam, which is configuration, not measurement. The tracker now records where ADS-B-verified detections land and serves a wedge of proven coverage, with each unlabelled track's delay ellipse cut against it, for retina-gui's Tracker page to draw. A position counts only when blah2 matched the detection to the track's own aircraft and projected the fix to the detection's time, at most 10 s stale. Passes are binned by bearing in 5 degree slices, and each slice holds the second-furthest range two different aircraft reached over a sliding 24 hours, so one wrong ADS-B match cannot push the wedge out. This deliberately does not port retina-server's evidence-only polygon, whose point-count thresholds measure dwell time rather than independent passes. The receiver and transmitter come from blah2's own location block, which the tracker already reads. The wedge is saved beside events.jsonl so a restart keeps it, and starts again when either end moves or on POST /coverage/reset. Track points on /events now carry their verified lat/lon. Arcs are flat, as retina-server's are. New endpoints are GET /coverage, /coverage/arcs and /coverage/track/<id>. Covered by tests/test_coverage.py (46 tests) and an updated history stats test; 410 pass. Design in docs/coverage-wedge.md. Deployed by hand to a test node for live trials; the thresholds are not yet validated across sites. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… keep good fixes On a test node's first day the main lobe's held ranges sat at 87-98% of the furthest bistatic range blah2 processes (its delayMax), so the wedge was reporting the node's configuration there and reading as antenna reach. /coverage now serves that limit as span_ring, the ellipse of the furthest processed differential, and flags each slice whose held range reaches 85% of it (limited, limited_bins), so the page can say which slices are capped by the setting rather than by the antenna. The 10 s fix-age limit came from retina-server, where it bounds an unprojected fix. blah2 projects every fix to the detection's time, and on that node the limit refused 22% of correctly tagged detections, since ADS-B arrives in batches about 8.6 s apart and ages run up to 20 s. Measured on the same recording, as the jump when a fresh fix replaces a projected one, the projection error was a median 0.07-0.14 km at every age up to 20 s, far inside a 5 degree slice. The limit is now blah2's own 20 s projection cap. Six new tests cover the limit, the ring and the flags; 416 pass. docs/coverage-wedge.md records the measurement and the new section. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On a test node's second day the wedge proved small slices (1-8 km) in almost every direction around the receiver, including straight away from the main lobe. Detections there sat at a median 45 degrees elevation: aircraft passing nearly overhead, which a flat map cannot tell from the antenna's horizontal pattern. Filtering by elevation does not separate them cleanly, because on a short-reach node with traffic 4-5 km up the main lobe's own detections sit at 26-30 degrees. So slices are split by ground range instead. A slice held under coverage.close_in_km (10 km) stays proven and counted, but /coverage serves it in close_lobes rather than lobes and flags it in close_in, so the page can draw it as close-in overhead traffic. 10 km clears the furthest ring slice seen (8.1 km); 8 km left it as a solid spike. It was set from one site and should be checked on others. Handling elevation properly is the 3D wedge. A new test covers the split; 417 pass. docs/coverage-wedge.md gains a Close-in slices section. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Claude finished @Purple10101's task in 34s —— View job Review of PR #38: coverage wedgeI read the diff by static review only. I did not run ruff or pytest, and the The design is sound. Only ADS-B-verified positions feed the wedge, and the tracker is untouched. The sliding second-best-range rule, the lock discipline and the input validation ( Worth fixing
Minor
Verified OK
|
…e file Review of #38 found two real faults in how the wedge is saved and restored. A coverage.json that parsed but was not an object, or held a non-numeric start time, raised out of load(), which runs at startup; under the node's restart: always that is a crash loop until someone deletes the file. Reproduced on a test node with the real entry point: the tracker exited 1 with an AttributeError at load. Damaged values elsewhere (a non-numeric reset time, an infinite range) were trusted in part. The state file is now validated whole before anything in it is used, and any failure leaves the wedge to start fresh. On the same node, with the same damaged file, the fixed tracker logs that it is ignoring it, serves /coverage and saves a valid file on reset. Saves also raced. The saver thread and a reset both wrote the same temp file, so a save that took its snapshot before a reset could land after it, leaving the pre-reset wedge on disk for the next restart to bring back. Saves are now serialised and snapshot inside the lock. The section banners in coverage.py are dropped, per the no-comments rule, and the docs record the dedupe bound, late detections and why the reset endpoint is trusted. 14 new tests reproduce both faults (8 failed before the fix); 431 pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Claude finished @Purple10101's task in 19s —— View job Review of PR #38: coverage wedgeThe No blocking issues found. Earlier review items that HEAD already fixes
Still open (minor)
Checked and OK
|
|
Thanks for the review. Fixed in ee13192, point by point: 1. Save race: fixed. It was worse than a logged error: a save that snapshotted before a reset could land after it, leaving the pre-reset wedge on disk for the next restart to bring back. Saves are now serialised by a save lock, and each takes its snapshot inside it. 3. Damaged state file: fixed. This was the serious one.
7. Style: the section banners in 5 and 8: now documented in Kept as is, with reasons in the docs:
431 tests pass; ruff, format and the dead-code gate are clean. 🤖 Generated with Claude Code |
Summary
Adds a coverage wedge: the region around the receiver where the node is proven to detect aircraft, built only from ADS-B-verified detections, never from a declared antenna aim. Alongside it, each unlabelled track's delay ellipse is cut against the wedge (solid inside, faint outside). retina-gui's Tracker page draws both on a map (companion PR in retina-gui).
Tracking is unchanged: this adds a recorder beside the tracker, positions on the history stream, and new endpoints.
What changed
delayMax, its reach is the node's configuration, not the antenna./coverageserves that ellipse (span_ring) and flags such slices (limited).close_lobes): close to the receiver they are mostly aircraft overhead, which a flat map cannot tell from the horizontal pattern.coverage.jsonbeside the-sevents file every 60 s and on reset; restarts keep the wedge (unlikeevents.jsonl). It starts again when the receiver or transmitter moves.GET /coverage,GET /coverage/arcs,GET /coverage/track/<id>,POST /coverage/reset.This deliberately does not port retina-server's evidence-only polygon: its point-count thresholds measure dwell time rather than independent passes, its 200-point FIFO cannot express a time window, and centre-of-bin vertices draw every lobe 5° narrower than its evidence.
Measured on jonathan-node-1
Live since 17 October (latest commit since 19 October 10:51 UTC).
delayMaxellipse, so jn1's reach there is set by its delay span./coverage6-24 ms; latest arcs 2-5 ms; a replay of the longest track 108 KB in 39 ms;coverage.json115 KB at a full day.Compatibility
lat/lon(null where unverified). Detection classes are unchanged; a consumer that ignores unknown keys is unaffected. Track points cost 28 bytes instead of 20 in/health's estimate.coveragesection, all defaulted (bin_deg,hold_s,max_position_age_s,min_pass_detections,close_in_km).locationis read from the blah2 config the tracker already loads. New--coverage-stateflag, defaulting beside the events file.Known limitations and follow-ups
min_pass_detections3,max_position_age_s20,close_in_km10, the 0.85 limit fraction) were set and validated on jonathan-node-1 only; fairforest B is next. jonathan-node-2 has no ADS-B labels, so it cannot build a wedge yet.GET /framelogs a BrokenPipe traceback when a client hangs up mid-reply; pre-existing, not from this change.Docs
docs/coverage-wedge.md(new), CLAUDE.md's test list, and the storage note inhistory.py.Verification
ruff check (E,F,W and the repo config), ruff format --check and pytest (417 passed) clean; the dead-code gate is clean. Checked end to end against a synthetic feed over TCP, and live on jonathan-node-1.
After this
🤖 Generated with Claude Code