GET /webhook/inc-dashboard wrapper spec: query params (cluster/status/window/from/to) -> SQL passthrough, full response schema, field semantics (open=live vs closed=window, mttr minutes, derived vs source SLA, map/metrics geocoding gap), examples, caching/auth. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.8 KiB
6.8 KiB
Handoff — dashboard_api endpoint contract: INC operations dashboard
For the dashboard_api repo (tracksolid stack). This endpoint is a thin wrapper
over reporting.fn_inc_dashboard(...) in tracksolid_db (built by fleettickets,
migration 09). It powers the FleetOps live INC map (open tickets + windowed
closed overlay + metric cards). Vehicle positions/routes come from FleetNow and
are overlaid by the SPA — not part of this endpoint.
Endpoint
GET /webhook/inc-dashboard
Mirror the existing GET /webhook/tickets (→ reporting.fn_tickets_for_map) for
auth, connection (role dashboard_ro), and JSON passthrough conventions.
Query parameters
| Param | Type | Required | Default | Maps to | Notes |
|---|---|---|---|---|---|
cluster |
string | no | — (all) | p_cluster |
Exact match on tickets.inc.cluster (UPPERCASE, e.g. MUIGAI INN). Empty = no filter. |
status |
string | no | — (all) | p_status |
Exact normalized_status (UPPERCASE, e.g. ACCEPTED, CLOSED COMPLETE). |
window |
enum | no | today |
p_window |
One of today | week | month | custom. Calendar EAT (week = ISO Mon-start). |
from |
ISO-8601 timestamp | only if window=custom |
— | p_from |
Inclusive start. Send with offset/Z (absolute time). |
to |
ISO-8601 timestamp | only if window=custom |
— | p_to |
Exclusive end. |
Resolution rules (match the SQL):
- If either
fromortois present, the function treats it as a custom window (thewindowvalue is overridden tocustom); a missing side becomes ±infinity. So for a bounded custom range, send bothfromandto. - Otherwise the preset (
today/week/month) is resolved to EAT calendar bounds inside the function — the API does not compute dates.
Validation (API side):
window∉ {today,week,month,custom} →400.window=customwith neitherfromnorto→400(would mean "all time").from/tounpar, orfrom >= to→400.cluster/statusare passed through verbatim (unknown values simply return empty sets — not an error).
Implementation (one call, passthrough)
SELECT reporting.fn_inc_dashboard(
p_cluster := $1, -- text | null
p_status := $2, -- text | null
p_window := $3, -- text (default 'today')
p_from := $4, -- timestamptz | null
p_to := $5 -- timestamptz | null
);
Return the resulting jsonb as the HTTP body unchanged (Content-Type: application/json). No reshaping needed.
Response (200) — shape
{
"window": { "from": "2026-06-16T00:00:00+03:00", "to": "...", "preset": "today" },
"open": { // ALL currently-open tickets (live; NOT time-filtered)
"type": "FeatureCollection",
"features": [
{ "type": "Feature",
"geometry": { "type": "Point", "coordinates": [lng, lat] },
"properties": {
"ticket_id": "WOT…", "normalized_status": "ACCEPTED",
"cluster": "MUIGAI INN", "region": "nairobi", "location_name": "…",
"assigned_team": "…", "owner": "…", "geo_source": "location|cluster|feed",
"sla_state": "breached|at_risk|ok|unknown", "hours_open": 107.7
} }
]
},
"closed": { // closed_at within the selected window
"type": "FeatureCollection",
"features": [
{ "type": "Feature",
"geometry": { "type": "Point", "coordinates": [lng, lat] },
"properties": {
"ticket_id": "WOT…", "normalized_status": "CLOSED COMPLETE",
"cluster": "…", "region": "…", "location_name": "…",
"assigned_team": "…", "owner": "…", "geo_source": "…",
"closed_at": "2026-06-15T13:04:52+00:00", "mttr": 1467, "sla_status": "Compliant|Breached"
} }
]
},
"metrics": {
"open_now": 30,
"closed_in_window": 2605,
"sla": {
"open": { "breached": 30, "at_risk": 0, "ok": 0, "unknown": 0 },
"closed": { "compliant": 1718, "breached": 887 }
},
"by_status": { "ACCEPTED": 20, "PENDING DISPATCH": 7, "CLOSED COMPLETE": 2400, … },
"by_cluster": { "MUIGAI INN": 82, "JUJA": …, "(none)": 1, … },
"closure_rate": { "per_day_avg": 86.83, "series": [ { "day": "2026-06-15", "count": 13 }, … ] },
"avg_mttr_min": 1467.1
},
"freshness": { "inc": { "export_type": "full", "exported_at": "…", "records_ingested": 21301, "ingested_at": "…" } }
}
Field semantics the SPA must know
openis always the full open set (matching cluster/status); the time window does not filter it.closedis only the overlay for the window.mttris in MINUTES (null until closed).hours_openis hours.sla_state(open) is derived (now − created_at_service, falling back tofirst_seen_at);unknown= no creation clock.sla_status(closed) is the source field (Compliant/Breached).- Map vs metrics gap:
featuresinclude only geocoded rows (geompresent); metric counts include all matching rows (a few tickets have nogeom, e.g. null cluster). Soopen.features.lengthmay be< metrics.open_now. geo_sourceindicates precision:location(street-level) >cluster(centroid; many tickets share a point) >feed> none.- Coordinates are GeoJSON
[lng, lat](x, y) order. window.from/toare absolute timestamps; presets are EAT calendar ranges.
Examples
# today (default)
curl '.../webhook/inc-dashboard'
# this month, one cluster
curl '.../webhook/inc-dashboard?window=month&cluster=MUIGAI%20INN'
# open ACCEPTED tickets (today window irrelevant to the open layer)
curl '.../webhook/inc-dashboard?status=ACCEPTED'
# custom range (send BOTH from & to; absolute times)
curl '.../webhook/inc-dashboard?from=2026-06-09T00:00:00%2B03:00&to=2026-06-16T00:00:00%2B03:00'
Operational notes
- Caching: the open layer + freshness change at most hourly (the ingest
cadence); closed/metrics change only when filters change. A short cache
(≈60 s) keyed on the full query string is safe; or
Cache-Control: no-storeif you prefer always-fresh — the function is cheap (indexed). - Performance: indexed (
ix_inc_closed_at,ix_inc_cluster_col,ix_inc_norm_status_col,ix_inc_geom); month-window payloads are a few thousand closed features — fine for one request. If payloads get large, consider ametrics-onlyflag later (not in scope now). - Auth: same as
/webhook/tickets. The function is granted todashboard_ro. - Timezone: all preset math is EAT inside the DB; the API passes
from/tothrough as absolutetimestamptz.
Out of scope (other work)
- FleetNow vehicle positions/routes overlay — SPA + FleetNow.
- Open-backlog-over-time / observed transitions — needs the
fleetticketshistory capture (not built yet); this endpoint reports current state + closure events.