tracksolid_timescale_grafan.../docs/PLATFORM_OVERVIEW.html

94 lines
117 KiB
HTML
Raw Normal View History

<!doctype html><html lang=en><head><meta charset=utf-8>
<meta name=viewport content="width=device-width, initial-scale=1">
<title>Fireside · Tracksolid Fleet Intelligence — Platform Overview</title>
<style>
:root{--bg:#0d1117;--panel:#161b22;--bd:#30363d;--fg:#e6edf3;--mut:#9da7b3;--acc:#3b82f6;--grn:#3fb950;--amb:#d29922;--mono:'SF Mono',ui-monospace,Menlo,Consolas,monospace}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.6 -apple-system,Segoe UI,Roboto,sans-serif}
.wrap{max-width:1100px;margin:0 auto;padding:32px 22px 80px}
h1{font-size:30px;margin:0 0 4px} h2{font-size:22px;margin:42px 0 12px;padding-bottom:6px;border-bottom:1px solid var(--bd)}
h3{font-size:17px;margin:26px 0 8px;color:#c9d4e0}
.sub{color:var(--mut)} a{color:var(--acc);text-decoration:none}
code,.mono{font-family:var(--mono);font-size:13px}
.flow{display:flex;flex-wrap:wrap;gap:10px;align-items:stretch;margin:16px 0}
.node{background:var(--panel);border:1px solid var(--bd);border-radius:10px;padding:12px 14px;min-width:150px;flex:1}
.node b{display:block;color:#fff} .node small{color:var(--mut)}
.arrow{display:flex;align-items:center;color:var(--acc);font-weight:700;font-size:20px}
table{width:100%;border-collapse:collapse;margin:8px 0 18px;background:var(--panel);border:1px solid var(--bd);border-radius:8px;overflow:hidden}
th,td{text-align:left;padding:8px 11px;border-bottom:1px solid var(--bd);vertical-align:top;font-size:13.5px}
th{background:#1c232c;color:#c9d4e0;font-weight:600}
tr:last-child td{border-bottom:none}
table.cols{margin:6px 0 4px;background:#10151c}
details{background:var(--panel);border:1px solid var(--bd);border-radius:8px;margin:7px 0;padding:4px 12px}
summary{cursor:pointer;padding:7px 0;font-weight:600}
summary .pill{font-weight:400}
.pill{display:inline-block;font-size:11px;padding:1px 7px;border-radius:20px;border:1px solid var(--bd);color:var(--mut);margin-left:6px}
.pill.ht{color:var(--amb);border-color:#5a4a1f} .pill.mv{color:var(--acc);border-color:#244a7a} .pill.fn{color:var(--grn);border-color:#27512f}
.k{color:var(--mut)} .badge{color:var(--grn);font-weight:600}
.note{background:#13202b;border-left:3px solid var(--acc);padding:10px 14px;border-radius:6px;margin:12px 0}
.warn{background:#241d10;border-left:3px solid var(--amb)}
.toc{columns:2;gap:30px} .toc a{display:block;padding:2px 0}
.muted{color:var(--mut);font-size:13px}
</style></head><body><div class=wrap>
<h1>Fireside Communications — Tracksolid Fleet Intelligence</h1>
<p class=sub>Platform reference · generated 2026-06-05 09:46 UTC from the live database (<code>tracksolid_db</code> @ twala.rahamafresh.com:5433)</p>
<div class="note"><b>What this document is.</b> A current-state reference for the fleet telematics platform after the
map dashboards were moved off n8n onto the dedicated <code>dashboard_api</code> service (<code>fleetapi.rahamafresh.com</code>).
Covers data flow, deployment, the read-API, and the full database schema (tables, views, functions).</div><h2 id=arch>1 · Architecture &amp; data flow</h2><div class=flow>
<div class=node><b>Tracksolid Pro / Jimi IoT API</b><small>eu-open.tracksolidpro.com — GPS, trips, alarms, OBD</small></div>
<div class=arrow></div>
<div class=node><b>Ingestion (Python)</b><small>ingest_movement · ingest_events · webhook_receiver</small></div>
<div class=arrow></div>
<div class=node><b>TimescaleDB + PostGIS</b><small>schema <code>tracksolid</code> — single source of truth</small></div>
</div>
<div class=flow>
<div class=node><b>reporting.* read layer</b><small>v_live_positions · v_trips (matview) · fn_* functions</small></div>
<div class=arrow></div>
<div class=node><b>dashboard_api (FastAPI)</b><small>https://fleetapi.rahamafresh.com :8890</small></div>
<div class=arrow></div>
<div class=node><b>Map SPAs (rustfs/S3)</b><small>liveposition · fleetintelligence</small></div>
</div>
<p class=muted>Grafana reads the same TimescaleDB directly via the <code>tracksolid.v_*</code> views (NOC + daily-ops dashboards).</p><h2 id=mig>2 · The n8n → fleetapi migration</h2><p>The two map dashboards previously fetched data from n8n webhooks at
<code class=k>https://automate.rahamafresh.com/webhook/&hellip;</code>. n8n was only a thin HTTP→SQL proxy and a fragile link in the
live-data path. It has been replaced by <b>dashboard_api</b>, a small FastAPI service that calls the proven
<code>reporting.*</code> functions directly. The SPAs now point their <code>N8N_BASE</code> constant at
<code class=badge>https://fleetapi.rahamafresh.com</code>; all webhook <i>paths</i> are unchanged, so the migration was a one-line
front-end change per SPA plus the new service.</p>
<table><thead><tr><th>Aspect</th><th>Before (n8n)</th><th>After (fleetapi)</th></tr></thead><tbody>
<tr><td>SPA base URL</td><td class=mono>automate.rahamafresh.com</td><td class=mono>fleetapi.rahamafresh.com</td></tr>
<tr><td>Live Positions</td><td>/webhook/live-positions</td><td>same path → reporting.fn_live_positions</td></tr>
<tr><td>Vehicle trail</td><td>/webhook/live-positions/track</td><td>same path (route alias added) → fn_vehicle_track</td></tr>
<tr><td>Fleet trips</td><td>/webhook/fleet-dashboard (GET+POST)</td><td>same paths → fn_trips_for_map</td></tr>
<tr><td>Transport</td><td>n8n workflow + credentials</td><td>FastAPI + psycopg2 pool (shared with ingestion)</td></tr>
</tbody></table><h2 id=deploy>3 · Deployment topology</h2><p>Host <code>twala.rahamafresh.com</code> (31.97.44.246, Hostinger VPS) running <b>Coolify 4.1</b>. The Tracksolid stack
is a docker-compose application (container suffix <code>bo3nov2ija7g8wn9b1g2paxs</code>); the reverse proxy is
<b>Traefik</b> on the <code>coolify</code> network with Let's Encrypt TLS.</p>
<table><thead><tr><th>Service</th><th>Role</th><th>Port / domain</th></tr></thead><tbody>
<tr><td class=mono>timescale_db</td><td>PostgreSQL 16 + TimescaleDB 2.15 + PostGIS 3</td><td>5432 internal · 5433 host</td></tr>
<tr><td class=mono>ingest_movement</td><td>GPS positions, trips, parking, track-list, device sync</td><td></td></tr>
<tr><td class=mono>ingest_events</td><td>Alarm event polling</td><td></td></tr>
<tr><td class=mono>webhook_receiver</td><td>Push receiver (/pushobd /pushevent /pushtripreport …)</td><td>8888 (Traefik)</td></tr>
<tr><td class=mono>dashboard_api</td><td><b>Map read-API</b> (replaces n8n)</td><td>8890 → <span class=badge>fleetapi.rahamafresh.com</span></td></tr>
<tr><td class=mono>grafana</td><td>NOC + daily-ops dashboards</td><td>3000 → grafana.rahamafresh.com</td></tr>
<tr><td class=mono>pgbouncer</td><td>Connection pooler (SCRAM via public.user_lookup)</td><td>6432 internal</td></tr>
<tr><td class=mono>db_backup</td><td>pg_dump → rustfs S3 (bucket fleet-db), scheduled</td><td></td></tr>
</tbody></table>
<p>The two map front-ends are single-file SPAs stored as <code>index.html</code> objects in <b>rustfs (S3)</b> at
<code>s3.rahamafresh.com</code> (buckets <code>liveposition</code>, <code>fleetintelligence</code>), each served by a small nginx
proxy container. The dashboard_api enforces CORS for both SPA origins.</p>
<div class="note warn"><b>Operational note.</b> dashboard_api currently runs as a standalone Traefik-labelled container reusing the
app image with <code>dashboard_api_rev.py</code> bind-mounted. The durable form is the compose <code>dashboard_api</code> service
(already in <code>docker-compose.yaml</code>) once the branch is merged and the <code>fleetapi</code> domain is set in Coolify.</div><h2 id=api>4 · Read-API reference (dashboard_api)</h2><p class=muted>Base: <code>https://fleetapi.rahamafresh.com</code> · all responses JSON · CORS limited to the two SPA origins.</p><table><thead><tr><th>Method</th><th>Path</th><th>Params</th><th>Returns</th><th>Notes</th></tr></thead><tbody><tr><td class=mono>GET</td><td class=mono>/health</td><td class=mono></td><td>{status: ok}</td><td>Liveness probe.</td></tr><tr><td class=mono>GET</td><td class=mono>/webhook/live-positions</td><td class=mono>cost_centre?, acc_status?</td><td>{summary, geojson}</td><td>Live Positions map feed → reporting.fn_live_positions. ~80 vehicle point features.</td></tr><tr><td class=mono>GET</td><td class=mono>/webhook/live-positions/track</td><td class=mono>vehicle_number, hours(1-24)</td><td>GeoJSON Feature (LineString)</td><td>One vehicle&#x27;s recent trail. Alias: /webhook/vehicle-track.</td></tr><tr><td class=mono>GET</td><td class=mono>/webhook/vehicle-track</td><td class=mono>vehicle_number, hours</td><td>GeoJSON Feature</td><td>Alias of live-positions/track (kept for compatibility).</td></tr><tr><td class=mono>GET</td><td class=mono>/webhook/fleet-dashboard</td><td class=mono></td><td>{drivers, cost_centres, cities, vehicles}</td><td>Filter options for the Fleet Intelligence UI.</td></tr><tr><td class=mono>POST</td><td class=mono>/webhook/fleet-dashboard</td><td class=mono>JSON: period|start_date|end_date, vehicle_numbers, driver, cost_centre, assigned_city</td><td>trips GeoJSON payload</td><td>Trips for the map → reporting.fn_trips_for_map. period preset: today|7d|30d|custom.</td></tr></tbody></table><h2 id=db>5 · Database schema</h2><h3 id=s_tracksolid>tracksolid <span class=muted>· Live data — the single source of truth. Ingested from the Tracksolid API.</span></h3><p class=muted>18 table(s) · 17 view(s) · 1 function(s) documented.</p><details><summary><span class=mono>tracksolid.alarms</span><span class=pill>table</span><span class=pill>344,356 rows</span></summary><p>Alarm events (alarm_type, alarm_name, alarm_time).</p><table class="cols"><thead><tr><th>Column</th><th>Type</th><th>Null</th><th>Default</th><th>Comment</th></tr></thead><tbody><tr><td class=mono>id</td><td class=mono>bigint</td><td>NOT NULL</td><td class=mono>nextval(&#x27;alarms_id_seq&#x27;::regclass)</td><td></td></tr><tr><td class=mono>imei</td><td class=mono>text</td><td></td><td class=mono></td><td></td></tr><tr><td class=mono>alarm_type</td><td class=mono>text</td><td></td><td class=mono></td><td></td></tr><tr><td class=mono>alarm_time</td><td class=mono>timestamp with time zone</td><td></td><td class=mono></td><td></td></tr><tr><td class=mono>geom</td><td class=mono>geometry(Point,4326)</td><td></td><td class=mono></td><td></td></tr><tr><td class=mono>lat</td><td class=mono>double precision</td><td></td><td class=mono></td><td></td></tr><tr><td class=mono>lng</td><td class=mono>double precision</td><td></td><td class=mono></td><td></td></tr><tr><td class=mono>speed</td><td class=mono>numeric(7,2)</td><td></td><td class=mono></td><td></td></tr><tr><td class=mono>acc_status</td><td class=mono>text</td><td></td><td class=mono></td><td></td></tr><tr><td class=mono>updated_at</td><td class=mono>timestamp with time zone</td><td></td><td class=mono>now()</td><td></td></tr><tr><td class=mono>alarm_name</td><td class=mono>text</td><td></td><td class=mono></td><td></td></tr><tr><td class=mono>source</td><td class=mono>text</td><td></td><td class=mono>&#x27;poll&#x27;::text</td><td></td></tr><tr><td class=mono>severity</td><td class=mono>text</td><td></td><td class=mono></td><td>Alarm severity level: critical | warning | info</td></tr><tr><td class=mono>geofence_id</td><td class=mono>text</td><td></td><td class=mono></td><td>Tracksolid geofence ID if this is a geofence alarm</td></tr><tr><td class=mono>geofence_name</td><td class=mono>text</td><td></td><td class=mono></td><td>Human-readable geofence name</td></tr><t
<li><b>Hypertables</b> (<code>position_history</code>, <code>heartbeats</code>, <code>fuel_readings</code>,
<code>temperature_readings</code>) report 0 in <code>pg_stat_user_tables</code> — counts above use TimescaleDB's
<code>approximate_row_count()</code>.</li>
<li><b>API distance fields are metres</b>, not km (divide by 1000) — see FIX-M16.</li>
<li><b>reporting.v_trips</b> is a materialized view kept current by an external refresh job (role
<code>reporting_refresher</code>); it is captured in <code>migrations/11_reporting_schema.sql</code> but the refresh schedule is
infrastructure, not schema.</li>
<li><b>Migrations</b> apply at container startup via <code>run_migrations.py</code> (idempotent, tracked in
<code>tracksolid.schema_migrations</code>). Latest: <code>11_reporting_schema.sql</code>.</li>
<li><b>Backups</b>: <code>db_backup</code> sidecar → rustfs bucket <code>fleet-db</code>, scheduled (default 02:30/08:30/14:30/20:30 Africa/Nairobi).</li>
</ul>
<p class=muted>See also: <code>docs/CONNECTIONS.md</code>, <code>docs/reference/01_BusinessAnalytics.md</code>,
<code>docs/manuals/OPERATIONS_MANUAL.md</code>, and the project <code>CLAUDE.md</code>.</p></div></body></html>