Staging environment + FleetOps split #17

Open
kianiadee wants to merge 23 commits from feat/staging-fleetops-architecture into main
Showing only changes of commit 1d4bd6000e - Show all commits

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 = """