106 lines
3.2 KiB
Python
106 lines
3.2 KiB
Python
"""photo-pipeline: Prefect status helper for the dashboard.
|
|
|
|
Queries the Prefect API (via the client) for recent flow runs and their state,
|
|
plus recent review actions from the fingerprint DB.
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import sqlite3
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
os.environ.setdefault("PREFECT_API_URL", "http://localhost:4200/api")
|
|
|
|
BASE = Path(__file__).parent
|
|
DB_PATH = BASE / "photo_pipeline.db"
|
|
|
|
|
|
def _fmt_time(dt):
|
|
if not dt:
|
|
return "—"
|
|
try:
|
|
return dt.astimezone().strftime("%Y-%m-%d %H:%M")
|
|
except Exception:
|
|
return str(dt)[:16]
|
|
|
|
|
|
def get_flow_runs(limit: int = 15) -> list[dict]:
|
|
"""Recent flow runs from Prefect, newest first."""
|
|
from prefect.client.orchestration import get_client
|
|
|
|
async def _fetch():
|
|
async with get_client() as client:
|
|
runs = await client.read_flow_runs(limit=limit)
|
|
# fetch flow names
|
|
flow_names = {}
|
|
for r in runs:
|
|
if r.flow_id not in flow_names:
|
|
try:
|
|
f = await client.read_flow(r.flow_id)
|
|
flow_names[r.flow_id] = f.name
|
|
except Exception:
|
|
flow_names[r.flow_id] = "?"
|
|
out = []
|
|
for r in runs:
|
|
out.append({
|
|
"name": r.name or "?",
|
|
"flow": flow_names.get(r.flow_id, "?"),
|
|
"state": (r.state_type or "?").replace("StateType.", ""),
|
|
"state_name": r.state_name or "",
|
|
"start": _fmt_time(r.start_time),
|
|
"end": _fmt_time(r.end_time),
|
|
"duration": _duration(r.start_time, r.end_time),
|
|
})
|
|
return out
|
|
|
|
try:
|
|
return asyncio.run(_fetch())
|
|
except Exception as e:
|
|
return [{"error": str(e)}]
|
|
|
|
|
|
def _duration(start, end):
|
|
if not start or not end:
|
|
return ""
|
|
try:
|
|
s = (end - start).total_seconds()
|
|
if s < 60:
|
|
return f"{s:.0f}s"
|
|
if s < 3600:
|
|
return f"{s/60:.1f}m"
|
|
return f"{s/3600:.1f}h"
|
|
except Exception:
|
|
return ""
|
|
|
|
|
|
def get_recent_actions(limit: int = 20) -> list[dict]:
|
|
"""Recent review decisions from the DB (by reviewed_at timestamp)."""
|
|
try:
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.row_factory = sqlite3.Row
|
|
rows = conn.execute(
|
|
"SELECT sha256, path, status, source, reviewed_at FROM image_hashes "
|
|
"WHERE reviewed_at IS NOT NULL "
|
|
"ORDER BY reviewed_at DESC LIMIT ?", (limit,)
|
|
).fetchall()
|
|
conn.close()
|
|
return [{
|
|
"name": Path(r["path"]).name,
|
|
"status": r["status"],
|
|
"source": r["source"],
|
|
"when": r["reviewed_at"] or "—",
|
|
} for r in rows]
|
|
except Exception as e:
|
|
return [{"error": str(e)}]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import json
|
|
print("=== flow runs ===")
|
|
for r in get_flow_runs(6):
|
|
print(f" {r.get('flow','?'):25s} {r.get('state','?'):12s} {r.get('start','')}")
|
|
print("=== recent actions ===")
|
|
for a in get_recent_actions(6):
|
|
print(f" {a.get('status','?'):10s} {a.get('name','?')}")
|