- 01Decoded text
- 02Full-match validation
- 03Recent-label map
- 04Accept or ignore
A frame is not an event
The camera may decode the same label on many consecutive frames. The journal needs a policy for turning those observations into events. This implementation remembers accepted labels for five seconds and limits the set to sixty-four entries.
"""A small, bounded QR event policy; no camera library required."""
import math
import re
class Journal:
def __init__(self, cooldown=5.0, capacity=64):
if cooldown <= 0 or not math.isfinite(cooldown) or capacity < 1:
raise ValueError("positive cooldown and capacity required")
self.cooldown, self.capacity = cooldown, capacity
self.seen = {}
self.last_time = None
def accept(self, payload, now):
if not isinstance(payload, str) or not re.fullmatch(r"CRIGOR:[A-Z0-9][A-Z0-9_-]{0,23}", payload):
return None
if not isinstance(now, (int, float)) or isinstance(now, bool) or not math.isfinite(now):
raise ValueError("finite monotonic timestamp required")
if self.last_time is not None and now < self.last_time:
raise ValueError("clock moved backwards")
self.last_time = now
self.seen = {key: t for key, t in self.seen.items() if now - t < self.cooldown}
if payload in self.seen or len(self.seen) >= self.capacity:
return None
self.seen[payload] = now
return {"label": payload.removeprefix("CRIGOR:"), "elapsed_s": now}At exactly the cooldown boundary, an expired label is allowed again. A second label can be recorded immediately. When the recent set is full, an additional label is ignored until space expires; memory use does not grow with arbitrary QR input.
last_time rejects a backwards timestamp. Tests can supply explicit times, while the real adapter calls time.monotonic(). A clock value is not inferred from frame number because camera frame rates vary.
Run a controlled replay
from journal import Journal
j = Journal()
print(j.accept("CRIGOR:BOX_A", 1.0))
print(j.accept("CRIGOR:BOX_A", 2.0))
print(j.accept("CRIGOR:BOX_A", 6.0))The middle result is None. The first and third are event dictionaries. Change the second label to CRIGOR:BOX_B and explain why it is accepted.
Try it
Run the supplied tests, then add a test for the longest allowed label. Decide whether a two-second cooldown would fit a different project and change the constructor argument, rather than scattering timing constants throughout capture code.