# Shelf label files Ground truth for the identity evaluators (`code/eval_identity.py`, `code/eval_live.py`) and for the evidence scripts in `docs/research/2026-09-28-recognition-architecture/evidence/`. {"image": "add/image6.jpg", "version": 2, "facings": [ ... ]} `image` is the photo the boxes index into. `facings` is one row per detected product facing, in that photo's own pixel coordinates. ## Row schema | Field | Required | Meaning | |---|---|---| | `index` | yes | Row identity. Stable across label revisions, so a run's per-facing log joins to truth by `index`, never by array position. | | `box` | yes | `[x1, y1, x2, y2]` in `image` pixels. | | `label` | yes | Canonical SKU id (`sku_l01`), `unknown` for a facing that is not in the catalog, or `exclude` for a facing no lane can be scored on. | | `source` | yes | How the label was decided. `v1-confirmed-visual` = the v1 label was checked against the package and kept; `visual-audit-2026-09-28` = the label was re-read from the package in the 2026-09-28 audit. | | `v1_label` | yes | The label this row carried in v1. Kept so claims computed on v1 stay reproducible and the 26 corrections stay auditable. | | `note` | when `label != v1_label` | Why the label changed. Also on a few unchanged rows that need a remark. | | `match` | no | v1 provenance only: the field DECISION.md §3 reads as a match IoU, and as evidence that v1 labels were propagated from tag or box matching rather than read off packages. Values here are 0.0, 0.911, 1.0. No scorer reads it; it is carried through unchanged for traceability. | `label` takes a canonical SKU id, `unknown`, or `exclude`. This file uses `sku_l01`-`sku_l12`. `exclude` rows stay in the file so the facing inventory is complete; every scorer drops them before computing a rate. The evaluators read `index`, `box`, and `label`; the provenance fields are additive. `image6.json` is the corrected v2 set: 111 rows, 109 scored (2 `exclude`), 64 in-catalog across 12 SKUs, 45 `unknown`. 26 labels changed from v1, and three more changed on 2026-09-29: facings #46-48 were `unknown` with the note "possibly what stock.csv calls l12", and the l12 reconciliation settled it, so they now carry `sku_l12`. This file is the single copy: the evidence directory used to hold a duplicate (`image6_v2.json`) and it was removed on 2026-09-29 after `cmp` showed the two byte-identical, because a copy that nothing reads is a copy that drifts. ## Schema check DECISION.md §7 sets the rule for every label file: > Every label row carries `source`, `confidence`, and `labeler`. A label file > without them fails a schema check. The current file does not meet it. All 111 rows carry `source` and `v1_label`, but none carries `confidence` or `labeler`: the file records where each label came from and what it replaced, not a per-row certainty or a labeler name. Inventing confidence values after the fact would be worse than the gap, so they are absent rather than guessed. Tier-2 labels must add both. `confidence` is the labeler's own certainty for that row; `labeler` names the person or pass that produced it, because Tier 2 is double-labelled and a disagreement has to be traceable to a pass. Tier 1 (`image6.json`) is dev-only and its numbers never ship, so it stays as it is.