Adds migrations 006/007/008 that wrap tracksolid.v_trips_enriched (introduced upstream in tracksolid_timescale_grafana_prod migration 09) and DROP+CREATE the trips_for_day / trips_for_range RPCs to return the new metadata columns: vehicle_plate, start_address, end_address, driving_time_s, idle_time_s, fuel_consumed_l, and daily_seq. Path/timestamps logic is unchanged — position_history is still scanned to produce the per-vertex timestamps_rel that drive deck.gl TripsLayer animation. route_geom can't replace this since it carries no per-vertex time, but the GIST index on it is available for future area filters. Day filter switches from start_time::date to v.trip_date_eat (the Africa/Nairobi local date already computed by v_trips_enriched), which fixes the latent bug where trips spanning midnight UTC could be misbucketed. Frontend Trip type still receives all original fields in compatible positions; the new fields are additive and ignored by the existing TripsLayer code. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|---|---|---|
| compose | ||
| db/migrations | ||
| web | ||
| .gitignore | ||
| README.md | ||
| trips_deckgl_tracksolid.md | ||
deckgl_tracksolid
Public dashboard that animates a day's vehicle trips from tracksolid_db,
colored and filtered by cost_centre. Stack: deck.gl TripsLayer over
Mapbox GL, fed by PostgREST in front of TimescaleDB.
React + deck.gl SPA → PostgREST → tracksolid_db (Timescale)
(web/) (compose/) public.trips_viz_v1
public.trips_for_day(date, text[])
public.trips_for_range(date, date, text[])
public.list_cost_centres()
The full design lives in trips_deckgl_tracksolid.md — read that first.
Layout
| Path | Purpose |
|---|---|
db/migrations/ |
Five SQL files. Idempotent. Apply in order. Add only public.* objects. |
compose/ |
docker-compose.yaml for the PostgREST API + the built web container. |
web/ |
Vite + React + TS SPA. Renders TripsLayer + PathLayer over Mapbox. |
trips_deckgl_tracksolid.md |
Design doc / plan. |
First-time setup
- Apply migrations to
tracksolid_db:psql "$ADMIN_DSN" \ -f db/migrations/001_viz_anon_role.sql \ -f db/migrations/002_trips_viz_view.sql \ -f db/migrations/003_trips_for_day_rpc.sql \ -f db/migrations/004_trips_for_range_rpc.sql \ -f db/migrations/005_list_cost_centres_rpc.sql - Set the
viz_anonlogin password out of band (do not commit):ALTER ROLE viz_anon LOGIN PASSWORD '<generate one>'; - Configure env:
cp compose/.env.example compose/.env # fill in VIZ_DATA_PASSWORD, VITE_MAPBOX_TOKEN, … cp web/.env.example web/.env.local # fill in VITE_API_URL (e.g. http://localhost:3000) and VITE_MAPBOX_TOKEN
Local development
# 1. Start PostgREST against the staging DB
cd compose && docker compose up postgrest
# 2. Run the web app against it
cd web && pnpm install && pnpm dev
# → http://localhost:5173
The web app reads VITE_API_URL (default http://localhost:3000) and
VITE_MAPBOX_TOKEN. Without a Mapbox token the deck.gl layers still render
on a black background — fine for testing data, ugly for demos.
Production deployment (Coolify)
Two services live alongside the existing Dekart stack:
postgrest— read-only API atapi.trips.rahamafresh.com. CORS allow-listed to the web FQDN.trips_web— Caddy serving the built SPA attrips.rahamafresh.com.
Both inherit env vars from the Coolify resource panel — see compose/.env.example
for the canonical list.
Security boundary
The viz_anon role has no grants on tracksolid.* — verified in setup. It
can only call the four RPCs in public, which run SECURITY DEFINER and only
expose the columns listed in trips_viz_v1. Direct queries against any
tracksolid table return permission denied for schema tracksolid.
If the public dashboard ever needs to be locked down, add Traefik basic-auth in Coolify (same pattern Dekart uses) — no app-level changes needed.