feat(reporting): add vehicle_type + fleet_segment to live map feed (migration 16)
Some checks are pending
Static Analysis / static (push) Waiting to run
Tests / test (push) Waiting to run

fn_live_positions now emits 'vehicle_type' (devices.vehicle_models) and
'fleet_segment' (reporting.fn_fleet_segment) in each GeoJSON feature so FleetNow
can give specialist vehicles (Crane/Motorbike/Pick-Up) their own marker icons.
Additive only — no signature change, STABLE function read immediately by
dashboard_api (no redeploy). Function body reproduced verbatim from prod via
pg_get_functiondef plus the two new properties.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
david kiania 2026-06-08 14:33:21 +03:00
parent 0c32094347
commit a8e1327aa8
2 changed files with 100 additions and 0 deletions

View file

@ -0,0 +1,99 @@
-- 16_live_feed_vehicle_type.sql
-- Expose vehicle_type + fleet_segment on the live-map GeoJSON feed so FleetNow can give the
-- specialist vehicles (Crane / Motorbike / Pick-Up) their own marker icons. All other vehicles
-- (field-service + unassigned) keep their current marker — FleetNow only overrides icons when
-- vehicle_type is one of the specialist types.
--
-- reporting.fn_live_positions is reproduced verbatim from the live prod definition
-- (== migrations/11_reporting_schema.sql, captured via pg_get_functiondef) with TWO added
-- feature properties:
-- 'vehicle_type' = devices.vehicle_models (authoritative API type, surfaced by v_live_positions)
-- 'fleet_segment' = reporting.fn_fleet_segment(vehicle_models) (field_service|specialist|unassigned)
-- No signature change, so dependents are unaffected; STABLE function, read immediately by
-- dashboard_api (no redeploy/restart). Safe to re-apply (CREATE OR REPLACE).
SET search_path = tracksolid, reporting, public;
CREATE OR REPLACE FUNCTION reporting.fn_live_positions(p_cost_centre text DEFAULT NULL::text, p_acc_status text DEFAULT NULL::text)
RETURNS jsonb
LANGUAGE plpgsql
STABLE
AS $function$
DECLARE
v_result jsonb;
BEGIN
p_cost_centre := NULLIF(p_cost_centre, '');
p_acc_status := NULLIF(p_acc_status, '');
WITH filtered AS (
SELECT * FROM reporting.v_live_positions
WHERE (p_cost_centre IS NULL OR cost_centre = p_cost_centre)
AND (p_acc_status IS NULL OR acc_status = p_acc_status)
)
SELECT jsonb_build_object(
'summary', jsonb_build_object(
'vehicle_count', COUNT(*),
-- "moving" and "parked" both restrict to devices that have reported
-- within the OFFLINE_THRESHOLD (24 h) so they represent the live
-- fleet, not equipment-failure stragglers. "offline" is its own
-- counter for the > 24 h tail.
'moving', COUNT(*) FILTER (WHERE acc_status = '1'
AND source_age_hours < 24),
'parked', COUNT(*) FILTER (WHERE acc_status = '0'
AND source_age_hours < 24),
'offline', COUNT(*) FILTER (WHERE source_age_hours >= 24),
'median_speed_moving', percentile_cont(0.5) WITHIN GROUP (ORDER BY speed)
FILTER (WHERE acc_status = '1'
AND source_age_hours < 24
AND speed > 0),
'last_batch_at', to_char(MAX(updated_at) AT TIME ZONE 'Africa/Nairobi',
'YYYY-MM-DD HH24:MI:SS'),
'oldest_fix_at', to_char(MIN(gps_time) AT TIME ZONE 'Africa/Nairobi',
'YYYY-MM-DD HH24:MI:SS'),
'newest_fix_at', to_char(MAX(gps_time) AT TIME ZONE 'Africa/Nairobi',
'YYYY-MM-DD HH24:MI:SS'),
'last_batch_utc', MAX(updated_at),
'newest_fix_utc', MAX(gps_time)
),
'geojson', jsonb_build_object(
'type', 'FeatureCollection',
'features', COALESCE(jsonb_agg(
jsonb_build_object(
'type', 'Feature',
'properties', jsonb_build_object(
'imei', imei,
'vehicle_number', vehicle_number,
'driver', assigned_driver,
'cost_centre', cost_centre,
'assigned_city', assigned_city,
'vehicle_category', vehicle_category,
'vehicle_type', vehicle_models,
'fleet_segment', reporting.fn_fleet_segment(vehicle_models),
'mc_type', mc_type,
'device_kind', device_kind,
'source_age_hours', source_age_hours,
'speed', speed,
'direction', direction,
'acc_status', acc_status,
'device_status', device_status,
'gps_signal', gps_signal,
'gps_num', gps_num,
'current_mileage', current_mileage,
'loc_desc', loc_desc,
'gps_time', to_char(gps_time AT TIME ZONE 'Africa/Nairobi',
'YYYY-MM-DD HH24:MI:SS'),
'updated_at', to_char(updated_at AT TIME ZONE 'Africa/Nairobi',
'YYYY-MM-DD HH24:MI:SS'),
'gps_time_utc', gps_time,
'updated_at_utc', updated_at
),
'geometry', jsonb_build_object(
'type', 'Point',
'coordinates', jsonb_build_array(lng, lat)
)
)
), '[]'::jsonb)
)
) INTO v_result FROM filtered;
RETURN v_result;
END $function$;

View file

@ -39,6 +39,7 @@ MIGRATIONS = [
"13_drop_dwh_gold.sql", # purge dormant dwh_gold schema + v_utilisation_daily "13_drop_dwh_gold.sql", # purge dormant dwh_gold schema + v_utilisation_daily
"14_fleet_segment_and_vehicles_view.sql", # reporting.fn_fleet_segment + reporting.v_vehicles roster "14_fleet_segment_and_vehicles_view.sql", # reporting.fn_fleet_segment + reporting.v_vehicles roster
"15_map_exclude_cost_centres.sql", # hide personal/management/mtn vehicles from the live map "15_map_exclude_cost_centres.sql", # hide personal/management/mtn vehicles from the live map
"16_live_feed_vehicle_type.sql", # add vehicle_type + fleet_segment to fn_live_positions feed
] ]
# ── Tables that must exist before the service is allowed to start ───────────── # ── Tables that must exist before the service is allowed to start ─────────────