From f4b9000100d464703e8b627eb334ced127de3213 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Thu, 16 Apr 2026 14:48:27 +0000 Subject: [PATCH] feat(run): per-frame detection log at logs/detections/YYYY-MM-DD.jsonl Writes one JSONL line per detector.step() with ts, rgb, match_name, distance, confidence, dot_found, window_found, accepted, color. Captures UNKNOWN classifications and no-dot frames that today's audit log skips, so the user can verify post-session what colors the program actually saw. Reuses AuditLog for daily rotation + buffering. Separate subdir keeps audit.jsonl uncluttered. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/atm/main.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/atm/main.py b/src/atm/main.py index b542b4d..e93ae97 100644 --- a/src/atm/main.py +++ b/src/atm/main.py @@ -447,6 +447,7 @@ def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None: fsm = StateMachine(lockout_s=cfg.lockout_s) canary = Canary(cfg, pause_flag_path=Path("logs/pause.flag")) audit = AuditLog(Path("logs")) + detection_log = AuditLog(Path("logs/detections")) backends = [ DiscordNotifier(cfg.discord.webhook_url), TelegramNotifier(cfg.telegram.bot_token, cfg.telegram.chat_id), @@ -503,6 +504,18 @@ def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None: continue # detection res = detector.step(now) + detection_log.log({ + "ts": now, + "event": "frame", + "window_found": res.window_found, + "dot_found": res.dot_found, + "rgb": list(res.rgb) if res.rgb is not None else None, + "match_name": res.match.name if res.match is not None else None, + "distance": round(res.match.distance, 2) if res.match is not None else None, + "confidence": round(res.match.confidence, 3) if res.match is not None else None, + "accepted": res.accepted, + "color": res.color, + }) if res.accepted and res.color: is_first = first_accepted first_accepted = False @@ -569,6 +582,7 @@ def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None: pass notifier.stop() audit.close() + detection_log.close() def _build_capture(cfg, capture_stub: bool = False):