Accept Auto-Calibrate outcomes from the nodes at POST /api/feedback/tower-outcome - #32
Conversation
|
Claude finished @jehanazad's task in 2m 20s —— View job Code review
No CLAUDE.md exists in this repo, so no repo-specific conventions to check beyond what's visible in the code itself. SummaryThis is a careful, well-tested piece of work. Auth ( I don't have anything blocking. Two minor, non-blocking observations:
Verified independently while reading:
Nice touches worth calling out: the |
…ower-outcome The fleet feedback ingest from the expected-area ranking branch, on its own so it can merge and start collecting rows while that ranking is reviewed. The ranking does not read the store yet; apply_feedback ships here and the ranking branch wires it in. Checked against retina-gui's calibrator rather than designed in the abstract: a row is a flattening of one entry of a run's history, the outcome vocabulary is the calibrator's verbatim, callsign's limit is the node's own TX_NAME_MAX_LENGTH, and a not_reached entry (no final gains) validates. Two additions over the ranking branch's shape: - run_id, and a partial unique index on (node_id, run_id, tower_key). A node that times out on the post and retries would otherwise land its run twice, and every duplicate doubles that run's weight. Retries now answer 200 with the rows under `ignored`, so the node knows to stop. - device_error, which the calibrator sets when the SDR wedged rather than reported a clean overload; kept apart from outcome so a later fit can weight the two differently. Own bearer secret (TOWER_FINDER_FEEDBACK_TOKEN), fail-closed like the admin one and deliberately not interchangeable with it: every node holds this one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
9551803 to
a049703
Compare
|
Claude finished @jehanazad's task in 3m 7s —— View job I'll analyze this and get back to you. |
Reconciles the ranking redesign with what landed on main since it branched: - The fleet feedback ingest merged separately as #32, with run_id dedupe and device_error on top of what this branch carried. Its files are taken from main wholesale; this branch keeps the ranking-side wiring (apply_feedback before the sort) and the README paragraph on how rows enter the ranking, folded into the newer section. - #33's terrestrial path loss and under-beam derating now shape received_power_dbm, which the sweep calibration reads. tower_scoring still models its own direct path with free space plus the horizon term, so the two direct-path figures differ; pointing scoring at path_loss/underbeam_loss for the direct path (and keeping free space for the target echo) is the follow-up #33 named. - Address lookup (#31) and the marker stacking fix (#29) merge cleanly apart from adjacent lines in app.py, the test helpers and the README API table. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Stands up the towers API's side of the calibration feedback loop so the nodes can start reporting. This is the fleet-feedback ingest from #30, split out so it can merge and accumulate rows while the ranking change in #30 is reviewed. #30 will need a rebase onto this once it lands; the ranking is what consumes the store.
What the nodes post
POST /api/feedback/tower-outcome, bearerTOWER_FINDER_FEEDBACK_TOKEN, one row or up to 100. The row shape was checked against retina-gui's calibrator (src/calibrator.py) rather than designed in the abstract: each row flattens one entry of a run'shistory(tower_name→callsign,fc→fc_hz,final_gain_*→gain_*,dwell_seconds→duration_s), theoutcomevocabulary is the calibrator's verbatim,callsignuses the node's own 32-character limit, and anot_reachedentry with no final gains validates. The README carries the field-by-field mapping for the retina-gui follow-up.Two additions over #30's shape
run_idwith a partial unique index on (node, run, tower). A node that times out on the post and retries would otherwise land its run twice, doubling that run's weight in the correction. A retry now answers200 {"stored": 0, "ignored": n}, which tells the node to stop. Rows without arun_id(archive job, older nodes) are never deduplicated.device_error, which the calibrator sets when the SDR wedged rather than reported a clean overload. Kept apart fromoutcomeso a later fit can weight them differently.Unchanged from #30
Own secret, fail-closed, not interchangeable with the admin token;
extra="forbid"so a misspelled field is a 422 rather than a weightless row; SQLite store on the runtime volume capped at 200k rows;GET /api/feedback/summaryfor admins;apply_feedbackships but nothing calls it yet.Verification
{"stored": 0, "ignored": 1}, misspelled field 422, summary 401 on the feedback token and 200 on the admin token, unknown/api/feedback/*path 404 rather than the SPA.Deploy
Set
TOWER_FINDER_FEEDBACK_TOKENinbackend/.envon each droplet (documented in the bootstrap step). The same value goes to retina-node's.envwhen retina-gui grows the posting side. Nodes hold no tower-finder credential today, so that distribution is new.🤖 Generated with Claude Code