docs(plan): per-sale expiry tracking design — batch registry + candidate matching
Grilled 2026-07-16 with the user; full decision record in docs/expiry-tracking-plan.md. Core reframe: expiry is captured once per batch at DO intake (staff-typed on the stock-entry confirmation page, from the physical packs), so the cashier scan only MATCHES OCR fragments against the 1-3 known in-stock batch dates instead of free-reading damaged dot-matrix prints (proven model-capability ceiling, 2026-07-15). Fallback: auto-FEFO + 'inferred' flag, zero cashier interaction. No cloud, ever. - docs/expiry-tracking-plan.md: architecture, matching algorithm spec (resolveExpiryFromEvidence), schema/API deltas, phases 1-3, testing plan - backend plans §13 (13.1-13.4): matcher util + offline tuning, route wiring + expiry_source provenance, multi-frame union, dot-matrix recognizer fine-tune - root plans §10 (10.1-10.3): cashier fast path, inferred badge + end-of-day review, burst capture for mounted camera - stock-feature-plan.md: extension note (batch dropdown becomes the manual-override path) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q8TumxFDnyVnfsR3mxPXfX
This commit is contained in:
1 parent
721dea41dc
commit
e6daa9b053
7 files changed
+350
-333
No files matched your search
@@ -450,6 +450,61 @@ implemented** — no code for this feature exists in the codebase yet.
|
||||
*Suggested order: 12.1 → 12.2 (needs 12.1's tables/movement helper) and 12.3
|
||||
(needs 12.1's summary route) — 12.2/12.3 are independent of each other.*
|
||||
|
||||
## 13. Backend — Per-Sale Expiry Resolution (Candidate Matching)
|
||||
`src/utils/expiry-matcher.ts`, `src/app/api/parse/route.ts`,
|
||||
`src/app/api/v1/scan-product/route.ts`, `src/app/api/v1/documents/`,
|
||||
`config/classify_ocr_server.py`
|
||||
|
||||
Added 2026-07-16 from a user-directed grilling session (ad-hoc feature per
|
||||
`AGENTS.md` Part B7, like §12). **Read
|
||||
[../../docs/expiry-tracking-plan.md](../../docs/expiry-tracking-plan.md) first**
|
||||
— full design, confirmed decisions (full automation at cashier, no cloud ever,
|
||||
auto-FEFO + `inferred` flag as the only fallback), matching algorithm spec, and
|
||||
phase plan. Core idea: expiry is captured once per batch at DO intake
|
||||
(staff-typed on the stock-entry page, §12/root §9), so the cashier scan only has
|
||||
to **match** OCR fragments against 1–3 known candidate dates — never free-read a
|
||||
damaged dot-matrix print under time pressure. **Blocked on 12.1 + 12.2** (needs
|
||||
`stock_batches` + the in-stock candidate filter). Flutter counterpart: root
|
||||
`plans/next-enhancements.md` §10.
|
||||
|
||||
- **13.1** [TODO] **`resolveExpiryFromEvidence()` matcher util + offline tuning
|
||||
harness.** Pure TS util implementing the 4-stage resolution
|
||||
(`single_batch` → `matched_exact` → `matched_fragment` → `inferred_fefo`)
|
||||
with digit-confusion-aware fuzzy scoring of candidate date print-forms (and
|
||||
batch codes) against the scan's OCR `text_lines`; thresholds
|
||||
(`SCORE_MIN`/`MARGIN_MIN`) tuned offline by replaying the 79 frozen-benchmark
|
||||
line-sets against synthetic candidate sets built from ground-truth labels —
|
||||
tune for **zero wrong-candidate picks** (flagged FEFO beats a confident wrong
|
||||
match). Includes verifying the classify server response actually carries
|
||||
`text_lines` to the gateway (add to payload if not — small
|
||||
`classify_ocr_server.py` change). Unit tests + harness script committed.
|
||||
- **13.2** [TODO] **Wire resolution into both scan routes + persist
|
||||
provenance.** `documents.expiry_source VARCHAR(20)` CHECK
|
||||
(`single_batch|matched_exact|matched_fragment|inferred_fefo|manual`) +
|
||||
`expiry_match_score REAL NULL`; `parse/route.ts` Product branch and
|
||||
`v1/scan-product/route.ts` call the matcher after the §12.2 in-stock filter
|
||||
and include `resolvedBatch`/`source`/`score` in `metadata.productScan` and
|
||||
the response (no second GPU call — same pattern as 11.1); PUT persists
|
||||
`expiry_source` (client override ⇒ `'manual'`); documents list gains an
|
||||
`?expiry_source=` filter for the end-of-day review of `inferred_fefo` sales.
|
||||
Blocked on 13.1.
|
||||
- **13.3** [TODO] **Phase 2 — multi-frame evidence union.** Accept burst
|
||||
uploads (N frames per scan) on the product-scan path; classify on the best
|
||||
frame, union OCR `text_lines` across all frames before matching (glare moves
|
||||
between frames — fragments accumulate). Pairs with a mounted camera at the
|
||||
cashier (root §10.3). Blocked on 13.2.
|
||||
- **13.4** [TODO] **Phase 3 — on-prem dot-matrix recognizer.** Synthetic
|
||||
dot-matrix/inkjet date-crop generator (dot dropout, scratch, fade, curvature,
|
||||
glare augmentation) + real-data flywheel (harvest scan crops weakly labeled
|
||||
by the batch registry's staff-typed expiry values); fine-tune a small rec
|
||||
model on the RTX 2060; deploy as an additional reader in
|
||||
`classify_ocr_server.py` feeding the same matcher. Goal: shrink the
|
||||
`inferred_fefo` residue. **No cloud — hard constraint.** Blocked on 13.2;
|
||||
independent of 13.3.
|
||||
|
||||
*Suggested order: 13.1 → 13.2 → (13.3 and/or 13.4 as needed once Phase-1
|
||||
accuracy is measured in the field).*
|
||||
|
||||
---
|
||||
|
||||
*Sections 1-4 migrated 2026-07-08 from root `plans/next-enhancements.md` sections
|
||||
|
||||
Reference in new issue
Block a user