update API

This commit is contained in:
Alberto-Audrix committed 2026-07-15 13:49:15 +07:00
1 parent 783b785b4f
commit 2b52ce6e6d
1 file changed
+249 -20
+249 -20
View File
@@ -266,46 +266,275 @@ def snapshots_page():
return render_template("snapshots.html", site_name=SITE_NAME, show_detect=SAVE_DETECT_SNAPSHOT)
def _empty_current():
return {
"counting_date": get_counting_date(),
"count": 0,
"count_in": 0,
"count_out": 0,
"start_time": None,
"last_detection_time": None,
}
def _load_current_state():
"""Load live counter state from current_counter.json."""
with open(CURRENT_COUNTER_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
return {
"counting_date": data.get("counting_date") or get_counting_date(),
"count": int(data.get("count", 0) or 0),
"count_in": int(data.get("count_in", 0) or 0),
"count_out": int(data.get("count_out", 0) or 0),
"start_time": data.get("start_time"),
"last_detection_time": data.get("last_detection_time"),
}
def _query_history(date_from=None, date_to=None, days=None, limit=None, offset=0):
"""Query daily_counters with optional date range and pagination."""
conn = get_db()
cur = conn.cursor()
clauses = []
params = []
if days is not None and date_from is None and date_to is None:
date_from = (datetime.now() - timedelta(days=days)).date().isoformat()
if date_from:
clauses.append("counting_date >= ?")
params.append(date_from)
if date_to:
clauses.append("counting_date <= ?")
params.append(date_to)
where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
cur.execute(f"SELECT COUNT(*) AS total FROM daily_counters {where}", params)
total = cur.fetchone()["total"]
sql = f"""
SELECT counting_date, camera_name, object_label,
total_count, total_in, total_out, start_time, end_time, updated_at
FROM daily_counters
{where}
ORDER BY counting_date DESC
"""
page_params = list(params)
if limit is not None:
sql += " LIMIT ? OFFSET ?"
page_params.extend([limit, offset])
cur.execute(sql, page_params)
rows = [
{
"date": row["counting_date"],
"camera_name": row["camera_name"],
"object_label": row["object_label"],
"total_count": row["total_count"],
"total_in": row["total_in"],
"total_out": row["total_out"],
"diff": (row["total_in"] or 0) - (row["total_out"] or 0),
"start_time": row["start_time"],
"end_time": row["end_time"],
"updated_at": row["updated_at"],
}
for row in cur.fetchall()
]
conn.close()
return rows, total
@app.route("/api/current")
@app.route("/api/current-counter")
def api_current_counter():
def api_current():
"""Current counting-day totals from live state file."""
try:
with open(CURRENT_COUNTER_PATH, "r") as f:
data = json.load(f)
state = _load_current_state()
return jsonify(
{
"success": True,
"counting_date": data.get("counting_date"),
"count": data.get("count", 0),
"count_in": data.get("count_in", 0),
"count_out": data.get("count_out", 0),
"start_time": data.get("start_time"),
"last_detection_time": data.get("last_detection_time"),
"active": True,
"site_name": SITE_NAME,
"counting": _read_counting_flag() if CONTROL_ENABLED else True,
**state,
}
)
except FileNotFoundError:
return jsonify(
{
"success": False,
"error": "No active counter",
"count": 0,
"count_in": 0,
"count_out": 0,
"counting_date": None,
"success": True,
"site_name": SITE_NAME,
"counting": _read_counting_flag() if CONTROL_ENABLED else True,
"active": False,
"error": "No active counter — state file missing",
**_empty_current(),
}
), 200
except Exception as e:
return jsonify(
{
"success": False,
"error": str(e),
"count": 0,
"count_in": 0,
"count_out": 0,
"counting_date": None,
"error": f"Failed to read current counter from {CURRENT_COUNTER_PATH}: {e}",
"site_name": SITE_NAME,
**_empty_current(),
}
), 500
@app.route("/api/history")
def api_history():
"""
Historical daily counters.
Query params:
days – last N calendar days (default 30; ignored if date_from/date_to set)
date_from – inclusive YYYY-MM-DD
date_to – inclusive YYYY-MM-DD
limit – page size (default: all matching)
offset – page offset (default 0)
"""
try:
days = request.args.get("days", type=int)
date_from = request.args.get("date_from")
date_to = request.args.get("date_to")
limit = request.args.get("limit", type=int)
offset = request.args.get("offset", 0, type=int)
if date_from:
try:
datetime.strptime(date_from, "%Y-%m-%d")
except ValueError:
return jsonify({
"success": False,
"error": {
"code": "VALIDATION_ERROR",
"message": f"Invalid date_from '{date_from}'. Use YYYY-MM-DD.",
},
}), 400
if date_to:
try:
datetime.strptime(date_to, "%Y-%m-%d")
except ValueError:
return jsonify({
"success": False,
"error": {
"code": "VALIDATION_ERROR",
"message": f"Invalid date_to '{date_to}'. Use YYYY-MM-DD.",
},
}), 400
if days is None and date_from is None and date_to is None:
days = 30
if offset < 0:
offset = 0
if limit is not None and limit < 1:
return jsonify({
"success": False,
"error": {
"code": "VALIDATION_ERROR",
"message": "limit must be a positive integer",
},
}), 400
rows, total = _query_history(
date_from=date_from,
date_to=date_to,
days=days,
limit=limit,
offset=offset,
)
payload = {
"success": True,
"site_name": SITE_NAME,
"filters": {
"days": days,
"date_from": date_from,
"date_to": date_to,
},
"count": len(rows),
"data": rows,
}
if limit is not None:
payload["pagination"] = {
"offset": offset,
"limit": limit,
"total": total,
"has_next": offset + limit < total,
"has_prev": offset > 0,
}
else:
payload["total"] = total
return jsonify(payload)
except sqlite3.OperationalError as e:
return jsonify({
"success": False,
"error": {
"code": "DATABASE_UNAVAILABLE",
"message": f"Database unavailable at {DB_PATH}: {e}",
},
"data": [],
"count": 0,
"total": 0,
}), 200
except Exception as e:
return jsonify({
"success": False,
"error": {
"code": "INTERNAL_ERROR",
"message": str(e),
},
}), 500
@app.route("/api/history/<counting_date>")
def api_history_day(counting_date):
"""Single counting-day record by YYYY-MM-DD."""
try:
datetime.strptime(counting_date, "%Y-%m-%d")
except ValueError:
return jsonify({
"success": False,
"error": {
"code": "VALIDATION_ERROR",
"message": f"Invalid counting_date '{counting_date}'. Use YYYY-MM-DD.",
},
}), 400
try:
rows, _ = _query_history(date_from=counting_date, date_to=counting_date)
if not rows:
return jsonify({
"success": False,
"error": {
"code": "NOT_FOUND",
"message": f"No history found for counting date {counting_date}",
},
}), 404
return jsonify({
"success": True,
"site_name": SITE_NAME,
"data": rows[0] if len(rows) == 1 else rows,
})
except sqlite3.OperationalError as e:
return jsonify({
"success": False,
"error": {
"code": "DATABASE_UNAVAILABLE",
"message": f"Database unavailable at {DB_PATH}: {e}",
},
}), 503
except Exception as e:
return jsonify({
"success": False,
"error": {
"code": "INTERNAL_ERROR",
"message": str(e),
},
}), 500
@app.route("/api/summary")
def api_summary():
try: