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

@ -244,6 +244,33 @@ class EngineClassificationTests(unittest.TestCase):
self.assertEqual(rows[0]["identity"]["state"], "recognized")
self.assertEqual(rows[0]["identity"]["sku_id"], "sku_a")
def test_an_accepted_detection_carries_its_catalog_row(self):
"""The phone must not have to look up a name and price from a bare SKU.
`classify_boxes` returned only the identity, so every accepted
still-image answer reached the client as an id with no product row:
measured through the real server, 41 of 41 accepted detections had no
`product`. The live path attached it; the still path did not.
"""
engine, _, _, _ = make_engine()
detections = [
{"id": 0, "box": [10, 10, 60, 90], "confidence": 0.9,
"identity": identity_payload(None)},
]
rows = engine.classify_boxes(make_image(), detections, method="hybrid")
self.assertEqual(rows[0]["identity"]["state"], "recognized")
self.assertEqual(rows[0]["product"]["product_name"], "Alpha")
self.assertEqual(rows[0]["product"]["price"], 100.0)
result = engine.recognize(make_image(), method="hybrid")
accepted = [
row for row in result["detections"]
if row["identity"]["identity_level"] == "accepted"
]
self.assertTrue(accepted, "the fake detector and matcher accept a box")
for row in accepted:
self.assertIn("product", row, "an accepted answer must carry its row")
def test_a_failing_crop_reports_unrecognized_not_pending(self):
"""One bad crop must not fail the batch, and must not read as pending.