The street-level review instrument (#103): production-path sheet, probe, reduction, and a pre-registered Denver pilot - #105
Conversation
The street-level review instrument must render exactly the crop Stage 1 cuts, and the production functions were unimportable: download_dataset.py imports inference_isolator, which loads the round-2 checkpoint from a relative path at module import time. Move fetch_panorama, both projections, and heading_to_azimuth verbatim into rampnet/gsv.py -- the consolidation CLAUDE.md already endorses (the KeypointModel precedent) -- and have download_dataset.py import them, so each still has exactly one definition. Only edits in the move are import wiring: cv2/requests/torch become function-local because requirements-dev.txt deliberately excludes cv2/requests and the test suite imports the module for its pure helpers. New pure helpers for #103: perspective_col_to_azimuth_deg and its inverse, the click-to-angle map. tests/test_gsv.py pins them against crop_half_angle_deg() (one definition of the crop geometry), pins the asymmetric strip edges (-18.4577/+18.3678), and drives a synthetic pano through the real renderer to pin the section-5j sign convention end to end (positive = clockwise = right of centre). 592 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…duction Three scripts, assembling the production pieces #103 inventoried: street_review_sheet.py -- renders, per record, the exact production view (rampnet.gsv fetch + projection, 90 deg at the record bearing, pitch -30) with the crop strip edges drawn where they truly are (asymmetric, -18.458/+18.368, from the one crop definition), the government bearing as the crosshair, a nonlinear degree ruler, and other records within the production 35 m inclusion radius as always-visible bearing markers. The verdict is a click -> signed ANGULAR offset in the section-5j residual convention. One pano per record by a recorded rule (nearest in 4-30 m, capture >= record date, tie-break newest); unjudgeable verdicts carry a mandatory reason tag so the street instrument's selection bias is measured, not assumed. Sites come from a built aerial sheet (--sites-from-verdicts, the Denver pairing) or fresh sampling via the aerial samplers unchanged. The section-5h lessons are structural, not remembered: pano absences are readable JSON markers (never zero-byte) with --refetch-absent; a failed search is never cached as a result; every input record leaves a terminal status in the manifest. The section-5l two-path bug class is prevented by construction: chips and verdict templates extend ONE base dict, and the JS export copies provenance by iterating the same Python field list. probe_panos_at_sites.py -- the sheet's dry run at exactly its sites (the aerial sheet's records), running the same imported pick rule, warming the same search cache, and reporting pick rate / date coverage / per-site failures with reasons. street_review_summary.py -- the reduction: frac-inside-strip against the true asymmetric edges (the gate quantity), the angular distribution through stage1_bearing_residual.summarize() verbatim so rows read against the section-5j corpus null, phantom/unjudgeable Wilson rates with the reason breakdown, a sign-flip null for systematic shift (never a test against zero), per-stratum rows, and the paired aerial calibration: each aerial click vector projected through the chosen pano geometry into a predicted residual (radial error predicts ~0, per section 5g). 40 new tests, including a Node harness that pins the JS click-to-angle map against the Python definition to 1e-6 and reads the real export payload. The new template drops the {{ }} escaping layer entirely -- plain __TOKEN__ substitution -- removing the blank-page hazard class rather than testing for it. 632 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Written BEFORE the review, on purpose: the pass/fail conditions for the street-level instrument on the one city whose answer is trusted (at most 1 of ~52 measured records outside the strip; |median| at the 1-3 deg floor; sign-flip p >= 0.05; phantom rate Wilson-compatible with the aerial 5.5%, ideally the same three records; reasons complete). If Denver fails, the instrument is wrong, not Denver. Also records the design facts a reader of the verdicts needs (sign convention, asymmetric edges, pano-pick rule, mandatory unjudgeable reasons) and what the instrument still cannot do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
) The probe ran before the build, as section 5o requires, and it earned its keep twice. First, coverage: 58 of 59 aerial sites pick a panorama under the rule (median chosen range 5.75 m, captures 2016-2026 median ~2021); the one failure, 68791, is a genuine GSV coverage gap recorded as its own status. Second, it reproduced the section-5h failure class on the new endpoint: 15 of 59 sites failed with HTTP 502 from GetMetadata -- rate limiting on pano-dense corners, all recoverable -- so cached_search now retries transient statuses with backoff before believing them (cheap: get_date_of_panorama is lru-cached, so a retry only re-POSTs what the last attempt missed), while schema drift still raises immediately. Tested both ways. Section 5o amended BEFORE any review: the pick rule samples the near field, so criterion 1's expectation is ~0.5 outside-strip records (5x the naive pooled figure), and the capture-vintage point -- street judges 2021+ reality where aerial judged ~2016 -- is stated next to the criteria, since a ramp rebuilt in between can legitimately disagree between instruments. 633 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eet (#103) The Denver build's first two panos failed, and the reason turned out to be bigger than the build: the streetviewpixels tile endpoint now returns HTTP 403 PERMISSION_DENIED to python-requests' default User-Agent, so the paper-era fetch_panorama -- verbatim -- fetches nothing at all today. Any explicit UA, including our honest RampNet-sourcing/1.0, gets the JPEG. The two-line fix lands in rampnet/gsv.py, where Stage 1 and the review instrument now share it; it is the one deliberate behavioural change to the otherwise-verbatim production code, documented at the constant. With that: 58 of 59 sites rendered in one pass (the sole drop is 68791, no_panos -- the genuine GSV coverage gap the probe predicted), 19 MB sheet, build 5035ec33. The unfilled verdicts template is committed as the built sheet's provenance: per-record pano choice, range, bearing, and terminal status, fixed before any review. The export->summary round trip is smoke- tested against the real sheet and the real aerial pairing (reviewer "smoke", not committed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er the gate honestly Code review of PR #105 found six things worth fixing before Jon's review hour. The first is the one that could only be fixed BEFORE the review happens. 1. The gate quantity censored its own denominator. `ramp_outside_view` marks a ramp visible but beyond the +/-45 deg render -- the largest coordinate error the sample can contain, and certainly outside a +/-18.4 deg strip -- yet it classified as unjudgeable and never reached `frac_inside_strip`. Criterion 1 was therefore conditional on judgeability, censored in exactly the direction that makes the instrument pass. The summary now reports `frac_inside_strip_bound` (every outside-view record counted as a failure) and SS5o gates on the bound. Occlusion unjudgeables stay out of both: they are missing at an *unknown* offset, which is what the second-vantage pass is for. SS5o gains an amendment log, because a pre-registration is only worth something if its edits are visible. 2. `phantom_disagreements` conflated "the aerial sheet could not look" with "the aerial sheet saw a ramp": an aerial-unjudgeable record has no `no_ramp` to compare against. The Denver pilot deliberately renders all 4 aerial unjudgeables, so this could have inflated the count by 4 of 58 at exactly the point criterion 4 is read. Now both instruments must have judged. 3. A rebuild silently overwrote a reviewed `verdicts.json` -- and the plan already includes two rebuilds after the review (`--refetch-absent`, the second-vantage pass). The build now refuses unless `--force`, and refuses BEFORE spending ~2,000 tile requests. 4. The Node harness re-implemented the page's state machine instead of driving it, so a regression in the real click/keydown handlers would have passed. It now drives `stage.onclick`, the captured keydown listener, and the seg() buttons the page itself wires up. Verified by mutation: breaking any of four clearing rules in the page is now caught; before, all four survived. 5. The renderer sign-convention test -- the only thing pinning SS5j's convention through the real projection -- was skipped in CI, because requirements-dev ships torch but no OpenCV. Added opencv-python-headless. 6. The summary reduced with this code's strip edges rather than the ones the sheet recorded, so re-reducing an old verdicts.json could silently change the gate. It now reads `manifest['projection']` and falls back, the same way `paired_calibration` already read the aerial manifest. Plus the minors: `--limit` truncated after the provenance strings were built, so a smoke run wrote a manifest claiming the full sample; the sampling path had no id-integrity check (a duplicate id silently handed a record another row's date); a partially reviewed sheet now says so loudly instead of quietly diluting every denominator; and the pano cache's cold-vs-warm JPEG generation is documented, since `sheet_build` hashes logic and not pixels. Sheet build is unchanged at 5035ec33, so the committed Denver sheet and its verdicts template stay valid. 74 tests across the five files (was 65). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Code review — and the fixes, in
|
Builds the instrument #103 designed: review location precision against the street-level imagery Stage 1 actually consumes — the panorama, the exact ±18.37° crop, and the government point's projected bearing — with the reviewer's verdict recorded as a signed angular offset in §5j's residual convention, so human review of candidate cities cross-validates against the automatic corpus null. Stacked on #97's branch (retarget to
mainafter #97 merges). Design brief and decisions: #103; the §5o pre-registration in this PR fixes the Denver pass/fail criteria before the review happens.What's here
rampnet/gsv.py— the production GSV path (fetch_panorama, both projections,heading_to_azimuth) lifted verbatim out ofdownload_dataset.py, which was unimportable (itsinference_isolatorimport loads the round-2 checkpoint at module scope).download_dataset.pynow imports from here — one definition each, theKeypointModelconsolidation precedent. Plus new pure helpers: the click↔angle maps, pinned againstcrop_half_angle_deg().fetch_panoramafetches nothing today; any explicit UA (ours:RampNet-sourcing/1.0) works. Two lines, documented at the constant.cached_searchretries transient statuses with backoff; schema drift still raises immediately.street_review_sheet.py— the sheet: full 90° production render with the strip edges drawn where they truly are (asymmetric, −18.458°/+18.368°), the bearing crosshair, a nonlinear degree ruler, and neighbour records' bearings (production 35 m inclusion) always visible to resolve which-ramp ambiguity. One pano per record by a recorded rule (nearest 4–30 m, capture ≥ record date, tie-break newest); mandatory reason tags on unjudgeable verdicts so the street instrument's selection bias is measured, not assumed. §5h made structural: pano absences are readable JSON markers (--refetch-absent), failed searches never cache, every input record leaves a terminal status. §5l's two-path bug class prevented by construction: chips and templates extend one base dict, and the JS export iterates the same Python field list.probe_panos_at_sites.py— the sheet's dry run at exactly its sites, running the same imported pick rule.street_review_summary.py— the reduction: frac-inside-strip against the true edges (the gate quantity), the distribution viastage1_bearing_residual.summarize()verbatim (§5j-column-comparable), Wilson rates + reason breakdown, a sign-flip null (never a test against zero), per-stratum rows, and the paired aerial calibration: each aerialclick_pxvector projected through the chosen pano's geometry into a predicted residual (radial error predicts ~0°, per §5g).cbb3191— six substantive findings and five minors, the load-bearing one being that the gate quantity censored its own denominator:ramp_outside_viewrecords are certainly outside the strip but classified unjudgeable, so §5o now pre-registers and reports a bound that counts them as failures. See the review comment for the full list.sheet_buildis unchanged at5035ec33, so the committed Denver sheet stays valid.The Denver pilot, as built
Probe: 58/59 sites pick a pano (median range 5.75 m, captures 2016–2026 median ~2021; the failure is a genuine GSV coverage gap,
68791, recorded as its own status). Build: 58/59 rendered in one pass, 19 MB sheet, build5035ec33; the unfilled verdicts template is committed as the sheet's provenance (per-record pano choice, range, bearing, status). Export→summary round trip smoke-tested against the real sheet and the real aerial pairing.What is deliberately NOT here
The review itself (Jon's hour), the resulting verdicts and §-write-up, the Seattle sheet, and the alternate-pano pass over the unjudgeable subset. The aerial sheet is not retired — this instrument yields no metric number (#103's stated price). The sheet HTML stays untracked (embedded Google imagery), same policy as the aerial sheets.
🤖 Generated with Claude Code (claude-fable-5)