#!/usr/bin/env python3 """ Push detection + stats events to test TimelinePanel and CostStatsPanel. Simulates a pipeline run with detections spread across video time, escalation events, and accumulating cost — exercises both new phase 8 panels. Usage: python tests/detect/manual/test_timeline_cost.py [--job JOB_ID] [--port PORT] [--delay SECS] Opens: http://mpr.local.ar/detection/?job= """ import argparse import json import logging import time from datetime import datetime, timezone import redis logging.basicConfig(level=logging.INFO, format="%(levelname)-7s %(name)s — %(message)s") logger = logging.getLogger(__name__) NODES = ["extract_frames", "filter_scenes", "detect_objects", "run_ocr", "match_brands", "escalate_vlm", "escalate_cloud", "compile_report"] # Detections spread across video time with different sources DETECTIONS = [ ("Nike", 0.97, "ocr", 2.0, 0.5), ("Nike", 0.95, "ocr", 4.5, 1.0), ("Emirates", 0.92, "ocr", 5.0, 2.0), ("Adidas", 0.89, "ocr", 8.0, 0.5), ("Nike", 0.94, "ocr", 12.0, 1.5), ("Coca-Cola", 0.85, "ocr", 15.0, 0.5), ("Emirates", 0.88, "ocr", 18.0, 2.0), ("Adidas", 0.91, "ocr", 22.0, 1.0), ("Mastercard", 0.78, "local_vlm", 25.0, 0.5), ("Nike", 0.96, "ocr", 28.0, 1.0), ("Emirates", 0.90, "ocr", 32.0, 2.0), ("Heineken", 0.72, "cloud_llm", 35.0, 0.5), ("Coca-Cola", 0.87, "ocr", 38.0, 0.5), ("Nike", 0.93, "ocr", 42.0, 1.5), ("Unknown", 0.65, "cloud_llm", 45.0, 0.5), ("Adidas", 0.90, "ocr", 48.0, 1.0), ("Emirates", 0.91, "ocr", 52.0, 2.0), ("Nike", 0.95, "ocr", 55.0, 1.0), ] def ts(): return datetime.now(timezone.utc).isoformat() def push(r, key, event): event["ts"] = event.get("ts", ts()) r.rpush(key, json.dumps(event)) return event def push_graph(r, key, active_node, status, delay): nodes = [] for n in NODES: if n == active_node: nodes.append({"id": n, "status": status}) elif NODES.index(n) < NODES.index(active_node): nodes.append({"id": n, "status": "done"}) else: nodes.append({"id": n, "status": "pending"}) push(r, key, {"event": "graph_update", "nodes": nodes}) time.sleep(delay) def push_stats(r, key, **overrides): base = { "event": "stats_update", "frames_extracted": 0, "frames_after_scene_filter": 0, "regions_detected": 0, "regions_resolved_by_ocr": 0, "regions_escalated_to_local_vlm": 0, "regions_escalated_to_cloud_llm": 0, "cloud_llm_calls": 0, "processing_time_seconds": 0, "estimated_cloud_cost_usd": 0, } base.update(overrides) push(r, key, base) def main(): parser = argparse.ArgumentParser() parser.add_argument("--job", default=f"timeline-{int(__import__('time').time()) % 100000}") parser.add_argument("--port", type=int, default=6382) parser.add_argument("--delay", type=float, default=0.4) args = parser.parse_args() r = redis.Redis(port=args.port, decode_responses=True) key = f"detect_events:{args.job}" r.delete(key) logger.info("Pushing %d detections to %s", len(DETECTIONS), key) logger.info("Open: http://mpr.local.ar/detection/?job=%s", args.job) input("\nPress Enter to start...") delay = args.delay # Pipeline stages with progressive stats push_graph(r, key, "extract_frames", "running", delay) push_stats(r, key, frames_extracted=120, processing_time_seconds=3.2) push_graph(r, key, "extract_frames", "done", delay) push_graph(r, key, "filter_scenes", "running", delay) push_stats(r, key, frames_extracted=120, frames_after_scene_filter=45, processing_time_seconds=5.1) push_graph(r, key, "filter_scenes", "done", delay) push_graph(r, key, "detect_objects", "running", delay) push_stats(r, key, frames_extracted=120, frames_after_scene_filter=45, regions_detected=38, processing_time_seconds=12.4) push_graph(r, key, "detect_objects", "done", delay) push_graph(r, key, "run_ocr", "running", delay) push_stats(r, key, frames_extracted=120, frames_after_scene_filter=45, regions_detected=38, regions_resolved_by_ocr=28, processing_time_seconds=18.7) push_graph(r, key, "run_ocr", "done", delay) # Brand matching — push detections one by one push_graph(r, key, "match_brands", "running", delay) for i, (brand, conf, source, timestamp, duration) in enumerate(DETECTIONS): if source != "ocr": continue push(r, key, {"event": "detection", "brand": brand, "confidence": conf, "source": source, "timestamp": timestamp, "duration": duration, "content_type": "soccer_broadcast", "frame_ref": i * 3}) logger.info("[%d] %s %.2f %s t=%.1fs", i + 1, brand, conf, source, timestamp) time.sleep(delay * 0.3) push_graph(r, key, "match_brands", "done", delay) # VLM escalation push_graph(r, key, "escalate_vlm", "running", delay) push(r, key, {"event": "log", "level": "INFO", "stage": "VLMLocal", "msg": "Processing 3 unresolved crops with moondream2"}) time.sleep(delay) for i, (brand, conf, source, timestamp, duration) in enumerate(DETECTIONS): if source != "local_vlm": continue push(r, key, {"event": "detection", "brand": brand, "confidence": conf, "source": source, "timestamp": timestamp, "duration": duration, "content_type": "soccer_broadcast", "frame_ref": i * 3}) logger.info("[vlm] %s %.2f t=%.1fs", brand, conf, timestamp) time.sleep(delay * 0.3) push_stats(r, key, frames_extracted=120, frames_after_scene_filter=45, regions_detected=38, regions_resolved_by_ocr=28, regions_escalated_to_local_vlm=3, processing_time_seconds=25.1, estimated_cloud_cost_usd=0) push_graph(r, key, "escalate_vlm", "done", delay) # Cloud escalation push_graph(r, key, "escalate_cloud", "running", delay) for i, (brand, conf, source, timestamp, duration) in enumerate(DETECTIONS): if source != "cloud_llm": continue push(r, key, {"event": "detection", "brand": brand, "confidence": conf, "source": source, "timestamp": timestamp, "duration": duration, "content_type": "soccer_broadcast", "frame_ref": i * 3}) logger.info("[cloud] %s %.2f t=%.1fs", brand, conf, timestamp) time.sleep(delay * 0.3) push_stats(r, key, frames_extracted=120, frames_after_scene_filter=45, regions_detected=38, regions_resolved_by_ocr=28, regions_escalated_to_local_vlm=3, regions_escalated_to_cloud_llm=2, cloud_llm_calls=2, processing_time_seconds=31.4, estimated_cloud_cost_usd=0.0042) push_graph(r, key, "escalate_cloud", "done", delay) # Report push_graph(r, key, "compile_report", "running", delay) push(r, key, {"event": "log", "level": "INFO", "stage": "Aggregator", "msg": f"Report: {len(set(d[0] for d in DETECTIONS))} brands, {len(DETECTIONS)} detections"}) push_graph(r, key, "compile_report", "done", delay) push(r, key, {"event": "job_complete", "job_id": args.job, "report": { "video_source": "soccer_clip.mp4", "content_type": "soccer_broadcast", "duration_seconds": 60.0, }}) logger.info("Done. Check Timeline (brand bars over time) and Cost & Stats panels.") if __name__ == "__main__": main()