feat(v1): add event-log summary tool for acceptance evidence

v1/report.py reads events.jsonl and lists each confirmed event with its
latency (flags the 1-3s target) and screenshot, or reports a clean pass
when empty. Supports T-204 latency records and false-alarm counting.
78 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ila
2026-07-21 23:52:59 +08:00
co-authored by Claude Opus 4.8
parent 68f30d416f
commit 2688f2d449
3 changed files with 147 additions and 1 deletions
+86
View File
@@ -0,0 +1,86 @@
"""Summarize confirmed fall events for review, latency records and
false-alarm counting during acceptance sessions.
Reads the JSONL written by ``alerts.EventArtifactWriter``. Use it after a test
session: run some normal activity (walking, sitting, bending, picking up) and
check that no events were recorded, or read back the latency of real falls.
python -m v1.report # scans artifacts/events
python -m v1.report artifacts/events # a source directory
python -m v1.report .../20260721/events.jsonl
"""
import json
import sys
from pathlib import Path
from typing import Dict, List
def load_events(jsonl_path: Path) -> List[Dict]:
path = Path(jsonl_path)
if not path.is_file():
return []
events = []
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line:
events.append(json.loads(line))
return events
def collect_jsonl(root: Path) -> List[Path]:
"""Return every events.jsonl under a source/day directory, or the file itself."""
path = Path(root)
if path.is_file():
return [path]
return sorted(path.glob("**/events.jsonl"))
def summarize(events: List[Dict]) -> Dict:
latencies = [float(e.get("latency_seconds", 0.0)) for e in events]
within = [lat for lat in latencies if 1.0 <= lat <= 3.0]
return {
"count": len(events),
"latencies": latencies,
"latency_in_target": len(within),
}
def format_report(jsonl_path: Path, events: List[Dict]) -> str:
summary = summarize(events)
lines = [
"{0}".format(jsonl_path),
" events: {0} (latency in 1-3s: {1}/{0})".format(
summary["count"], summary["latency_in_target"]
),
]
for event in events:
lines.append(
" {0} track={1} utc={2} latency={3:.2f}s shot={4}".format(
event.get("event_id", "?"),
event.get("track_id", "?"),
event.get("confirmed_at_utc", "?"),
float(event.get("latency_seconds", 0.0)),
event.get("screenshot", "?"),
)
)
if not events:
lines.append(" (no confirmed events — a clean pass for negative activity)")
return "\n".join(lines)
def main(argv=None) -> int:
argv = list(sys.argv[1:] if argv is None else argv)
root = Path(argv[0]) if argv else Path("artifacts/events")
files = collect_jsonl(root)
if not files:
print("no events.jsonl found under {0}".format(root))
return 0
for jsonl_path in files:
print(format_report(jsonl_path, load_events(jsonl_path)))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+60
View File
@@ -0,0 +1,60 @@
import json
from v1.report import collect_jsonl, format_report, load_events, summarize
def _write_events(path, events):
path.write_text(
"\n".join(json.dumps(e, ensure_ascii=False) for e in events) + "\n",
encoding="utf-8",
)
def _event(event_id, latency):
return {
"event_id": event_id,
"track_id": "P-0001",
"confirmed_at_utc": "2026-07-21T08:30:00+00:00",
"latency_seconds": latency,
"screenshot": "20260721/{0}.png".format(event_id),
}
def test_load_events_reads_each_jsonl_line(tmp_path):
jsonl = tmp_path / "events.jsonl"
_write_events(jsonl, [_event("FALL-A-000001", 1.8), _event("FALL-A-000002", 2.1)])
events = load_events(jsonl)
assert len(events) == 2
assert events[0]["event_id"] == "FALL-A-000001"
def test_summarize_counts_latency_in_target_window(tmp_path):
events = [_event("a", 1.8), _event("b", 0.9), _event("c", 3.0)]
summary = summarize(events)
assert summary["count"] == 3
assert summary["latency_in_target"] == 2 # 1.8 and 3.0 are within 1-3s, 0.9 is not
def test_empty_log_reads_as_clean_pass(tmp_path):
jsonl = tmp_path / "events.jsonl"
jsonl.write_text("", encoding="utf-8")
events = load_events(jsonl)
report = format_report(jsonl, events)
assert events == []
assert "no confirmed events" in report
def test_collect_jsonl_finds_day_directories(tmp_path):
day = tmp_path / "20260721"
day.mkdir()
_write_events(day / "events.jsonl", [_event("FALL-A-000001", 1.8)])
found = collect_jsonl(tmp_path)
assert found == [day / "events.jsonl"]