From 06f9fb4819af22c6883ba66965601facdc5cde8f Mon Sep 17 00:00:00 2001 From: kiyo9w Date: Mon, 28 Sep 2026 13:11:06 +0900 Subject: [PATCH] fix: include latest shelf tag in live inspection --- recognization/code/live.py | 28 +++++++++++++++++++++++- recognization/code/test_live.py | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/recognization/code/live.py b/recognization/code/live.py index 385f379..8e1308b 100644 --- a/recognization/code/live.py +++ b/recognization/code/live.py @@ -191,6 +191,10 @@ class LiveSession: self._shift_map = None self._compliance_tracker = None self._last_compliance: Dict[str, Any] = {"violations": 0, "by_rule": {}, "rows": []} + # Settled tag + reconciliation from the last result, keyed by track. + # Inspect copies these; it does not re-run OCR. + self._settled_tags: Dict[int, Dict[str, Any]] = {} + self._settled_tag_seq = 0 self._label_memory: "collections.deque[Dict[str, Any]]" = collections.deque( maxlen=LABEL_MEMORY_MAX ) @@ -293,6 +297,8 @@ class LiveSession: self._latest_frame_id = None self._frame_origin = [0.0, 0.0] self._shift_map = None + self._settled_tags = {} + self._settled_tag_seq = 0 # ------------------------------------------------------------------ frame path def process_frame(self, frame_id: int, payload: bytes) -> Dict[str, Any]: @@ -439,6 +445,17 @@ class LiveSession: if tag_summary is not None: summary["price_tags"] = tag_summary["tags"] summary["compliance"] = tag_summary["compliance"] + settled: Dict[int, Dict[str, Any]] = {} + for payload in track_payload: + snapshot: Dict[str, Any] = {} + if "price_tag" in payload: + snapshot["price_tag"] = payload["price_tag"] + if "reconciliation" in payload: + snapshot["reconciliation"] = payload["reconciliation"] + if snapshot: + settled[int(payload["track_id"])] = snapshot + self._settled_tags = settled + self._settled_tag_seq = seq self._submit_classification(submissions, method, generation) self._run_tag_passes(image, tag_bands) @@ -912,7 +929,16 @@ class LiveSession: if track is None or image is None: raise KeyError("TRACK_NOT_IN_VIEW") box = list(track.raw_box) - return self.engine.inspect(image, box, method=chosen_method) + # Snapshot is the frame current at tap time. engine.inspect runs + # outside the lock, so a newer frame may land first; the reply still + # carries the tag from the frame the tap matched, not that later one. + settled = None + if self._settled_tag_seq == self._latest_seq: + settled = self._settled_tags.get(track_id) + result = self.engine.inspect(image, box, method=chosen_method) + if settled: + result.update(settled) + return result # ------------------------------------------------------------------ catalog cache def refresh_catalog(self) -> None: diff --git a/recognization/code/test_live.py b/recognization/code/test_live.py index d1355c5..a5a4eab 100644 --- a/recognization/code/test_live.py +++ b/recognization/code/test_live.py @@ -384,6 +384,44 @@ class ClassificationBudgetTests(unittest.TestCase): process(session, 1) with self.assertRaises(KeyError): session.inspect(999) + + def test_inspect_includes_settled_tag_from_the_latest_frame(self): + session, engine, _, _ = make_session() + tag = {"state": "readable", "price": 150, "box": [100, 270, 200, 320]} + + class Tagged: + def plan(self, boxes, image_shape, shelf_offset=None): + return [] + + def attach(self, tracks): + by_track = {int(track["track_id"]): dict(tag) for track in tracks} + return by_track, list(by_track.values()), {"readable": len(by_track)} + + def stats(self): + return {} + + session._tags = Tagged() + result = process(session, 1) + track = result["tracks"][0] + track_id = track["track_id"] + self.assertEqual(track["price_tag"]["price"], 150) + self.assertIn("reconciliation", track) + + inspected = session.inspect(track_id) + self.assertEqual(inspected["price_tag"], track["price_tag"]) + self.assertEqual(inspected["reconciliation"], track["reconciliation"]) + self.assertIn("identity", inspected) + + session._tags = None + bare = process(session, 2) + self.assertNotIn("price_tag", bare["tracks"][0]) + inspected_bare = session.inspect(track_id) + self.assertNotIn("price_tag", inspected_bare) + self.assertNotIn("reconciliation", inspected_bare) + + session.reset() + with self.assertRaises(KeyError): + session.inspect(track_id) engine.shutdown() def test_close_stops_the_loop(self):