feat: add Van's real baseline lane and stop size tokens naming products
Two review findings, both confirmed by measurement. 1. The evaluator had no always-argmax lane, so every "vs Van" claim compared against the *gated* matcher, which is not Van's contract - the original demo answered every crop with its best-scoring SKU and had no accept line. The new 'van' lane is that contract: same scores, no gate. Against it the local lane wins both axes at both resolutions (full 61%/77% vs 52%/29%; live 59%/95% vs 57%/32%), but the live coverage margin is one facing, not seven, and the 95% figure is live-resolution only - at full resolution the same lane reads 77% with 11 false accepts and a hub alarm. 2. A crop reading only "400g" scored 0.667 on three SKUs, above TEXT_ACCEPT, so the text lane named a product from a size line. The fill-in job now requires two non-digit bigrams; the veto is unchanged, so "65%" still contradicts a local answer. The raw OCR read travels with the ranking through score_crop_lanes for that check. Tests: 3 for the size gate (policy + live) and the van lane's own row. Removing the gate fails all three. Suite 276 -> 280.
This commit is contained in:
parent
13ee7426e2
commit
fd72251160
7 changed files with 180 additions and 22 deletions
|
|
@ -115,7 +115,7 @@ class LocalScaleEngine(RecognitionEngine):
|
|||
def score_crop_lanes(self, crop, method=None):
|
||||
if self.lane_answers:
|
||||
return self.lane_answers.pop(0)
|
||||
return {}, []
|
||||
return {}, [], ""
|
||||
|
||||
|
||||
class FakeInventory:
|
||||
|
|
@ -331,7 +331,7 @@ class LocalScaleTests(unittest.TestCase):
|
|||
dropping that path silently removed the lane's fill-in job.
|
||||
"""
|
||||
session, engine, _, _ = make_session(engine_cls=LocalScaleEngine)
|
||||
engine.lane_answers = [({}, [("sku_a", 0.9)])]
|
||||
engine.lane_answers = [({}, [("sku_a", 0.9)], "コクうま 65%")]
|
||||
result = process(session, 1)
|
||||
self.assertTrue(
|
||||
wait_for(lambda: any(
|
||||
|
|
@ -355,7 +355,7 @@ class LocalScaleTests(unittest.TestCase):
|
|||
wrong reason.
|
||||
"""
|
||||
session, engine, _, _ = make_session(engine_cls=LocalScaleEngine)
|
||||
engine.lane_answers = [({"sku_a": 5.0}, [("other", 0.3)])] * 4
|
||||
engine.lane_answers = [({"sku_a": 5.0}, [("other", 0.3)], "other マヨネーズ")] * 4
|
||||
for frame_id in range(1, 14):
|
||||
result = process(session, frame_id)
|
||||
for track in result["tracks"]:
|
||||
|
|
@ -365,6 +365,22 @@ class LocalScaleTests(unittest.TestCase):
|
|||
)
|
||||
engine.shutdown()
|
||||
|
||||
def test_a_size_only_read_cannot_name_a_product(self):
|
||||
"""A crop reading "400g" matches every 400g product's size line.
|
||||
|
||||
Size and number tokens are not identity text: without this gate the
|
||||
text lane answered a distractor crop reading only "400g" as a product.
|
||||
"""
|
||||
session, engine, _, _ = make_session(engine_cls=LocalScaleEngine)
|
||||
engine.lane_answers = [({}, [("sku_a", 0.9)], "400g")] * 4
|
||||
for frame_id in range(1, 14):
|
||||
for track in process(session, frame_id)["tracks"]:
|
||||
self.assertIsNone(
|
||||
track["identity"]["sku_id"],
|
||||
"a size-only read must not name a product",
|
||||
)
|
||||
engine.shutdown()
|
||||
|
||||
|
||||
class TagPassIntegrationTests(unittest.TestCase):
|
||||
"""The tag pass must reach the live result without blocking frames."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue