Commit graph

6 commits

Author SHA1 Message Date
david kiania
6cf0905b31 feat(dashboard_api): FleetOps analytics endpoints + fuel view (Phase 3)
Adds the read-only /analytics/* surface the FleetOps SPA will consume, plus
the migration that backs the fuel roll-up. All endpoints SELECT the indexed
reporting.* / tracksolid.v_* views and never write, so the forthcoming staging
instance can serve them against the prod DB as grafana_ro.

dashboard_api_rev.py:
  - GET /analytics/fleet-summary     per-vehicle + per-cost-centre roll-up
  - GET /analytics/utilisation       per-vehicle utilisation + daily fleet trend
  - GET /analytics/driver-behaviour  per-driver speeding / harsh index
  - GET /analytics/fuel              actual vs estimated litres (data-gated flags)
  - GET /analytics/filters           dropdown options (alias of GET /webhook/fleet-dashboard)
  - responses run through jsonable_encoder (Decimal->float, date->ISO)
  - VTRIPS_REFRESH_INTERVAL_S<=0 now DISABLES the v_trips refresher, so a
    read-only staging instance never attempts REFRESH (prod still owns it).

migrations/17_fleetops_fuel_view.sql:
  - reporting.v_fuel_daily encapsulates the v_trips->devices join (so the
    read-only role needs SELECT only on the view) and grants it to grafana_ro.

Registered 17 in run_migrations.py. Note: live migration head is 16, not 13
as CLAUDE.md implies. Endpoints are unit-compilable but untested live until
the staging bridge (Phase 1) exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 12:12:00 +03:00
kianiadee
d95e5c2dbd config(cors): allow fleetnow.rahamafresh.com origin on dashboard_api
The merged FleetNow dashboard (separate repo, Coolify) reads this read-API, so
its origin must be in DASHBOARD_CORS_ORIGINS. Added to the code default; live
config is set via the env in ~/deploy_dashboard_api.sh on the host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 10:07:46 +03:00
david kiania
30b351576c feat(api): self-refresh reporting.v_trips in dashboard_api
Some checks are pending
Static Analysis / static (push) Waiting to run
Tests / test (push) Waiting to run
The Fleet Trips dashboard reads reporting.v_trips (a materialized view).
Its refresh was a scheduled n8n workflow; when n8n was retired the matview
froze (last refresh 2026-06-01) so the dashboard showed no recent trips
even though tracksolid.trips kept ingesting live.

Move the refresh into the owned stack: a background loop in dashboard_api
runs REFRESH MATERIALIZED VIEW CONCURRENTLY reporting.v_trips every
VTRIPS_REFRESH_INTERVAL_S (default 300s). Safe across uvicorn --workers
via a pg advisory lock (one worker refreshes per tick); runs in a thread
so the ~9s refresh never blocks the event loop; logs to
reporting.refresh_log (source='dashboard_api') for continuity. Uses a
dedicated autocommit connection because REFRESH ... CONCURRENTLY cannot
run inside a transaction block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:25:45 +03:00
david kiania
f1387d1476 fix(api): parse form-urlencoded POST body in fleet-dashboard handler
Some checks are pending
Static Analysis / static (push) Waiting to run
Tests / test (push) Waiting to run
The Fleet Trips SPA posts application/x-www-form-urlencoded, but the
POST /webhook/fleet-dashboard handler read the body with request.json().
That threw on every request, the except swallowed it to body={}, and all
filters (vehicle_numbers, cost_centre, assigned_city) plus period/dates
were dropped — so every query returned the full unfiltered fleet (1,266
trips) regardless of the dropdowns. The map/KPIs/trips never changed,
which read as "the dropdowns don't work."

Parse by Content-Type: urllib.parse.parse_qs for form bodies (no new
dependency — avoids python-multipart), JSON still accepted defensively
for n8n-compat callers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 13:23:10 +03:00
david kiania
831f683b83 fix(api): expose /webhook/live-positions/track so map trail matches SPA path
Some checks are pending
Static Analysis / static (push) Waiting to run
Tests / test (push) Waiting to run
The Live Positions SPA calls GET /webhook/live-positions/track, but the
read-API only exposed /webhook/vehicle-track. Clicking a vehicle to view its
1-hour trail therefore 404'd even after repointing N8N_BASE. Register the SPA's
actual path as a route alias to the same handler (vehicle-track kept as alias),
so the only frontend change remains the base URL. Docstring updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:54:58 +03:00
david kiania
5703d70aa6 feat(api): dedicated FastAPI read-API for map dashboards (replaces n8n)
Some checks failed
Static Analysis / static (push) Has been cancelled
Tests / test (push) Has been cancelled
n8n was a thin HTTP->SQL proxy for the Live Position and Fleet Trips maps and
proved fragile (credential reloads, :latest drift, shared connection limits).
This service calls the same proven reporting.* functions directly, reusing the
existing psycopg2 pool / Docker image / Coolify deploy.

Endpoints mirror the n8n webhook paths so the only frontend change is N8N_BASE:
  GET  /webhook/live-positions  -> {summary, geojson}   (fn_live_positions)
  GET  /webhook/vehicle-track   -> GeoJSON Feature       (fn_vehicle_track)
  GET  /webhook/fleet-dashboard -> filter options
  POST /webhook/fleet-dashboard -> trips payload         (fn_trips_for_map)

Response shapes replicate the n8n "Build response JSON" nodes exactly; empty
filters/sentinels ('', null, undefined) normalize to SQL wildcards. CORS limited
to the dashboard origins. Added dashboard_api service to docker-compose (port
8890, Coolify-routed). SQL contracts validated against prod.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 04:23:37 +03:00