From 9852eff985a6b636980a521fc785dae98f00f347 Mon Sep 17 00:00:00 2001 From: kianiadee Date: Sat, 23 May 2026 23:13:46 +0300 Subject: [PATCH] Popup: pre-emptive driver-name extraction from device_name (until P3 roster lands) --- ...20260601000013_driver_from_device_name.sql | 161 ++++++++++++++++++ web/fleet-core.js | 1 + web/index-live.html | 1 + 3 files changed, 163 insertions(+) create mode 100644 db/migrations/20260601000013_driver_from_device_name.sql diff --git a/db/migrations/20260601000013_driver_from_device_name.sql b/db/migrations/20260601000013_driver_from_device_name.sql new file mode 100644 index 0000000..11753e5 --- /dev/null +++ b/db/migrations/20260601000013_driver_from_device_name.sql @@ -0,0 +1,161 @@ +-- migrate:up +-- +-- Tracksolid's device_name field is a free-form admin tag. In this fleet it +-- follows the convention "{driver name} - {plate}[_cam]" for ~90% of devices: +-- "John Mbugua - KDW 573B_cam" → driver "John Mbugua" +-- "Parked - KMGK 596V" → no driver (placeholder tag) +-- "JC400P-92732" → no driver (device serial) +-- +-- Extract a best-effort driver name so the popup can show it pre-emptively, +-- ahead of the proper driver-roster work in P3 (PRD F3.10-F3.13). When the +-- HR-synced roster lands, this helper becomes redundant and gets removed. + +CREATE OR REPLACE FUNCTION serve._driver_name(device_name text) RETURNS text +LANGUAGE sql IMMUTABLE AS $$ + SELECT CASE + WHEN m IS NULL THEN NULL + WHEN lower(m[1]) IN ('parked', 'unassigned', 'spare', 'available') THEN NULL + WHEN m[1] ~ '^[0-9_]+$' THEN NULL + ELSE m[1] + END + FROM (SELECT regexp_match(COALESCE(device_name, ''), '^(.+?) - .+$') AS m) p +$$; + +DROP FUNCTION IF EXISTS serve.fn_live_view(jsonb); + +CREATE OR REPLACE FUNCTION serve.fn_live_view(filters jsonb) +RETURNS jsonb +LANGUAGE plpgsql STABLE +AS $$ +DECLARE + fresh_window interval := COALESCE((filters->>'fresh_window')::interval, interval '24 hours'); + offline_after interval := COALESCE((filters->>'offline_after')::interval, interval '5 minutes'); + move_speed_kmh numeric := COALESCE((filters->>'move_speed_kmh')::numeric, 5); + p_cost_centre text := filters->>'cost_centre'; + p_assigned_city text := filters->>'assigned_city'; + p_vehicle_numbers text[] := CASE + WHEN filters ? 'vehicle_numbers' + THEN ARRAY(SELECT jsonb_array_elements_text(filters->'vehicle_numbers')) + ELSE NULL + END; + result jsonb; +BEGIN + WITH candidates AS ( + SELECT + lp.imei, lp.occurred_at, lp.geom, lp.speed_kmh, lp.direction_deg, + lp.mc_type, lp.current_mileage_km, lp.gps_signal, lp.satellites, + lp.device_name, lp.pos_type, + d.device_type, d.activation_at, + v.vehicle_id, v.plate, v.cost_centre, v.assigned_city + FROM state.live_positions lp + JOIN domain.devices d ON d.imei = lp.imei + JOIN domain.vehicles v ON v.vehicle_id = d.vehicle_id + WHERE d.lifecycle = 'active' + AND (p_cost_centre IS NULL OR v.cost_centre = p_cost_centre) + AND (p_assigned_city IS NULL OR v.assigned_city = p_assigned_city) + AND (p_vehicle_numbers IS NULL OR v.plate = ANY (p_vehicle_numbers)) + ), + ranked AS ( + SELECT c.*, ROW_NUMBER() OVER ( + PARTITION BY c.vehicle_id + ORDER BY + CASE c.device_type WHEN 'tracker' THEN 0 ELSE 1 END, + CASE WHEN c.occurred_at > now() - fresh_window THEN 0 ELSE 1 END, + c.occurred_at DESC, + c.activation_at DESC NULLS LAST + ) AS rn + FROM candidates c + ), + deduped AS (SELECT * FROM ranked WHERE rn = 1), + enriched AS ( + SELECT d.*, + CASE + WHEN d.occurred_at <= now() - offline_after THEN 'offline' + WHEN d.speed_kmh IS NOT NULL AND d.speed_kmh > move_speed_kmh THEN 'moving' + ELSE 'parked' + END AS operational_state, + serve._cost_centre_color(d.cost_centre) AS cost_centre_color, + EXTRACT(EPOCH FROM (now() - d.occurred_at))::int AS age_sec, + round(ST_Y(d.geom)::numeric, 4) AS lat_rounded, + round(ST_X(d.geom)::numeric, 4) AS lng_rounded + FROM deduped d + ), + with_addr AS ( + SELECT e.*, g.address, g.address_short + FROM enriched e + LEFT JOIN state.geocoded_positions g + ON g.lat_rounded = e.lat_rounded + AND g.lng_rounded = e.lng_rounded + ), + summary AS ( + SELECT jsonb_build_object( + 'total_active', count(*), + 'moving', count(*) FILTER (WHERE operational_state = 'moving'), + 'parked', count(*) FILTER (WHERE operational_state = 'parked'), + 'offline', count(*) FILTER (WHERE operational_state = 'offline'), + 'below_freshness_slo', count(*) FILTER ( + WHERE occurred_at <= now() - interval '90 seconds' + ), + 'as_of', to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') + ) AS s + FROM with_addr + ), + features AS ( + SELECT COALESCE(jsonb_agg( + jsonb_build_object( + 'type', 'Feature', + 'geometry', ST_AsGeoJSON(e.geom)::jsonb, + 'properties', jsonb_build_object( + 'vehicle_id', e.vehicle_id, + 'plate', e.plate, + 'plate_short', serve._label_short(e.device_name, e.plate), + 'driver_name', serve._driver_name(e.device_name), + 'imei', e.imei, + 'device_type', e.device_type, + 'device_name', e.device_name, + 'mc_type', e.mc_type, + 'pos_type', e.pos_type, + 'cost_centre', e.cost_centre, + 'cost_centre_color', e.cost_centre_color, + 'assigned_city', e.assigned_city, + 'address', e.address, + 'address_short', e.address_short, + 'occurred_at', to_char(e.occurred_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"'), + 'age_sec', e.age_sec, + 'speed_kmh', e.speed_kmh, + 'heading_deg', e.direction_deg, + 'gps_signal', e.gps_signal, + 'satellites', e.satellites, + 'current_mileage_km', e.current_mileage_km, + 'operational_state', e.operational_state, + 'style_class', 'vehicle-' || e.operational_state, + 'marker_color', CASE WHEN e.operational_state = 'moving' + THEN e.cost_centre_color + ELSE '#9ca3af' END, + 'show_arrow', (e.operational_state = 'moving' AND e.direction_deg IS NOT NULL) + ) + ) + ), '[]'::jsonb) AS feats + FROM with_addr e + ), + slo_block AS ( + SELECT COALESCE(jsonb_object_agg( + metric, + jsonb_build_object('threshold', threshold, 'current', current_value, 'status', status) + ), '{}'::jsonb) AS ss + FROM slo.v_current_status + ) + SELECT jsonb_build_object( + 'summary', (SELECT s FROM summary), + 'geojson', jsonb_build_object('type', 'FeatureCollection', 'features', (SELECT feats FROM features)), + 'slo_status', (SELECT ss FROM slo_block) + ) + INTO result; + RETURN result; +END; +$$; + +-- migrate:down + +DROP FUNCTION IF EXISTS serve.fn_live_view(jsonb); +DROP FUNCTION IF EXISTS serve._driver_name(text); diff --git a/web/fleet-core.js b/web/fleet-core.js index 8ead54b..ccfde40 100644 --- a/web/fleet-core.js +++ b/web/fleet-core.js @@ -258,6 +258,7 @@ function _popupHtml(props) { ${_esc(props.plate)} ${_esc(pillText)} + ${props.driver_name ? `` : ''} ${tagLine ? `` : ''} ${addressLine ? `` : ''} ${headingLine ? `` : ''} diff --git a/web/index-live.html b/web/index-live.html index 1bcf02f..a922a15 100644 --- a/web/index-live.html +++ b/web/index-live.html @@ -63,6 +63,7 @@ .pill-parked { background: rgba(148,163,184,0.15); color: var(--muted); } .pill-offline { background: rgba(148,163,184,0.15); color: var(--muted); } .pill-unknown { background: rgba(148,163,184,0.15); color: var(--muted); } + .popup-driver { color: var(--text); font-size: 13.5px; margin: 4px 0 2px; font-weight: 500; } .popup-meta { color: var(--muted); font-size: 12px; margin: 2px 0 4px; } .popup-address { color: var(--text); font-size: 13.5px; margin: 6px 0 8px; font-weight: 500; } .popup-row { color: #cbd5e1; font-size: 12.5px; margin: 4px 0; }