feat(dashboard_api): wire GET /webhook/inc-dashboard route
Some checks failed
Static Analysis / static (push) Waiting to run
Tests / test (push) Waiting to run
Static Analysis / static (pull_request) Has been cancelled
Tests / test (pull_request) Has been cancelled

Thin passthrough over reporting.fn_inc_dashboard (fleettickets migration 09),
powering the FleetOps live INC map (open + windowed-closed + metrics). Mirrors
/webhook/tickets for auth/connection and returns the function's jsonb unchanged.

Params: cluster, status, window (today|week|month|custom), from/to (custom).
Validates window enum, custom-requires-from/to, ISO-8601 from/to, and from<to
(400 on failure). Matches docs/dashboard-api-contract.md from the fleettickets repo.

Verified end-to-end against the prod DB (read-only): default/week/cluster
filters return correct payloads; bad window and custom-without-bounds → 400.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
david kiania 2026-06-18 15:06:52 +03:00
parent 024b698bca
commit 1d4bd6000e

View file

@ -43,7 +43,7 @@ from urllib.parse import parse_qs
import psycopg2
import psycopg2.extras
from fastapi import FastAPI, Request
from fastapi import FastAPI, Query, Request
from fastapi.encoders import jsonable_encoder
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
@ -304,6 +304,70 @@ def tickets(
)
# ── INC operations dashboard ──────────────────────────────────────────────────
# Thin passthrough over reporting.fn_inc_dashboard (fleettickets migration 09):
# returns { window, open: GeoJSON, closed: GeoJSON, metrics, freshness } for the
# FleetOps live INC map. open = all currently-open INC tickets (not time-filtered);
# closed = closures within the selected window; metrics react to the selection.
# Vehicle positions/routes are overlaid by the SPA (FleetNow), not this endpoint.
_INC_WINDOWS = {"today", "week", "month", "custom"}
def _bad_request(msg):
return JSONResponse({"error": {"type": "bad_request", "message": msg}}, status_code=400)
@app.get("/webhook/inc-dashboard")
def inc_dashboard(
cluster: str | None = None, # exact tickets.inc.cluster (UPPERCASE), blank = all
status: str | None = None, # exact normalized_status, blank = all
window: str = "today", # today | week | month | custom (calendar EAT)
from_: str | None = Query(None, alias="from"), # custom start (inclusive), ISO-8601
to: str | None = None, # custom end (exclusive), ISO-8601
):
# Validation (mirrors the contract). The SQL treats any from/to as a custom
# window; presets resolve to EAT calendar bounds inside the function.
if window not in _INC_WINDOWS:
return _bad_request("window must be one of today|week|month|custom")
f, t = _clean(from_), _clean(to)
if window == "custom" and not f and not t:
return _bad_request("custom window requires from and/or to")
def _parse(v):
if not v:
return None
try:
return datetime.fromisoformat(v)
except ValueError:
return False
pf, pt = _parse(f), _parse(t)
if pf is False or pt is False:
return _bad_request("from/to must be ISO-8601 timestamps with an offset/Z")
if pf and pt and pf >= pt:
return _bad_request("from must be earlier than to")
try:
with get_conn() as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT reporting.fn_inc_dashboard(%s, %s, %s, %s, %s)",
(_clean(cluster), _clean(status), window, f, t),
)
payload = cur.fetchone()[0] or {}
return JSONResponse(payload) # jsonb body returned unchanged
except Exception:
log.exception("inc-dashboard failed")
return JSONResponse(
{
"error": {
"type": "unknown",
"message": "INC dashboard is unavailable. Try again in a few seconds.",
}
}
)
# ── Fleet trips (#002) ───────────────────────────────────────────────────────
_FILTER_OPTIONS_SQL = """