-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -- Migration 09 — tracksolid.trips Enrichment -- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -- The polling endpoint jimi.device.track.mileage does not return start/end -- coordinates, fuel, idle, or trip sequence. This migration adds the columns -- needed to enrich every poll-ingested trip from data we already have: -- • start/end coordinates and full route polyline reconstructed from -- position_history at insert time (see ingest_movement_rev.py::poll_trips) -- • reverse-geocoded human-readable addresses (Nominatim) -- • denormalised vehicle_plate so trip displays don't need a join -- • waypoint count for audit / data-quality checks -- -- Adds a v_trips_enriched view exposing daily_seq (Nth trip for IMEI on this -- Africa/Nairobi date) — replaces reliance on the device-supplied trip_seq -- which is only populated when the rarely-firing /pushtripreport webhook -- delivers a payload. -- -- Run after migration 08. Safe to re-run (ADD COLUMN IF NOT EXISTS, -- CREATE INDEX IF NOT EXISTS, CREATE OR REPLACE VIEW). -- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BEGIN; -- ── 1. New columns on tracksolid.trips ────────────────────────────────────── ALTER TABLE tracksolid.trips ADD COLUMN IF NOT EXISTS route_geom geometry(LineString, 4326), ADD COLUMN IF NOT EXISTS start_address TEXT, ADD COLUMN IF NOT EXISTS end_address TEXT, ADD COLUMN IF NOT EXISTS vehicle_plate TEXT, ADD COLUMN IF NOT EXISTS waypoints_count INTEGER; COMMENT ON COLUMN tracksolid.trips.route_geom IS 'Full GPS route polyline built at ingest from position_history points ' 'where gps_time BETWEEN start_time AND end_time. NULL when fewer than ' '2 fixes are available for the trip window.'; COMMENT ON COLUMN tracksolid.trips.start_address IS 'Reverse-geocoded human-readable address near start_geom (Nominatim). ' 'NULL on lookup failure; address is best-effort, not authoritative.'; COMMENT ON COLUMN tracksolid.trips.end_address IS 'Reverse-geocoded human-readable address near end_geom (Nominatim). ' 'NULL on lookup failure; address is best-effort, not authoritative.'; COMMENT ON COLUMN tracksolid.trips.vehicle_plate IS 'Denormalised tracksolid.devices.vehicle_number cached at trip-insert ' 'time. Avoids a join for trip displays; refreshed only on next ingest.'; COMMENT ON COLUMN tracksolid.trips.waypoints_count IS 'Number of position_history fixes that contributed to route_geom. ' 'Audit aid: 0 or 1 means route_geom is NULL or degenerate.'; -- ── 2. Spatial indexes for replay / map queries ───────────────────────────── CREATE INDEX IF NOT EXISTS idx_trips_route_geom ON tracksolid.trips USING GIST (route_geom); CREATE INDEX IF NOT EXISTS idx_trips_start_geom ON tracksolid.trips USING GIST (start_geom); CREATE INDEX IF NOT EXISTS idx_trips_end_geom ON tracksolid.trips USING GIST (end_geom); -- ── 3. v_trips_enriched view ──────────────────────────────────────────────── -- Adds trip_date_eat (Africa/Nairobi local date) and daily_seq (Nth trip on -- that date for the IMEI) without depending on the device-supplied trip_seq. CREATE OR REPLACE VIEW tracksolid.v_trips_enriched AS SELECT t.*, (t.start_time AT TIME ZONE 'Africa/Nairobi')::date AS trip_date_eat, ROW_NUMBER() OVER ( PARTITION BY t.imei, (t.start_time AT TIME ZONE 'Africa/Nairobi')::date ORDER BY t.start_time ) AS daily_seq FROM tracksolid.trips t; COMMENT ON VIEW tracksolid.v_trips_enriched IS 'tracksolid.trips with computed daily_seq (Nth trip per IMEI per local ' 'Africa/Nairobi day) and trip_date_eat. Replaces reliance on the ' 'device-supplied trip_seq column, which is NULL for poll-ingested trips.'; -- ── 4. Read access for grafana_ro ─────────────────────────────────────────── GRANT SELECT ON tracksolid.v_trips_enriched TO grafana_ro; COMMIT;