feat: improve webhook filtering, worker status startup handling, and timestamp parsing

- Skip half_hour_report events from webhook posts in people_flow
- Handle pre-existing stale worker status files during startup gracefully
- Make store_dwell_alert timestamp parsing robust against invalid/empty values
- Update lessons learned and todo documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 17:05:31 +08:00
parent 9cde462cd1
commit 4e2ca3cff7
8 changed files with 148 additions and 63 deletions

View File

@@ -11,6 +11,10 @@ def _payload_for_webhook(payload: dict) -> dict:
return outbound
def _should_post_webhook(payload: dict) -> bool:
return payload.get("event") != "half_hour_report"
def dispatch_json_event(
path: str | Path,
payload: dict,
@@ -22,7 +26,7 @@ def dispatch_json_event(
with output_path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(payload, ensure_ascii=False) + "\n")
if not webhook_url.strip():
if not webhook_url.strip() or not _should_post_webhook(payload):
return
req = request.Request(

View File

@@ -66,12 +66,17 @@ def worker_status_stall_reason(
now: float | None = None,
) -> str | None:
current_time = datetime.now().timestamp() if now is None else now
age_seconds = worker_status_age_seconds(path, now=current_time)
if age_seconds is None:
try:
stat_result = path.stat()
except FileNotFoundError:
if current_time - started_at < max_age_seconds:
return None
return f"rtsp worker status missing path={path}"
if stat_result.st_mtime < started_at and current_time - started_at < max_age_seconds:
return None
age_seconds = max(0.0, current_time - stat_result.st_mtime)
if age_seconds <= max_age_seconds:
return None

View File

@@ -5,7 +5,7 @@ import json
from src.people_flow.webhook import dispatch_json_event
def test_dispatch_json_event_omits_tracks_from_webhook_but_keeps_local_log(
def test_dispatch_json_event_does_not_post_half_hour_report_but_keeps_local_log(
tmp_path, monkeypatch
):
sent: dict[str, object] = {}
@@ -44,9 +44,4 @@ def test_dispatch_json_event_omits_tracks_from_webhook_but_keeps_local_log(
lines = output.read_text(encoding="utf-8").splitlines()
assert json.loads(lines[0]) == payload
assert sent["url"] == "https://example.test/webhook"
assert sent["timeout"] == 7.5
assert sent["payload"] == {
"event": "half_hour_report",
"total_people": 3,
}
assert sent == {}

View File

@@ -99,3 +99,29 @@ def test_worker_status_stall_reason_reports_missing_and_stale_status(tmp_path: P
assert "status=missing" not in reason
assert "phase=tracking_frame" in reason
assert "age_seconds=200.0" in reason
def test_worker_status_stall_reason_ignores_preexisting_stale_file_during_startup(
tmp_path: Path,
):
status_path = tmp_path / "worker_status.json"
write_worker_status(
status_path,
"waiting_to_reconnect",
source="rtsp://camera/stream",
window_index=0,
frame_index=0,
last_processed_at=None,
note="open_failed",
)
os.utime(status_path, (100.0, 100.0))
assert (
worker_status_stall_reason(
status_path,
started_at=250.0,
max_age_seconds=180.0,
now=300.0,
)
is None
)