Cache only text reads that could name a product

The per-track OCR cache kept any non-empty read, so a first crop that read
only a size ("400g") was replayed on every later attempt. A size matches every
400g product's size line, so the track could never be named even after a clean
crop arrived: the cached read replaced it. Only a read carrying identity text
is cached now, so the next attempt gets a fresh OCR.

`carries_identity` moves to module scope in live.py; a function-local import
shadowed the name at the cache site and raised UnboundLocalError.
This commit is contained in:
tngo 2026-09-29 00:41:52 +09:00
parent b6c7c4b667
commit fc49ba60e6
2 changed files with 30 additions and 3 deletions

View file

@ -403,6 +403,29 @@ class LocalScaleTests(unittest.TestCase):
self.assertEqual(engine.ocr_calls, 1, "OCR must run once per track")
engine.shutdown()
def test_a_size_only_read_is_not_cached(self):
"""An unreadable crop must not veto the product for the track's life.
The cache used to keep any non-empty read, so a first crop that read
only "400g" was replayed on every later attempt: the track could never
be named even after a clean crop arrived, because the cached read
replaced it. Only a read that carries identity text is cached now, so
the next attempt gets a fresh OCR.
"""
session, engine, _, _ = make_session(engine_cls=LocalScaleEngine)
engine.lane_answers = [
({}, [("sku_a", 0.9)], "400g"),
({}, [("sku_a", 0.9)], "コクうま 65%"),
] + [({}, [("sku_a", 0.9)], "コクうま 65%")] * 6
labelled = False
for frame_id in range(1, 14):
for track in process(session, frame_id)["tracks"]:
if track["identity"]["sku_id"] == "sku_a":
labelled = True
self.assertTrue(labelled, "a later readable crop must still name the track")
self.assertGreater(engine.ocr_calls, 1, "the size-only read must not be cached")
engine.shutdown()
class TagPassIntegrationTests(unittest.TestCase):
"""The tag pass must reach the live result without blocking frames."""