DB: write incrementally after each camera, Flask dashboard with single-camera view
- batch_runner: _store_to_db after each camera (not just at end) - dashboard: Flask app with templates/index.html - Single-camera full-screen live stream with auto-switch - DB history panel in sidebar - store_results.py: standalone script for existing reports
This commit is contained in:
1 parent
6dbaa517dc
commit
efd4726bc6
4 files changed
+446
-278
No files matched your search
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Store batch run results into a SQLite database.
|
||||
|
||||
Reads the aggregate JSON report written by the batch runner and inserts
|
||||
all camera-level + summary data. Safe to run multiple times — uses
|
||||
(date, location, camera_id) as the unique key, so re-runs update
|
||||
existing rows instead of duplicating.
|
||||
|
||||
Usage:
|
||||
python3 store_results.py /path/to/output/counts_2026-06-10.json --location kandang-atas
|
||||
python3 store_results.py /path/to/output/counts_2026-06-10.json --db /var/lib/chickens.db
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sqlite3
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS batch_runs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
date TEXT NOT NULL,
|
||||
location TEXT NOT NULL,
|
||||
camera_id TEXT NOT NULL,
|
||||
total_entered INTEGER NOT NULL DEFAULT 0,
|
||||
frames_processed INTEGER NOT NULL DEFAULT 0,
|
||||
elapsed_seconds REAL NOT NULL DEFAULT 0.0,
|
||||
stopped_reason TEXT NOT NULL DEFAULT '',
|
||||
source_video TEXT NOT NULL DEFAULT '',
|
||||
generated_at TEXT NOT NULL DEFAULT '',
|
||||
UNIQUE(date, location, camera_id)
|
||||
)
|
||||
"""
|
||||
|
||||
INSERT_SQL = """
|
||||
INSERT INTO batch_runs
|
||||
(date, location, camera_id, total_entered, frames_processed,
|
||||
elapsed_seconds, stopped_reason, source_video, generated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(date, location, camera_id) DO UPDATE SET
|
||||
total_entered = excluded.total_entered,
|
||||
frames_processed = excluded.frames_processed,
|
||||
elapsed_seconds = excluded.elapsed_seconds,
|
||||
stopped_reason = excluded.stopped_reason,
|
||||
source_video = excluded.source_video,
|
||||
generated_at = excluded.generated_at
|
||||
"""
|
||||
|
||||
SUMMARY_QUERY = """
|
||||
SELECT
|
||||
date,
|
||||
location,
|
||||
COUNT(*) AS camera_count,
|
||||
SUM(total_entered) AS total_chickens,
|
||||
SUM(elapsed_seconds) AS total_seconds,
|
||||
ROUND(SUM(elapsed_seconds) / 60.0, 1) AS total_minutes
|
||||
FROM batch_runs
|
||||
WHERE date = ? AND location = ?
|
||||
GROUP BY date, location
|
||||
"""
|
||||
|
||||
|
||||
def store_report(report_path: str, location: str, db_path: str) -> None:
|
||||
with open(report_path) as f:
|
||||
report = json.load(f)
|
||||
|
||||
date = report["date"]
|
||||
cameras = report["cameras"]
|
||||
generated_at = report.get("generated_at", "")
|
||||
|
||||
Path(db_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute(CREATE_TABLE)
|
||||
|
||||
rows = 0
|
||||
for camera_id, entry in cameras.items():
|
||||
if entry.get("skipped"):
|
||||
continue
|
||||
conn.execute(INSERT_SQL, (
|
||||
date, location, camera_id,
|
||||
entry.get("total_entered", 0),
|
||||
entry.get("frames_processed", 0),
|
||||
entry.get("elapsed_seconds", 0),
|
||||
entry.get("stopped_reason", ""),
|
||||
entry.get("source_video", ""),
|
||||
generated_at,
|
||||
))
|
||||
rows += 1
|
||||
|
||||
conn.commit()
|
||||
|
||||
# print summary
|
||||
row = conn.execute(SUMMARY_QUERY, (date, location)).fetchone()
|
||||
if row:
|
||||
print(f"\n[db] {row[0]} | {row[1]} | {row[2]} cameras | "
|
||||
f"{row[3]} chickens | {row[4]:.0f}s ({row[5]} min)")
|
||||
|
||||
# also print per-camera breakdown
|
||||
cur = conn.execute(
|
||||
"SELECT camera_id, total_entered, elapsed_seconds "
|
||||
"FROM batch_runs WHERE date=? AND location=? ORDER BY camera_id",
|
||||
(date, location))
|
||||
for cam_id, count, secs in cur:
|
||||
print(f" {cam_id}: {count} chickens, {secs:.0f}s")
|
||||
|
||||
conn.close()
|
||||
print(f"\n[db] wrote {rows} rows to {db_path}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Store batch run results into SQLite")
|
||||
parser.add_argument("report", help="Path to counts_YYYY-MM-DD.json")
|
||||
parser.add_argument("--location", required=True, help="Location name (e.g. kandang-atas)")
|
||||
parser.add_argument("--db", default="chicken_counts.db", help="SQLite database path")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not Path(args.report).exists():
|
||||
print(f"error: report not found: {args.report}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
store_report(args.report, args.location, args.db)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user