Attach the catalog row to accepted still-image answers

`classify_boxes` returned only the identity, so every accepted still-image
answer reached the client as a bare SKU id with no name or price. Measured
through the real server on add/image6.jpg: 41 of 41 accepted detections had no
`product`. The live path attached the row; the still path did not, which is
the F-02 finding the decision document recorded as still live in production.

The row is attached where the decision is made, so both paths carry it, and
`recognize` passes it through to the wire. Verified through the running server:
41 of 41 accepted answers now carry product_name, price, and stock counts.

Also corrects the decision document's full-resolution claim, which was
measured before the reference-upscaling and text-signature fixes: it said 77%
precision with 11 false accepts and a hub alarm, where the current lane reads
88% with 5 and no alarm. The accept line does not need to differ by input path
- correct facings sit at p25 2.07 (full) and 2.87 (live), wrong ones at p75
1.00 and 1.66, so 2.0 is the boundary in both - and the results table and the
stream G1 numbers are refreshed to the current measurements.
This commit is contained in:
tngo 2026-09-29 00:53:56 +09:00
parent b6c87c45ea
commit 771e9285d7
3 changed files with 116 additions and 34 deletions

View file

@ -685,13 +685,20 @@ class RecognitionEngine:
# operational failure must stay distinguishable from absence.
decision = decide_identity({}, method=method, scale=self.identity_scale)
decision = identity_payload(decision)
results.append(
{
"detection_id": int(box["id"]),
"box": list(box["box"]),
"identity": decision,
}
)
row = {
"detection_id": int(box["id"]),
"box": list(box["box"]),
"identity": decision,
}
# The catalog row travels with an accepted answer. Without it the
# phone gets a bare SKU id and has to look the name and price up
# itself, which is what left 41 of 41 accepted still-image answers
# without a product row.
if decision.get("state") == STATE_RECOGNIZED:
product = self.product(decision.get("sku_id"))
if product is not None:
row["product"] = product
results.append(row)
return results
def inspect(self, image: Any, box: Sequence[int], method: Optional[str] = None) -> Dict[str, Any]:
@ -905,16 +912,18 @@ class RecognitionEngine:
identities: List[Dict[str, Any]] = []
if classify and boxes:
identities = self.classify_boxes(image, boxes, method=method)
identities_by_id = {row["detection_id"]: row["identity"] for row in identities}
identities_by_id = {row["detection_id"]: row for row in identities}
for box in boxes:
detections.append(
{
"id": box["id"],
"box": box["box"],
"confidence": box["confidence"],
"identity": identities_by_id.get(box["id"], identity_payload(None)),
}
)
row = identities_by_id.get(box["id"])
detection = {
"id": box["id"],
"box": box["box"],
"confidence": box["confidence"],
"identity": (row or {}).get("identity") or identity_payload(None),
}
if (row or {}).get("product") is not None:
detection["product"] = row["product"]
detections.append(detection)
slots = self.empty_slots(image.shape, boxes) if with_empty_slots else []
classified_at = time.perf_counter()
summary = self.summarize(detections, slots)