feat(T5/dashboard): import DBF idempotent + nomenclator browser + audit CSV + stare RAR
T5 (tools/import_dbf.py): citire prestatii_rar.DBF / mapare_prestatii.DBF cu dbfread, raport dry-run (randuri valide/duplicate/goale, mapari orfane = cod necunoscut in nomenclator), --commit cu upsert idempotent in tranzactie. Dashboard: browser nomenclator, indicator stare RAR (indisponibil? derivat din ultimul login < 30h, coada arata ultima stare locala), export audit CSV (/v1/audit/export?status=sent|all&date_from&date_to, b64Image exclus, coloana purge_after pentru retentia 90z). Verify: 11 teste noi (test_import_dbf 6, test_dashboard 5), suita 111 pass, dry-run real pe DBF-urile din repo + smoke live dashboard/CSV. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,25 @@ def _worker_alive(hb) -> bool:
|
||||
return age <= get_settings().worker_heartbeat_stale_s
|
||||
|
||||
|
||||
def _rar_state(hb, worker_alive: bool) -> str:
|
||||
"""Eticheta de disponibilitate RAR, derivata din ultimul login reusit.
|
||||
|
||||
Nu interogam RAR live aici (dashboard-ul degradeaza la ultima stare cunoscuta
|
||||
a cozii). JWT TTL = 30h: un login mai vechi de atat inseamna ca nu mai stim
|
||||
sigur ca RAR raspunde -> "indisponibil?". Fara niciun login -> necunoscut.
|
||||
"""
|
||||
if not worker_alive:
|
||||
return "necunoscut (worker oprit)"
|
||||
last = hb["last_rar_login_ok"] if hb else None
|
||||
if not last:
|
||||
return "fara login reusit inca"
|
||||
try:
|
||||
age = (datetime.now(timezone.utc) - datetime.fromisoformat(last)).total_seconds()
|
||||
except (ValueError, TypeError):
|
||||
return "necunoscut"
|
||||
return "indisponibil?" if age > 108000 else "ok"
|
||||
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
def dashboard(request: Request) -> HTMLResponse:
|
||||
conn = get_connection()
|
||||
@@ -48,20 +67,37 @@ def dashboard(request: Request) -> HTMLResponse:
|
||||
counts = _status_counts(conn)
|
||||
hb = read_heartbeat(conn)
|
||||
blocked = sum(counts.get(s, 0) for s in _BLOCKED)
|
||||
worker_alive = _worker_alive(hb)
|
||||
ctx = {
|
||||
"request": request,
|
||||
"rar_env": get_settings().rar_env,
|
||||
"version": __version__,
|
||||
"counts": counts,
|
||||
"blocked": blocked,
|
||||
"worker_alive": _worker_alive(hb),
|
||||
"worker_alive": worker_alive,
|
||||
"last_login": hb["last_rar_login_ok"] if hb else None,
|
||||
"rar_state": _rar_state(hb, worker_alive),
|
||||
}
|
||||
return templates.TemplateResponse("dashboard.html", ctx)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@router.get("/_fragments/nomenclator", response_class=HTMLResponse)
|
||||
def fragment_nomenclator(request: Request) -> HTMLResponse:
|
||||
"""Browser nomenclator RAR (cache local upsert-at de worker la fiecare login)."""
|
||||
conn = get_connection()
|
||||
try:
|
||||
rows = conn.execute(
|
||||
"SELECT cod_prestatie, nume_prestatie, updated_at FROM nomenclator_rar ORDER BY cod_prestatie"
|
||||
).fetchall()
|
||||
return templates.TemplateResponse(
|
||||
"_nomenclator.html", {"request": request, "rows": rows}
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@router.get("/_fragments/banner", response_class=HTMLResponse)
|
||||
def fragment_banner(request: Request) -> HTMLResponse:
|
||||
conn = get_connection()
|
||||
|
||||
Reference in New Issue
Block a user