FleetOps — Tickets → INC operations dashboard

Implementation plan · erase existing INC/CRQ view, rebuild INC first · endpoint-first, dashboard-cards layout

Context

The FleetOps SPA's Tickets tab is currently a full-bleed MapLibre map showing combined INC (red) + CRQ (blue) ticket circles plus live FleetNow vehicles, fed by the legacy GET /webhook/tickets (→ reporting.fn_tickets_for_map). Meanwhile, the 16_fleettickets repo has designed and documented a richer INC operations dashboard (Phase 2): an open-ticket layer + windowed closed overlay + derived SLA states + ticket metric cards, served by a new reporting.fn_inc_dashboard(...) function and exposed at GET /webhook/inc-dashboard.

We are overhauling the SPA to that documented design. Per the user: erase the existing INC + CRQ ticket view and rebuild INC first (CRQ deferred). INC is fully documented; CRQ reuses the same machinery later.

Key blocker found: GET /webhook/inc-dashboard currently 404s — the DB function lives in 16_fleettickets/migrations/09_inc_dashboard_fn.sql but the HTTP wrapper is not in the dashboard_api service. The legacy /webhook/tickets returns 200 with live INC+CRQ data (INC ingest is live: 21,301 records, freshness current).

Decisions (confirmed with user):

Reference docs (source of truth)


Phase A API endpoint — repo tracksolid_timescale_grafana_prod

File: ~/Downloads/projects/tracksolid_timescale_grafana_prod/dashboard_api_rev.py. Deployed by scp + ssh to the remote host; the staging instance (fleetapi.fivetitude.com) runs read-only as dashboard_ro. These steps touch a live server and may need the user to run the scp/ssh deploy via ! <cmd>.

A1. Verify / apply the DB function

A2. Add the /webhook/inc-dashboard handler

Mirror the existing tickets() handler (dashboard_api_rev.py:275-304): one passthrough SQL call, JSON body returned unchanged. Reuse get_conn, _clean.

from fastapi import FastAPI, Request, Query   # add Query to existing import (line 46)

@app.get("/webhook/inc-dashboard")
def inc_dashboard(
    cluster: str | None = None,
    status:  str | None = None,
    window:  str = "today",
    from_:   str | None = Query(None, alias="from"),   # 'from' is reserved
    to:      str | None = None,
):
    # Validation per the contract:
    #  - window not in {today,week,month,custom}        -> 400
    #  - window == 'custom' with neither from nor to    -> 400
    #  - from/to unparseable, or from >= to             -> 400
    # If either from/to is present, the SQL treats it as custom (window overridden).
    try:
        with get_conn() as conn, conn.cursor() as cur:
            cur.execute(
                "SELECT reporting.fn_inc_dashboard(%s, %s, %s, %s, %s)",
                (_clean(cluster), _clean(status), window,
                 _clean(from_), _clean(to)),
            )
            payload = cur.fetchone()[0] or {}
        return JSONResponse(payload)          # passthrough, unchanged
    except Exception:
        log.exception("inc-dashboard failed")
        return JSONResponse({"error": {"type": "unknown",
            "message": "INC dashboard is unavailable. Try again in a few seconds."}})

A3. Deploy + verify


Phase B SPA overhaul — 15_fleetops/src/index.html (single file)

B1. Erase the existing INC/CRQ view

Remove from src/index.html:

B2. Keep + reuse (do NOT reinvent)

The vehicle overlay machinery stays — the contract says the SPA overlays FleetNow:

B3. New markup — #view-tickets (dashboard cards + map)

B4. New JS — INC data + map


Verification (end-to-end)

  1. API (Phase A): curl matrix above against fleetapi.fivetitude.com — shapes, filters, 400s. Compare metrics.open_now to SELECT count(*) FROM tickets.inc WHERE is_actionable (and inc_open_sla SLA distribution).
  2. SPA (Phase B): serve src/ locally (python3 -m http.server in src/, or the Caddy Docker image) with API_BASE=https://fleetapi.fivetitude.com. Open the Tickets tab and confirm:
    • Metric cards + header KPIs populate; by-status / by-cluster tables match metrics.
    • Map shows SLA-colored open INC + live vehicles; toggling Closed INC overlays the windowed closed set; SLA legend correct.
    • Changing Cluster / Status / Window + Apply refetches and updates cards, tables, and both layers; custom range shows date inputs and bounds the closed overlay.
    • Hover popups show the documented fields (open vs closed).
    • No console calls to /webhook/tickets; only /webhook/inc-dashboard + /webhook/live-positions.

Out of scope (future)

Generated as the implementation plan for the FleetOps Tickets → INC overhaul.