feat: price-tag UI, still-image tag passes, and honest tag evidence
Web client: price-tag boxes in a distinct style (teal, chip with the parsed price), an OCR section in the product card showing the tag's price, name, discount and the reconciliation verdict with both values, and a compliance strip under the stage. The photo stage now draws the image into the canvas and uses the same containment as the media, so boxes cannot drift from the product (they did: the canvas stretched to the stage while the photo was contained). Server fixes the browser runs forced out: - Photo mode reconciled before identities existed, so every tag read "tag only". The tag bundle is cached with the still and classify re-reconciles against the identities it just produced, returning them per detection. - The still path only ever read the first four bands (plan hands out two per call); it now drains every band, twice at different upscales so the vote has independent reads. - Voting is confidence-weighted and a dead heat stays unread; the plausible shelf-price range is 10-9999, which stops a misread from becoming a ¥26,300 price. The compliance coverage bar moved to 0.7 after 0.5 produced a false "group without price tag" on a shelf where every facing has one. Measured on add/image6.jpg: 8 readable tags with names and prices, 942 ms for the whole still, 7 real price mismatches, no false violations, and hints that say plainly when a row could not be read. Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
a27841afa4
commit
8f750c15c5
8 changed files with 330 additions and 60 deletions
|
|
@ -152,15 +152,15 @@ BANDS = [band(1, [0, 300, 700, 340]), band(2, [0, 600, 700, 640])]
|
|||
|
||||
class ComplianceTests(unittest.TestCase):
|
||||
def test_missing_tag_on_a_covered_row_is_a_violation(self):
|
||||
tracks = [
|
||||
track(1, [10, 100, 90, 300]),
|
||||
track(2, [110, 100, 190, 300]),
|
||||
]
|
||||
# 3 of 4 facings carry tag evidence (75% >= the 70% bar), one has none.
|
||||
tracks = [track(index, [10 + index * 150, 100, 140 + index * 150, 300]) for index in range(4)]
|
||||
tags = {
|
||||
1: tag(state=TAG_STATE_READABLE, price=168),
|
||||
0: tag(state=TAG_STATE_READABLE, price=168, id="tag_a"),
|
||||
1: tag(state=TAG_STATE_READABLE, price=178, id="tag_b"),
|
||||
2: tag(state=TAG_STATE_ABSENT),
|
||||
3: tag(state=TAG_STATE_READABLE, price=188, id="tag_c"),
|
||||
}
|
||||
findings = evaluate_compliance(tracks, tags, [tags[1]], BANDS)
|
||||
findings = evaluate_compliance(tracks, tags, [tags[0], tags[1], tags[3]], BANDS)
|
||||
rules = [finding.rule for finding in findings]
|
||||
self.assertIn(RULE_MISSING_TAG, rules)
|
||||
missing = [finding for finding in findings if finding.rule == RULE_MISSING_TAG][0]
|
||||
|
|
@ -201,18 +201,13 @@ class ComplianceTests(unittest.TestCase):
|
|||
self.assertEqual(findings, [])
|
||||
|
||||
def test_group_of_consecutive_facings_without_tags(self):
|
||||
# 5 facings: 2 tagged, 3 not. Coverage counts facings with *any* tag
|
||||
# evidence, so give the row enough evidence to be judged at all.
|
||||
tracks = [track(index, [10 + index * 100, 100, 90 + index * 100, 300]) for index in range(6)]
|
||||
tags = {
|
||||
0: tag(state=TAG_STATE_READABLE, price=168, id="tag_a"),
|
||||
1: tag(state=TAG_STATE_READABLE, price=178, id="tag_b"),
|
||||
2: tag(state=TAG_STATE_ABSENT),
|
||||
3: tag(state=TAG_STATE_ABSENT),
|
||||
4: tag(state=TAG_STATE_ABSENT),
|
||||
5: tag(state=TAG_STATE_READABLE, price=188, id="tag_c"),
|
||||
}
|
||||
findings = evaluate_compliance(tracks, tags, [tags[0], tags[1], tags[5]], BANDS)
|
||||
# Coverage counts facings with *any* tag evidence, so the row needs enough
|
||||
# of it to be judged at all (bar is 70%).
|
||||
tracks = [track(index, [10 + index * 70, 100, 70 + index * 70, 300]) for index in range(10)]
|
||||
tags = {index: tag(state=TAG_STATE_READABLE, price=168 + index, id=f"tag_{index}") for index in range(10)}
|
||||
for absent in (2, 3, 4):
|
||||
tags[absent] = tag(state=TAG_STATE_ABSENT, id=None)
|
||||
findings = evaluate_compliance(tracks, tags, [t for t in tags.values() if t["id"]], BANDS)
|
||||
groups = [finding for finding in findings if finding.rule == RULE_MISSING_TAG_GROUP]
|
||||
self.assertEqual(len(groups), 1)
|
||||
self.assertEqual(groups[0].count, 3)
|
||||
|
|
@ -220,13 +215,14 @@ class ComplianceTests(unittest.TestCase):
|
|||
self.assertIn("3 facings", groups[0].detail)
|
||||
|
||||
def test_two_missing_facings_are_not_a_group(self):
|
||||
tracks = [track(index, [10 + index * 100, 100, 90 + index * 100, 300]) for index in range(3)]
|
||||
tracks = [track(index, [10 + index * 150, 100, 140 + index * 150, 300]) for index in range(4)]
|
||||
tags = {
|
||||
0: tag(state=TAG_STATE_READABLE, price=168, id="tag_a"),
|
||||
1: tag(state=TAG_STATE_ABSENT),
|
||||
2: tag(state=TAG_STATE_READABLE, price=188, id="tag_b"),
|
||||
3: tag(state=TAG_STATE_READABLE, price=198, id="tag_c"),
|
||||
}
|
||||
findings = evaluate_compliance(tracks, tags, [tags[0], tags[2]], BANDS)
|
||||
findings = evaluate_compliance(tracks, tags, [tags[0], tags[2], tags[3]], BANDS)
|
||||
self.assertNotIn(RULE_MISSING_TAG_GROUP, [finding.rule for finding in findings])
|
||||
missing = [finding for finding in findings if finding.rule == RULE_MISSING_TAG]
|
||||
self.assertEqual(len(missing), 1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue