# PLAN.md — Operative plan as of 2026-07-01 **Supersedes** the day-by-day calendar in `SPRINT_BRIEF.md` (the brief's rationale, metrics, and locked parameters still stand — only the schedule and scope details below replace it). `HANDOFF.md` Part 2 remains valid technical reference. ## 0. Goal (confirmed with user, 2026-07-01) - **This is a commercial product.** The product is the **web app** (current FastAPI GUI): contractors upload a blueprint PDF, get units/capacities/CSVs, review flagged boxes in the browser. - **North star: zero-touch.** Human review of flags is acceptable only during the current maturing phase; the long-term bar is fully automatic. This makes the recall-first rule *stronger*, not weaker — every silent deletion is a product failure, and "boxes needing review per floor" is now a first-class leaderboard metric (it measures distance from zero-touch). - **Full A/B/C bake-off proceeds** (not descoped), on a **~1 month** timeline. - **Budget is not the constraint.** Track spend per run (ledger), but spend what the diagnosis/training needs. The $150 ceiling is retired. - **Division of labor:** the user does gold-CSV hand-review and Kaggle GPU training runs; Claude does everything else (code, harness, data prep, cascade, scoring, writeups, GUI/productization). ## 1. Root cause of the Carroll p13 blocker (diagnosed 2026-07-01, free/local) The failure chain, confirmed by reading the run artifacts and code: 1. **Schedule extraction for p13 is corrupted** — `outputs/_p13_ultra/...schedule_cache.json` contains OCR garbage: `AC-28H` qty 50, `AC-U` qty 24, `AC-CESS`, `AC-O`, `AC-C`. Nothing matching the real `AC-9-x` tags on the floor. 2. The corrupted schedule **poisons the tag vocabulary** used by detection, so every candidate box carries a wrong tag string. 3. **Snap fails 0/45** — `_local_ocr_snap` searches for tag text that doesn't exist on the page (wrong string). 4. **Verify rejects 45/45** — `_refine_hit_box` (analyze_blueprint_b.py:2314) demands the **exact text `'{hit.tag}'`** be visible in the crop and returns `None` (silent deletion) otherwise. A tag misread becomes a deleted unit. 5. The review CSV ends up **empty** (header only) — the 45 rejections leave no reviewable trace (`HVAC_VERIFY_DEBUG` exists but was off). Tile size was never the lever (the runbook's own decision table already said so). The bug is **architectural**: the verify stage conflates two questions — "is there a unit here?" and "does the tag match?" — and lets the weaker signal (tag text, downstream of a corruptible schedule) veto the stronger one. ## 2. The fixes (Week 1 core work) - **F1 — Never silently delete.** Verify-rejected candidates become fuzzy/yellow rows in the review CSV instead of vanishing. (~5-line change around analyze_blueprint_b.py:2152.) This alone un-blinds every future run. - **F2 — Two-question verify.** Split the verify prompt: (a) *class-level* — "is there an HVAC indoor unit in this crop?" (controls keep/flag); (b) *tag-level* — "what tag text is printed near it?" (controls the tag field; mismatch with the detected tag → orange, never rejection). This also pre-builds the multi-class future (outdoor/CU as a distinct class answer). - **F3 — Schedule sanity gate.** Score every extracted schedule before use: qty outliers (>20 for a single tag), malformed tags (`AC-U`, `AC-O`, `AC-CESS`), fraction of empty model numbers. A suspect schedule must NOT constrain the detection/verify tag vocabulary; instead flag it in the GUI (the manual schedule-region picker already exists as the human fallback). - **F4 — Verify model configurable.** `gemini-2.5-flash` is hardcoded in `_refine_hit_box`; make it a recipe knob and test `gemini-3.5-flash` (brief step 7 — expected cheaper AND better). - **F5 — Debug trail on by default** for all bench runs (`HVAC_VERIFY_DEBUG=1` in `BEST_ENV` / bench harness), plus a **cost ledger**: append per-run model/tokens/$ (from `_record_gemini_usage`) to `analyzer_b/results/cost_ledger.csv`. ## 3. Other plan defects found in review (fixed by this plan) - **Two unreconciled benchmark harnesses.** The 80-version leaderboard (`bench.py`, 355+Dean, 4 pages) never validated `v26_consensus` against the gold set (Lewis/Carroll). Winner risk: a config tuned to easy pages. → Standardize on `bench_detectors.py` + gold set as canonical; keep `bench.py` pages as a secondary regression set. - **Gold set too small for the decision rule.** With 2 buildings, "within 2 points" differences are noise. → Promote **2 more reviewed buildings to gold** (user reviews them during Week 2), report per-building numbers, and never trust sub-2-point gaps. - **Doc sprawl.** `.gsd/` is dead, publication docs are parallel-track. This file is now the single operative plan; anything conflicting is stale. - **Repo hygiene / secret risk.** The stray `HVAC-Blueprint-Analyzer\` junk directory (broken-copy artifacts with U+F05C in filenames) contains an untracked, **un-gitignored `.env`** — a `git add -A` would commit an API key. Nothing has been committed yet (verified against full git history). → Verify the stray `.env` matches the root `.env` (no unique secrets), delete the junk directory, apply/merge `.gitignore_new`, commit the real backlog deliberately (not with `-A`). - **OneDrive IO.** Training/eval must run from `C:\hvac_work` (non-OneDrive), foreground — unchanged rule, now part of Week 2 setup rather than a footnote. ## 4. Week-by-week ### Week 1 (now → ~Jul 8) — Fix Track A, make the benchmark trustworthy **Claude:** 1. Implement F1–F5. 2. Re-run p13 (~$1–2): expect the 6 `AC-9-x` units to appear (green/orange/yellow — any color is a win vs. silent 0/6). 3. Wire gold Lewis + Carroll into the canonical harness; re-score `v26_consensus` and the fixed pipeline on gold → new Track A baseline row (center-recall, review-rate, $/floor via ledger). 4. Run `error_taxonomy.py` across ALL reviewed buildings; encode recurring failure modes into `guardrails.py` (multi-class-friendly rules, not tag-string post-filters). 5. Repo hygiene items from §3. **User:** re-review gold pages that gain new flags after the fix (~30–60 min); confirm p13 gold boxes are still right. **Exit criteria:** p13 recall > 0; Track A has an honest gold-set number. ### Week 2 (~Jul 8–15) — Track B data + training **Claude:** harvest YOLO labels from the ~20 reviewed buildings into `dataset` with a **multi-class-ready schema** (class 0 = indoor unit; ids reserved for outdoor/CU, diffuser, fan); finish symbol bank; run flip-n-slide; build the synthetic data factory (~35% real / 65% synthetic, tune); assemble `kaggle_upload.zip` + turnkey `kaggle_run.py` notebook (DocLayNet-pretrained base + yolov8n-doclaynet control). **User:** launch 2–3 Kaggle T4 training runs (everything pre-packaged; kick off, download weights); hand-review 2 more buildings → gold set grows to 4. **Exit criteria:** proposal-recall@conf0.05 on gold measured. **Kill-switch (unchanged):** < ~0.85 → Track B shelved, Track C falls back to "Gemini proposes, Flash patch-verifies". ### Week 3 (~Jul 15–22) — Track C cascade **Claude:** proposer (YOLO or Gemini per kill-switch) with SAHI tiling + Weighted Boxes Fusion; verifier = the F2 two-question patch check on Flash; union-check vs. full-page Gemini on sample floors (the real recall question); measure $/floor and sec/floor. **User:** spot-review cascade output on 1–2 non-gold buildings. **Exit criteria:** Track C metric row complete. ### Week 4 (~Jul 22–29) — Bake-off + productize the winner **Claude:** full A/B/C metric table on the (now 4-building) gold set: center-recall, review-rate (the zero-touch metric), proposal-recall@0.05, $/floor, sec/floor, tag accuracy. Apply the decision rule (max recall; cost breaks ties only within 2 points AND consistent across buildings). Writeup. Then productize: wire winner into `modules/pipeline.py` DEFAULT_RECIPE, add per-run cost/latency telemetry to the GUI, HF Spaces deploy prep, and a follow-the-sun path from "review flags" → "zero-touch confidence thresholds". **User:** sign off on the winner; HF Spaces account/secrets for deploy; pricing thinking can start from the measured $/floor. ## 4b. Progress log (2026-07-01, evening) - **F1–F5 implemented and validated.** p13 re-run #1 proved the kill chain: sanity gate fired, tags read correctly off the page, rejects flagged yellow. - **New failure family discovered: `leader_callout_separation`.** Carroll p13's units are UNLABELED hatched bodies; tags live in circled callouts ~1000px away. Fix F6 (detect unlabeled duct-connected bodies as tag `UNKNOWN`, no snap-drag, verify reads the tag): p13 center-recall **0% → 67%** (4/6 located, 2 FN left, FP/review burden high — 39 flagged). Runs cost $1.67 + $1.67. - **Gold set now 4 buildings** (user reviewed 1326 + 23042 on 2026-07-01; CSVs locked in `analyzer_b/ground_truth/_gold_1326/` and `_gold_23042/`). - **Gold box convention (confirmed with user):** box covers unit body + tag when adjacent; unit body ONLY on leader-callout plans. Tag string lives in the tag column, location in the box. - **New engine requirement F7 — multi-floor sheets.** One sheet can serve several floors ("3RD-4TH FLOOR PLAN", "THIRD THRU SIXTH", Carroll's "10TH & 11TH"/ "12TH-16TH"). Two bugs: (a) the page classifier sometimes marks these `other` and SKIPS them entirely (Carroll pages 14/15/17/18 — whole floors silently missing from output); (b) building unit counts/capacity must multiply by floors-covered. Gold convention: boxes count once per sheet; `floor_multipliers.csv` sidecar in each gold dir carries the multiplier. ## 4c. Track A baseline on the 4-building gold set (2026-07-02) — THE headline finding Production config (`v26_consensus` equivalent: tile 1800, runs 2, consensus 2, strict prompt, no fp-filter) on 17 never-tuned-on gold pages, $6.13 total: | Building | Center-recall | Gold units | Main failure story | |---|---|---|---| | 11 Lewis | **13.3%** | 15 | over-eager schedule gate emptied a mostly-good vocabulary → strict prompt blind | | 417 Carroll | **51.8%** | 83 | p13 = 0 (leader-callout units need inclusive prompt); offsets on dense floors | | 1326 (2016) | **66.2%** | 71 | p1 Cellar 0% with 14 preds/0 matches (uninvestigated — possibly another callout/offset family) | | 23042 (2024) | **3.9%** | 76 | real units are `AH-2.x.y`; schedule lacks those rows entirely → strict prompt blind | | **TOTAL** | **≈39%** | 245 | | **The leaderboard's F1 0.967 was overfit to its 2 tuning buildings.** Root cause across all four: the strict prompt treats the (unreliable-in-a-different-way-every- time) schedule as a hard vocabulary. **F8** (implemented 2026-07-02): quarantine suspect schedule ENTRIES instead of nuking whole schedules; if >30% of entries are junk or none survive, force the inclusive prompt automatically. **A/B RESULT (2026-07-02): inclusive prompt is the new production default (`v30_inclusive`).** | Building | Strict recall | Inclusive recall | FP strict→incl | |---|---|---|---| | 11 Lewis | 13.3% | **60.0%** | 5 → 46 | | 417 Carroll | 51.8% | **90.4%** | 4 → 151 | | 1326 | 66.2% | **80.3%** | 17 → 102 | | 23042 | 3.9% | **69.7%** | 3 → 146 | | **TOTAL** | **39%** | **79.2%** | 29 → 445 | Cost: $6.13 strict vs $14.66 inclusive for 17 pages (~$0.86/page). Wired into `modules/pipeline.py` DEFAULT_RECIPE (`recall_prompt: True`) and registered as `v30_inclusive` in versions.py. Open fronts, in attack order: (1) review burden ~26 yellow boxes/page → dedupe/merge + FP-family guardrails mined from the Queens deletes (216 cropped FPs available); (2) 51 remaining FNs on gold → error taxonomy per miss; (3) 81 mistags → tag-read hardening (Qwen second opinion); (4) $/floor ~2.4× — Track C patch-verify is the cost lever. Verify-stage F1–F6 remain in effect throughout. ## 4d. Review-loop notes (2026-07-02) - **72-01 Queens Blvd human review done** (partial floors — only floors with a saved/applied CSV count as verified). Feedback from the reviewer: yellow flags on fl 28 were 7 real units / 15 FPs — recall net is working (7 units saved from being missed) but FP burden must come down. Lever order: (1) dedupe/merge near-identical candidates before review, (2) harvest her delete decisions into FP-family guardrails via error taxonomy, (3) Track C patch-verifier is the real precision machine. - **Bug: floor-name misread** — a Queens sheet titled "PARTIAL 5TH FLOOR" was labeled "35th Floor". Same family as F7 (page classify/title parsing). - **Direction confirmed by user:** YOLO detector classes = unit TYPE from the schedule (wall-mounted / ceiling-ducted / cassette / vertical …), NOT tag strings; tag comes from OCR/verify. Symbol bank harvesting should key crops by unit type. ## 4e. Precision work + taxonomy (2026-07-03) - **Cross-location dedupe: null result** (implemented, kept as safety net). Offline validation on saved _incl_* detections removed 0 boxes — duplicates are OFFSET near-dupes (100–300px, different sizes), not stacked; geometric merging would risk real back-to-back twins. - **CU/CUH guardrail measured on Carroll:** FP 151→131, recall unchanged. Wire into engine path (currently only in ab_guardrails.py). - **Queens harvest done:** 216 FP crops + 311 bank candidates under `analyzer_b/harvest/`, metadata CSV included. FP families identified (grille callouts, room-type text, keynote circles, shafts, 'A/C' text, fixtures) → added as verify-prompt negatives. Taxonomy in `analyzer_b/results/error_taxonomy_notes.md`. - **Biggest FN family fixed in prompt:** 23042's small SOLID-BAR wall units with stacked dual-floor labels (15 of 51 gold FNs) — new clause in the inclusive prompt. ⚠ CONTAMINATION NOTE: this clause was derived from GOLD failures, so gold is no longer pristine for Track A prompt-tuning. Fair bake-off requires B/C to see the same taxonomy; ideally validate future prompt tweaks on non-gold buildings first. - **v31 measurement run scored (2026-07-05): 83.3% recall — NEW SHIP CONFIG.** | Building | v30 | v31 | note | |---|---|---|---| | Lewis | 60.0% | 60.0% | flat | | Carroll | 90.4% | 90.4% | flat (p13 33% on production recipe vs 67% ultra) | | 1326 | 80.3% | **70.4%** | p1 Cellar collapsed to 0/11 — now 27% of ALL gold FNs, top diagnosis target | | 23042 | 69.7% | **92.1%** | solid-bar clause worked (p8+p3 = 100%) | | **TOTAL** | 79.2% | **83.3%** | FN 51→41; FP 445→499; mistag 81→74; F1 ~0.43 flat | Cost: ~$8.65 (remainder) + ~$5 (pre-crash portion). Status split across v31 CSVs: 599 yellow / 209 gray-noise (bulk-deletable) / 145 green / 10 orange — noise share understated (Lewis + Carroll p4/6/8 predate the noise band). Verdict per decision rule: v31 recall +4.1 pts → stays (prompts already live). Precision/F1 recovery still owed: F1 target ≥0.52 unmet — next levers are the 1326-p1 diagnosis, outdoor guardrail (in flight), mistag hardening, and Track C. - **Review-burden product fix identified:** flag-don't-drop means verify can't shrink the yellow pile, only label it. GUI must band yellows: conf≥0.5 "needs a look" vs conf 0.3 verify-rejected "likely noise" (collapsed, bulk-delete). Task #9. ## 4f. First-contact evaluation protocol (user-proposed 2026-07-03 — adopt) Every NEW building is an unseen test set exactly once, before any fix or training derives from it. This replaces "tune against gold" as the honest generalization measure (gold keeps the regression-test job). 1. Run 1–2 pages of the new building (one typical + one weird/dense) with the CURRENT ship config — ~$1–2, `--pages` / GUI floor select. 2. Human verifies every box (keep/delete/add) — the score isn't real until a human confirms the unit count. 3. Score via `review_mining.py` (TP/FP-deleted/FN-added → recall+precision) and append to `analyzer_b/results/first_contact_ledger.csv`: building, date, config/version, pages, recall, precision, notes. THE LEDGER'S TREND LINE IS THE HEADLINE PRODUCT METRIC — expected accuracy on a brand-new upload, config version by config version. 4. Mine errors → guardrails/prompts/training data → building joins the training pool. Never re-count a building's first-contact score after fixes derived from it (that's a tuned score, not a first-contact score). 5. Caveat: 2-page samples are noisy (±1 unit on an 8-unit floor = 12 pts) — read the trend across buildings, not single-building deltas. ## 4g. Title-block reader v2 (2026-07-06/07 — task #11, user's 3-step plan) 1. **High-DPI clip OCR** of the title-block regions (right strip, bottom-right corner, bottom strip at 350 DPI via fitz clip — cheap, renders only strips). 2. **Sheet-number parsing** (`M-303.00` pattern) — new `sheet_no` manifest column; the M-30x sequence flags the floor-plan series even when titles fail. 3. **Gemini-Flash fallback** (`HVAC_CLASSIFY_LLM=0` to disable) — fires ONLY when no INFORMATIVE standard title emerges (bare "MECHANICAL"/"MECHANICAL PLAN FILED" now counts as uninformative — that gate bug initially masked the fallback entirely). ~$0.01/failing page. Validated on 554 W 46th (the 0-plan-pages building): **0 → 12 plan pages**, every floor correct Cellar→Bulkhead, p6 auto-read as "3RD TO 7TH" ×5 (matches the user's hand-verified truth), p2 stamp-title regression fixed. Canonical 554 manifest upgraded. Floor-name fallback regex also fixed (no longer glues "FLOOR" to numbers across newlines — 527 Grand's "Floor 44" ghost). ## 4h. First-contact run: E082 (2026-07-07) v31 ship config on "20260415-E08201001-Mechanical_100% Construction Documents.pdf" pages 13 (Cellar ductwork — dense pick) + 18 (10th-floor ductwork — typical pick). Out dir = the GUI stem dir (classify artifacts copied from `_fc_e082`; June schedule cache reused). Pre-review CSVs snapshotted to `_prereview/`. **Cost: $1.74** (737 Gemini calls). Boxes awaiting user review: p13 = 39 (2 confirmed / 3 recovered / 31 review / 3 noise), p18 = 89 (7 / 1 / 73 / 8). Ledger row 4 pends her verification. Same day: Kaggle Track B weights returned (ctrl 0.943 / doclaynet 0.981 same-corpus holdout recall); gold tile set built (3,136 tiles, 245/245 GT sanity-checked). **Gold bench result: KILL-SWITCH FIRED** — proposal-recall @0.05 = 0.20 (ctrl) / 0.14 (doclaynet) vs the 0.85 bar; R@0.25 = 0.14/0.05. Same-corpus 0.94–0.98 → gold 0.14–0.20 = severe overfit to training buildings (data volume/diversity is the blocker, as the recalibration memo predicted). Track B shelved per pre-agreed rule (user sign-off pending); Track C proceeds as "Gemini proposes, Flash patch-verifies". Full leaderboard in C:\hvac_work\gold_tiles_v1\bench_log.txt. ## 4i. Plan revision (2026-07-07) — post-Track-B, research-informed Full research reports in session logs; sources verified. Market context: NO commercial takeoff tool has solved MEP/HVAC symbol counting (97-98% claims are architectural elements; vendors explicitly punt MEP to human QA) — our recall-first + tiers approach matches or leads the market. **Metric change: report ZERO-TOUCH F1** (greens+oranges auto-accepted, grays auto-dropped, yellows per current policy) per first-contact + as a bake-off column. That's the product number; raw F1 stays for regression comparison. **Tier 1 (free/cheap, do first):** 1. **Offline verifier bench** from ~350 labeled crops (216 Queens + 90 E082 deletes + 38 E082 units + gold sheets): score the F2 verify prompt per FP family offline; iterate at pennies instead of $14 gold sweeps. On the same bench: **A/B Flash-Lite vs Flash** for verify (research: small-crop classification is Flash-Lite territory, 5-20× cheaper) and a **tag-grammar regex demotion** (read tag matches no plausible `FAM-…-n` grammar → gray) — kills AC-CESS/KE-2 word-fragment families. 2. **Detection-prompt audit vs Gemini best practices**: coordinate order [y0,x0,y1,x1]; UNIQUE per-instance labels for repeated units (published finding: Gemini undercounts dense identical objects — plausible factor in 1326-p1's 0/11 and dense-floor offsets); thinking budget off for detection. 3. **GreedyNMM/NMM box fusion** for fp_offset_near_dupes (plain IoU dedupe measured zero effect; NMM merges transitive chains) — test offline on saved detections first. 4. **OCR snap audit**: rotated-text orientation handling (known PaddleOCR rotated-text bug family; verify what our snap uses and its rotation path). 5. Verify prompt tag-grounding: ✓ already implemented (audited 2026-07-07); template parser will make the tag context floor-correct. **Tier 2 (spikes, timeboxed):** 6. **Exemplar-similarity scorer** from the symbol bank (DINOv2/Siamese embedding vs approved crops). Strongest research result: JBE 2025 MEP paper, few-shot graph+Siamese matching, **F1 0.95 on UNSEEN equipment types** — similarity generalizes where classifiers overfit (exactly Track B's failure). Use as tier-ranking feature + demotion protection (CRAC-C-1 case). 7. **Zero-shot visual-exemplar detectors** (T-Rex2/DINO-X/YOLO-World) on the already-built gold_tiles_v1 — UNVALIDATED on line art per literature; strict timebox; success = proposal-recall ≥0.85 → Track C gets a cheap local proposer. 8. **Vector-PDF subpath clustering** (pymupdf cluster_drawings) as auxiliary recall proposer on the 2 vector buildings — free spike, temper expectations (academic SOTA needed trained GNNs). 9. **Calibrated tier thresholds** (conformal-style) once verify+similarity scores exist; recalibrate as buildings accumulate; guarantees are approximate under building-shift — advisory, not certified. 10. Caution (research): if fusing Qwen+Gemini as voters, CALIBRATE each model's confidence first (MoCaE finding: naive fusion degrades). **Unchanged/reaffirmed:** structure-prior stack (template rows → pattern lever → gap flags → cross-floor consistency) is the highest-leverage Track A work; no Track B retraining this sprint; no more gold prompt-tuning; consensus-run marginal-value audit; 1326-p1 + Lewis FN diagnosis before bake-off (dense-cluster undercount is now the leading 1326-p1 hypothesis). ## 4j. Fable-as-proposer probe (2026-07-08) — promising, reshapes Track C User observed Claude Fable 5, one-pass in chat (no tiling/schedule), marking up whole floors with apparent 100% recall + correct per-apartment tags + category labels (indoor AC vs CRAC vs fans), ~$5/floor in chat. Tested on the TWO gold floors that most broke the tiled-Gemini pipeline, blind, scored vs gold: | Floor | Gold | Gemini pipeline | Fable one-pass | |---|---|---|---| | 1326 p1 (cellar) | 11 | 0/11 | **11/11**, tags 11/11, 0 FP | | Carroll p13 (leader-callout) | 6 | 4/6 (only w/ F6 code; 0 without) | **6/6**, tags 6/6, 0 FP | PROVEN: recall + tag identification are excellent on the hardest cases; also correctly excluded TEF/BS/ERV/CV callouts on p13 (the FP family Gemini boxes). NOT YET MEASURED: coordinate precision — the strict center-match failed only because centers were ESTIMATED BY EYE off a downsampled Read image then scaled x5-7 (my measurement error, not Fable's; loose tolerance = 11/11 & 6/6). Cost: API single-pass est $0.15-0.40/floor (one call replaces the pipeline's hundreds of tiled/verify/consensus calls) — comparable-to-cheaper than pipeline ~$0.65, and the $5 chat figure is conversation-overhead-inflated. ARCHITECTURE IMPLICATION: Fable becomes a serious PROPOSER for Track C — "Fable proposes (near-perfect recall + correct tags) -> existing OCR-snap grounds each unit to its printed tag for a pixel-tight box" — potentially deleting tiling/consensus/verify entirely. Recall-first rule favors a proposer that misses nothing. OPEN RISKS before committing: (1) coordinate precision of Fable's OWN emitted coords (needs a scripted API call, not eyeball); (2) dense floors — Claude caps images ~1568px, so a 39-unit floor (Carroll p8) in one downsampled pass may lose tiny symbols -> may need a few zoom sub-calls (still << hundreds of tiles). NEXT STEP: real API test needs ANTHROPIC_API_KEY in .env (currently MISSING; OpenRouter is set but may lack claude-fable-5 + has a small daily cap). Build fable_propose.py -> send floor image, get structured coords, run hard + dense + a clean floor, score precision, pair with snap, log true cost. Then it enters the A/C bake-off as a proposer variant. ## 5. Standing rules - **Recall-first, now enforced in code:** no code path may silently delete a candidate — reject means flag. - **Review-rate is on the leaderboard** — it is the zero-touch progress metric. - Every paid run appends to the cost ledger. - Training/eval from `C:\hvac_work`, foreground. - Gold buildings (now growing to 4) are never trained or tuned on. - Dead end stays dead: no graph/topological approaches (raster PDFs only).