feat: carry the JAN and the price's provenance through the tag pipeline

The card reader produces a JAN (the last three digits printed on the tag) and
says whether a price was measured from the big digits or derived from the
tax-included one; the session record and the wire payload dropped both, so the
strongest identity anchor the tag carries never left the reader.

Also reads each card at three scales rather than two. The tax-included price is
small print and only resolves at 3x; without it the price falls back to the
stylised digits, which misread (measured: 30 instead of 269, 20 instead of 199).

Measured through the real still path on add/image6.jpg, the tags now attached to
products read: 269/357, 379/400, 199/248, 209/500, 219/200, 349/150 - prices and
JANs, where the same run previously reported tags that were not in the readable
row at all and prices like 2630 from another tag's text.

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
kiyo9w 2026-09-24 22:11:51 +09:00
parent 23fd087611
commit 8e33b3ebdd

View file

@ -142,6 +142,8 @@ class TagRecord:
"discount": self.discount,
"unit_price": self.unit_price,
"name": self.name,
"jan": self.jan,
"price_source": self.price_source,
"flags": list(self.flags),
"confidence": round(float(self.confidence), 3),
"observations": len(self.observations),
@ -510,7 +512,9 @@ class TagService:
aspect = width / float(height)
if not (TAG_MIN_ASPECT <= aspect <= TAG_MAX_ASPECT):
continue
pooled = read_card_lines(self.engine, image, box, scales=(1.0, 2.0 * max(1.0, scale_bias)))
pooled = read_card_lines(
self.engine, image, box, scales=(1.0, 2.0, 3.0 * max(1.0, scale_bias))
)
card = card_from_lines(pooled) if pooled else seed
card.box = box
if not card.fields.is_readable() and card.fields.price is None:
@ -588,6 +592,8 @@ class TagService:
names: Dict[str, float] = {}
units: Dict[float, float] = {}
discounts: Dict[float, float] = {}
jans: Dict[str, float] = {}
sources: Dict[str, float] = {}
flags: set = set()
confidences: List[float] = []
for observation in record.observations:
@ -603,6 +609,11 @@ class TagService:
units[fields.unit_price] = units.get(fields.unit_price, 0.0) + weight
if fields.discount is not None:
discounts[fields.discount] = discounts.get(fields.discount, 0.0) + weight
if getattr(fields, "jan", None):
jans[fields.jan] = jans.get(fields.jan, 0.0) + weight
sources[getattr(fields, "price_source", "none")] = (
sources.get(getattr(fields, "price_source", "none"), 0.0) + weight
)
flags.update(fields.flags)
confidences.append(observation.confidence)
@ -620,6 +631,13 @@ class TagService:
record.name = top(names)
record.unit_price = top(units)
record.discount = top(discounts)
record.jan = top(jans)
# "measured" beats "derived" when both appear: a read is better evidence
# than an inference, even though the inference is usually right.
if sources.get("measured") and sources.get("derived"):
record.price_source = "measured" if sources["measured"] >= sources["derived"] else "derived"
else:
record.price_source = top(sources) or "none"
record.flags = sorted(flags)
record.confidence = sum(confidences) / len(confidences) if confidences else 0.0