From 6182f678eb9cc96bbc8ed323b23f9c576c73c948 Mon Sep 17 00:00:00 2001 From: kiyo9w Date: Thu, 24 Sep 2026 14:11:30 +0900 Subject: [PATCH] docs: price-tag fields, compliance semantics, and the measured limits Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- recognization/docs/API.md | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/recognization/docs/API.md b/recognization/docs/API.md index fb27f7c..d5ac7df 100644 --- a/recognization/docs/API.md +++ b/recognization/docs/API.md @@ -30,6 +30,8 @@ uv pip install --python .venv/bin/python -r requirements-dev.txt # first time | `--classify-workers` | `5` | Classification thread pool size (measured default). | | `--live-method` | `hybrid` | Default method for live sessions. | | `--live-conf` | `0.1` | Detection threshold in live mode (the tracker gates). | +| `--no-tags` | off | Disable price-tag OCR entirely (`DORAYAKI_LIVE_TAGS=0`). | +| `DORAYAKI_OCR` | `auto` | OCR backend: `auto` (Vision on macOS), `vision`, `fake`, `none`. | | `DORAYAKI_TORCH_THREADS` | `4` | torch CPU threads; 0 leaves torch's default. | | `--max-sessions` | `4` | Concurrent live sessions. | | `--ssl-certfile` / `--ssl-keyfile` | – | HTTPS (needed for a phone camera). | @@ -153,6 +155,45 @@ with a `file` field. before identities arrive. The `image_id` stays valid for 15 minutes for `classify`/`inspect`. +### Price tags (OCR) + +When an OCR backend is available (macOS Vision; see the design doc), a still can +carry tag evidence. `?tags=true` runs the tag pass synchronously (measured ~0.9 s +for a six-band shelf) and adds, per detection: + +```json +"price_tag": {"id": "tag_3f2a", "box": [91, 657, 250, 744], "state": "readable", + "price": 357, "regular_price": null, "discount": 0.24, "unit_price": 290.52, + "name": "ゼロノンコレステロール", "flags": ["sale", "discounted"], + "confidence": 0.82, "observations": 2, "settled": false}, +"reconciliation": {"state": "confirmed | mismatch | unverified | no_tag | tag_only", + "signals": [{"kind": "price", "agrees": false, "item": 168, "tag": 269}], + "message": "tag says 269, catalog says 168"} +``` + +`price_tag.state` is `readable` (text parsed), `unreadable` (a tag was found but +its text was not read) or `absent` (no tag under this facing). The summary gains +`price_tags` counts and `compliance`: + +```json +"price_tags": {"readable": 8, "unreadable": 18, "absent": 85}, +"compliance": {"violations": 7, "by_rule": {"price_mismatch": 7}, + "rows": [{"row_index": 2, "rule": "price_mismatch", "box": [...], "missing": 1}], + "hints": [{"rule": "unreadable_tags", "detail": "tags detected for 5 of 16 facings in this row"}]} +``` + +**Compliance fails closed on evidence quality.** A row is only judged when at +least 70% of its facings carry tag evidence (readable or unreadable); otherwise it +produces a hint such as `move closer: the price tags in this row could not be +read`, never a violation. Findings must also persist for 3 of the last 5 frames +before they publish. This is deliberate: the first implementation reported 106 +missing-tag violations on a shelf where only 8 tags were readable, which was our +detection being incomplete, not the shelf. + +`POST /v1/images/{image_id}/classify` re-reconciles against the cached tag bundle +and returns `reconciliation` (and `price_tag`) on each identity row, because in +photo mode the tags are read before the identities exist. + ### `POST /v1/images/{image_id}/classify` ```json @@ -283,6 +324,21 @@ The slide's "約28~30ms per image" matches the measured **detection** time (28.7 the full detect-and-classify of a whole 170-product shelf is ~2 s because each product is matched against the catalog separately. +## Price-tag OCR: what it can and cannot do (measured) + +- **Capture distance decides everything.** At `add/image6.jpg`'s ~1 m (tags ~300x80 px, + digits ~22 px) macOS Vision reads a tag's printed fields reliably and the tag pass + finds 8 readable tags with names and prices in ~0.9 s. At `image.jpg`'s ~2 m (digits + ~14 px) it reads none of them - the pass correctly reports nothing readable. +- **Tag detection is the weak link, not the OCR.** On a shelf with ~100 facings the + pass currently finds tags for a minority of them at this distance; the compliance + hints say so instead of inventing violations. +- **A single read can misread a digit.** The vote needs independent reads (live mode + accumulates them across frames; stills read each band twice at different upscales), + and a dead heat stays unread rather than guessing. +- **The demo catalog is fictional**, so tag prices legitimately disagree with it. That + is shown as `mismatch` with both values; it is not an error. + ## Known limitations - **No session totals.** Facings are counted only for products currently in view. A