Make the catalog hash honest, share the text helpers, drop the duplicate labels

`catalog_hash` hashed the CSV and the view *names*, so replacing
`sku_l09.jpg` with a different product's packshot left the hash unchanged and
a report could claim the same catalog while every l09 decision in it had
moved. It hashes each view's bytes now.

`identity_lanes` carried its own `normalize_text` and `bigrams`, near-copies
of the ones in `rebuild_catalog` that had already drifted (the lane's table
folded ユ to itself, the catalog's did not). The lane imports the catalog's
now, so one read scores the same either way.

`evidence/image6_v2.json` was byte-identical to `eval/labels/image6.json`; the
evidence scripts and the decision document point at the canonical file and the
copy is gone. `score_crop`'s docstring now states the contract its callers
already depend on: the dict's insertion order is the lane's ranking, and
re-sorting by value names a different SKU than the lane chose.

Tests: the lane is shared by the classify pool, so a concurrent-ranking test
pins that one BFMatcher does not cross crops under eight threads.
This commit is contained in:
tngo 2026-09-29 00:48:03 +09:00
parent fc49ba60e6
commit b6c87c45ea
36 changed files with 89 additions and 1524 deletions

View file

@ -112,7 +112,7 @@ verification → calibrated open-set decision → temporal accumulation.
### The four ceilings, separated
On corrected labels (`evidence/image6_v2.json`): 109 scored facings, 61
On corrected labels (`eval/labels/image6.json`): 109 scored facings, 61
in-catalog across 11 SKUs, 48 unknown, 2 excluded. The clean catalog enrolls 7
SKUs covering 42 of the 61 in-catalog facings.
@ -288,7 +288,7 @@ rank, decision, lane latencies), and report the four ceilings separately.
### Benchmark tiers (build these first; they block every other decision)
- **Tier 1, diagnostic:** `evidence/image6_v2.json` (109 facings). Dev only.
- **Tier 1, diagnostic:** `eval/labels/image6.json` (109 facings). Dev only.
Free to tune on. Its numbers never ship.
- **Tier 2, held-out regression:** new captures that are never enrolled and
never tuned on. At least 3 visits across ≥ 2 LOPIA locations or days, same
@ -335,7 +335,7 @@ reverted.
| `test_live.py` change | encodes the reverted budget | Revert with it. |
| `code/rebuild_catalog.py` (untracked) | catalog tooling | Keep. Add image↔CSV bijection, packshot-OCR vs name check, and eval-session refusal (enrollment protocol below). |
| `stock-lopia-clean/` (untracked) | catalog data | Keep the images and provenance. Fix `stock.csv` (F-04) and the README's "Six SKUs" (it lists five). Replace the l06 packshot with the current package. |
| `eval/labels/image6.json` (untracked) | ground truth | **Replace** with `evidence/image6_v2.json`, schema: `label`, `source`, `note`, `v1_label`. |
| `eval/labels/image6.json` | ground truth | Adopted; schema: `label`, `source`, `note`, `v1_label`. |
| `identity.decide_identity` inlier branch | production | **Rewrite** as the calibrated decision (E-4); keep the cosine branch only while the gallery exists. |
| Production score path (`engine.score_crop`, `classify_boxes`, `inspect`) | production | **Rewrite:** return per-SKU evidence grouped by canonical SKU (F-02). |
| `feature_matcher.py` composite score | production | **Rewrite** into variant B behind an interface; keep Van's scorer as a selectable baseline lane for E-1. |
@ -431,7 +431,7 @@ covers a recognition backend. The product-level floor is Van's system (G1–G4).
- **Evidence:** `evidence/image6-v1-labels-overlay.jpg`; the per-index list in §3.
Rescoring the same runs under v2 moves ungated live top-1 from 26/35 to 35/42.
- **Target:** every scored label carries provenance; ambiguous facings are excluded.
- **Change:** adopt `evidence/image6_v2.json`; schema-check `label/source/note`.
- **Change:** adopt `eval/labels/image6.json`; schema-check `label/source/note`.
- **Validation:** E-0 double-labelling on Tier 2.
- **Acceptance:** no architecture claim cites v1.
@ -602,7 +602,7 @@ and overstates its precision, so the evaluator now has a `van` lane: the same
scores, always answered. Every "vs Van" claim in this record uses that row.
.venv/bin/python code/eval_identity.py --stock stock-lopia-clean \
--labels docs/research/2026-09-28-recognition-architecture/evidence/image6_v2.json \
--labels eval/labels/image6.json \
--image add/image6.jpg --lanes van,matcher,local --live
full resolution — 109 scored facings (61 in-catalog, 48 unknown)
@ -673,7 +673,7 @@ Two further review findings, both fixed and both measured:
The headline number is reproducible from the repo, not from a scratch script:
.venv/bin/python code/eval_identity.py --stock stock-lopia-clean \
--labels docs/research/2026-09-28-recognition-architecture/evidence/image6_v2.json \
--labels eval/labels/image6.json \
--image add/image6.jpg --lanes local --live
live resolution: catalog 69% · identity 86% · decision 86% ·