349 lines
17 KiB
MySQL
349 lines
17 KiB
MySQL
|
|
-- 07_analytics_views.sql
|
||
|
|
-- Analytics views backing the Daily Operations dashboard.
|
||
|
|
-- Each view wraps a query block from 01_BusinessAnalytics.md; the section
|
||
|
|
-- is recorded in COMMENT ON VIEW so debuggers can trace any panel back to
|
||
|
|
-- its spec with a single \d+ command.
|
||
|
|
--
|
||
|
|
-- All views are regular (not materialised). v_driver_aggregates_daily is
|
||
|
|
-- the only one with performance risk as position_history grows; convert
|
||
|
|
-- it to a TimescaleDB continuous aggregate once the hypertable exceeds
|
||
|
|
-- ~100k rows.
|
||
|
|
|
||
|
|
BEGIN;
|
||
|
|
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
-- v_fleet_today
|
||
|
|
-- One row per device with today's operational roll-up.
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
CREATE OR REPLACE VIEW tracksolid.v_fleet_today AS
|
||
|
|
WITH today AS (
|
||
|
|
SELECT (NOW() AT TIME ZONE 'Africa/Nairobi')::date AS d
|
||
|
|
),
|
||
|
|
trips_today AS (
|
||
|
|
SELECT
|
||
|
|
t.imei,
|
||
|
|
COUNT(*) AS trips,
|
||
|
|
SUM(t.distance_km) AS km,
|
||
|
|
SUM(t.driving_time_s)::numeric / 3600 AS drive_hours,
|
||
|
|
SUM(t.idle_time_s)::numeric / 3600 AS idle_hours,
|
||
|
|
MIN(t.start_time AT TIME ZONE 'Africa/Nairobi')::time AS first_departure,
|
||
|
|
MAX(COALESCE(t.end_time, t.start_time) AT TIME ZONE 'Africa/Nairobi')::time AS last_return
|
||
|
|
FROM tracksolid.trips t
|
||
|
|
CROSS JOIN today
|
||
|
|
WHERE (t.start_time AT TIME ZONE 'Africa/Nairobi')::date = today.d
|
||
|
|
GROUP BY t.imei
|
||
|
|
),
|
||
|
|
alarms_today AS (
|
||
|
|
SELECT
|
||
|
|
a.imei,
|
||
|
|
COUNT(*) AS alarm_count
|
||
|
|
FROM tracksolid.alarms a
|
||
|
|
CROSS JOIN today
|
||
|
|
WHERE (a.alarm_time AT TIME ZONE 'Africa/Nairobi')::date = today.d
|
||
|
|
GROUP BY a.imei
|
||
|
|
)
|
||
|
|
SELECT
|
||
|
|
d.imei,
|
||
|
|
d.driver_name,
|
||
|
|
d.vehicle_number,
|
||
|
|
d.vehicle_name,
|
||
|
|
COALESCE(d.assigned_city, d.city, 'unassigned') AS assigned_city,
|
||
|
|
d.enabled_flag,
|
||
|
|
COALESCE(tt.km, 0) AS km_today,
|
||
|
|
COALESCE(tt.trips, 0) AS trips_today,
|
||
|
|
COALESCE(tt.drive_hours, 0) AS drive_hours,
|
||
|
|
COALESCE(tt.idle_hours, 0) AS idle_hours,
|
||
|
|
tt.first_departure,
|
||
|
|
tt.last_return,
|
||
|
|
COALESCE(at.alarm_count, 0) AS alarms_today,
|
||
|
|
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_fix,
|
||
|
|
lp.speed AS last_speed,
|
||
|
|
(tt.imei IS NULL) AS did_not_move
|
||
|
|
FROM tracksolid.devices d
|
||
|
|
LEFT JOIN trips_today tt ON tt.imei = d.imei
|
||
|
|
LEFT JOIN alarms_today at ON at.imei = d.imei
|
||
|
|
LEFT JOIN tracksolid.live_positions lp ON lp.imei = d.imei;
|
||
|
|
|
||
|
|
COMMENT ON VIEW tracksolid.v_fleet_today IS
|
||
|
|
'01_BusinessAnalytics.md §9 Fleet Readiness Scorecard. One row per device with today''s roll-up.';
|
||
|
|
|
||
|
|
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
-- v_vehicles_not_moved_today
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
CREATE OR REPLACE VIEW tracksolid.v_vehicles_not_moved_today AS
|
||
|
|
SELECT
|
||
|
|
d.imei,
|
||
|
|
d.vehicle_name,
|
||
|
|
d.vehicle_number,
|
||
|
|
d.driver_name,
|
||
|
|
COALESCE(d.assigned_city, d.city, 'unassigned') AS assigned_city,
|
||
|
|
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_seen,
|
||
|
|
lp.speed
|
||
|
|
FROM tracksolid.devices d
|
||
|
|
LEFT JOIN tracksolid.live_positions lp ON lp.imei = d.imei
|
||
|
|
LEFT JOIN tracksolid.trips t
|
||
|
|
ON t.imei = d.imei
|
||
|
|
AND (t.start_time AT TIME ZONE 'Africa/Nairobi')::date
|
||
|
|
= (NOW() AT TIME ZONE 'Africa/Nairobi')::date
|
||
|
|
WHERE d.enabled_flag = 1
|
||
|
|
AND t.imei IS NULL;
|
||
|
|
|
||
|
|
COMMENT ON VIEW tracksolid.v_vehicles_not_moved_today IS
|
||
|
|
'01_BusinessAnalytics.md §2.3. Enabled vehicles with zero trips today.';
|
||
|
|
|
||
|
|
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
-- v_active_dispatch_map
|
||
|
|
-- Every enabled device with current position, colour-coded status for geomap.
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
CREATE OR REPLACE VIEW tracksolid.v_active_dispatch_map AS
|
||
|
|
SELECT
|
||
|
|
d.imei,
|
||
|
|
d.vehicle_number,
|
||
|
|
d.vehicle_name,
|
||
|
|
d.driver_name,
|
||
|
|
d.driver_phone,
|
||
|
|
COALESCE(d.assigned_city, d.city, 'unassigned') AS assigned_city,
|
||
|
|
lp.lat,
|
||
|
|
lp.lng,
|
||
|
|
lp.speed,
|
||
|
|
lp.direction,
|
||
|
|
lp.acc_status,
|
||
|
|
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_fix,
|
||
|
|
CASE
|
||
|
|
WHEN lp.gps_time IS NULL THEN 'never_reported'
|
||
|
|
WHEN lp.gps_time < NOW() - INTERVAL '10 minutes' THEN 'stale'
|
||
|
|
WHEN COALESCE(lp.speed, 0) > 5 THEN 'moving'
|
||
|
|
WHEN lp.acc_status = '1' THEN 'idle_ignition_on'
|
||
|
|
ELSE 'parked'
|
||
|
|
END AS status
|
||
|
|
FROM tracksolid.devices d
|
||
|
|
LEFT JOIN tracksolid.live_positions lp ON lp.imei = d.imei
|
||
|
|
WHERE d.enabled_flag = 1;
|
||
|
|
|
||
|
|
COMMENT ON VIEW tracksolid.v_active_dispatch_map IS
|
||
|
|
'01_BusinessAnalytics.md §4.3 All Active Vehicles Map. Geomap source.';
|
||
|
|
|
||
|
|
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
-- v_currently_idle
|
||
|
|
-- Engine on, speed ~0, fresh fix. "Currently burning fuel while parked."
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
CREATE OR REPLACE VIEW tracksolid.v_currently_idle AS
|
||
|
|
SELECT
|
||
|
|
d.imei,
|
||
|
|
d.vehicle_number,
|
||
|
|
d.driver_name,
|
||
|
|
COALESCE(d.assigned_city, d.city, 'unassigned') AS assigned_city,
|
||
|
|
lp.lat,
|
||
|
|
lp.lng,
|
||
|
|
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS since,
|
||
|
|
EXTRACT(EPOCH FROM (NOW() - lp.gps_time))::int AS idle_seconds
|
||
|
|
FROM tracksolid.devices d
|
||
|
|
JOIN tracksolid.live_positions lp ON lp.imei = d.imei
|
||
|
|
WHERE d.enabled_flag = 1
|
||
|
|
AND lp.acc_status = '1'
|
||
|
|
AND COALESCE(lp.speed, 0) < 2
|
||
|
|
AND lp.gps_time > NOW() - INTERVAL '15 minutes';
|
||
|
|
|
||
|
|
COMMENT ON VIEW tracksolid.v_currently_idle IS
|
||
|
|
'01_BusinessAnalytics.md §2.2 idle lens. Engine on, speed <2 km/h, fix in last 15m.';
|
||
|
|
|
||
|
|
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
-- v_driver_aggregates_daily
|
||
|
|
-- Per-driver per-day km + speeding + harsh-driving aggregates.
|
||
|
|
-- TODO: Convert to TimescaleDB continuous aggregate once position_history > 100k rows.
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
CREATE OR REPLACE VIEW tracksolid.v_driver_aggregates_daily AS
|
||
|
|
WITH trips_agg AS (
|
||
|
|
SELECT
|
||
|
|
imei,
|
||
|
|
(start_time AT TIME ZONE 'Africa/Nairobi')::date AS day,
|
||
|
|
SUM(distance_km) AS km,
|
||
|
|
COUNT(*) AS trips
|
||
|
|
FROM tracksolid.trips
|
||
|
|
WHERE start_time > NOW() - INTERVAL '31 days'
|
||
|
|
GROUP BY imei, (start_time AT TIME ZONE 'Africa/Nairobi')::date
|
||
|
|
),
|
||
|
|
speeding AS (
|
||
|
|
SELECT
|
||
|
|
imei,
|
||
|
|
(gps_time AT TIME ZONE 'Africa/Nairobi')::date AS day,
|
||
|
|
COUNT(*) FILTER (WHERE speed > 80) AS events_80,
|
||
|
|
COUNT(*) FILTER (WHERE speed > 100) AS events_100,
|
||
|
|
COUNT(*) FILTER (WHERE speed > 120) AS events_120
|
||
|
|
FROM tracksolid.position_history
|
||
|
|
WHERE gps_time > NOW() - INTERVAL '31 days'
|
||
|
|
AND speed IS NOT NULL
|
||
|
|
GROUP BY imei, (gps_time AT TIME ZONE 'Africa/Nairobi')::date
|
||
|
|
),
|
||
|
|
harsh_raw AS (
|
||
|
|
SELECT
|
||
|
|
imei,
|
||
|
|
gps_time,
|
||
|
|
speed,
|
||
|
|
LAG(speed) OVER (PARTITION BY imei ORDER BY gps_time) AS prev_speed,
|
||
|
|
LAG(gps_time) OVER (PARTITION BY imei ORDER BY gps_time) AS prev_time
|
||
|
|
FROM tracksolid.position_history
|
||
|
|
WHERE source = 'track_list'
|
||
|
|
AND gps_time > NOW() - INTERVAL '31 days'
|
||
|
|
),
|
||
|
|
harsh AS (
|
||
|
|
SELECT
|
||
|
|
imei,
|
||
|
|
(gps_time AT TIME ZONE 'Africa/Nairobi')::date AS day,
|
||
|
|
COUNT(*) AS harsh_events
|
||
|
|
FROM harsh_raw
|
||
|
|
WHERE ABS(speed - prev_speed) > 30
|
||
|
|
AND EXTRACT(EPOCH FROM (gps_time - prev_time)) BETWEEN 5 AND 60
|
||
|
|
GROUP BY imei, (gps_time AT TIME ZONE 'Africa/Nairobi')::date
|
||
|
|
)
|
||
|
|
SELECT
|
||
|
|
d.imei,
|
||
|
|
d.driver_name,
|
||
|
|
d.vehicle_number,
|
||
|
|
COALESCE(d.assigned_city, d.city, 'unassigned') AS assigned_city,
|
||
|
|
t.day,
|
||
|
|
COALESCE(t.km, 0) AS km,
|
||
|
|
COALESCE(t.trips, 0) AS trips,
|
||
|
|
COALESCE(s.events_80, 0) AS events_80,
|
||
|
|
COALESCE(s.events_100, 0) AS events_100,
|
||
|
|
COALESCE(s.events_120, 0) AS events_120,
|
||
|
|
COALESCE(h.harsh_events, 0) AS harsh_events,
|
||
|
|
ROUND(COALESCE(s.events_80, 0) / NULLIF(t.km, 0) * 100, 2) AS speeding_per_100km,
|
||
|
|
ROUND(COALESCE(h.harsh_events, 0)/ NULLIF(t.km, 0) * 100, 2) AS harsh_per_100km
|
||
|
|
FROM trips_agg t
|
||
|
|
JOIN tracksolid.devices d ON d.imei = t.imei
|
||
|
|
LEFT JOIN speeding s ON s.imei = t.imei AND s.day = t.day
|
||
|
|
LEFT JOIN harsh h ON h.imei = t.imei AND h.day = t.day;
|
||
|
|
|
||
|
|
COMMENT ON VIEW tracksolid.v_driver_aggregates_daily IS
|
||
|
|
'01_BusinessAnalytics.md §3.1 (speeding) + §3.2 (harsh driving). Daily grain; panels window via $__timeFilter(day).';
|
||
|
|
|
||
|
|
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
-- v_fleet_km_daily
|
||
|
|
-- Fleet-wide daily distance, broken by assigned_city.
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
CREATE OR REPLACE VIEW tracksolid.v_fleet_km_daily AS
|
||
|
|
SELECT
|
||
|
|
(t.start_time AT TIME ZONE 'Africa/Nairobi')::date AS day,
|
||
|
|
COALESCE(d.assigned_city, d.city, 'unassigned') AS assigned_city,
|
||
|
|
SUM(t.distance_km) AS km,
|
||
|
|
COUNT(DISTINCT t.imei) AS active_vehicles,
|
||
|
|
COUNT(*) AS trips
|
||
|
|
FROM tracksolid.trips t
|
||
|
|
JOIN tracksolid.devices d ON d.imei = t.imei
|
||
|
|
WHERE t.start_time > NOW() - INTERVAL '90 days'
|
||
|
|
GROUP BY 1, 2;
|
||
|
|
|
||
|
|
COMMENT ON VIEW tracksolid.v_fleet_km_daily IS
|
||
|
|
'01_BusinessAnalytics.md §7 Panel 5 Distance Trend. City-cohort cut built in.';
|
||
|
|
|
||
|
|
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
-- v_alarms_daily
|
||
|
|
-- Daily alarm counts by alarm_name for the time-series panel.
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
CREATE OR REPLACE VIEW tracksolid.v_alarms_daily AS
|
||
|
|
SELECT
|
||
|
|
(alarm_time AT TIME ZONE 'Africa/Nairobi')::date AS day,
|
||
|
|
COALESCE(alarm_name, alarm_type, 'unknown') AS alarm_name,
|
||
|
|
COUNT(*) AS alarm_count
|
||
|
|
FROM tracksolid.alarms
|
||
|
|
WHERE alarm_time > NOW() - INTERVAL '90 days'
|
||
|
|
GROUP BY 1, 2;
|
||
|
|
|
||
|
|
COMMENT ON VIEW tracksolid.v_alarms_daily IS
|
||
|
|
'01_BusinessAnalytics.md §7 Panel 7 Alarm Frequency. Stacked-by-alarm_name time series.';
|
||
|
|
|
||
|
|
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
-- v_utilisation_daily
|
||
|
|
-- Per-vehicle per-day utilisation from dwh_gold.fact_daily_fleet_metrics.
|
||
|
|
-- Empty until dwh_gold.refresh_daily_metrics nightly ETL is scheduled.
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
CREATE OR REPLACE VIEW tracksolid.v_utilisation_daily AS
|
||
|
|
SELECT
|
||
|
|
f.day,
|
||
|
|
d.imei,
|
||
|
|
d.vehicle_number,
|
||
|
|
d.driver_name,
|
||
|
|
COALESCE(d.assigned_city, d.city, 'unassigned') AS assigned_city,
|
||
|
|
f.total_distance_km,
|
||
|
|
f.total_drive_hours,
|
||
|
|
f.total_idle_hours,
|
||
|
|
f.alarm_count,
|
||
|
|
f.overspeed_count,
|
||
|
|
ROUND(
|
||
|
|
f.total_drive_hours / NULLIF(f.total_drive_hours + f.total_idle_hours, 0) * 100,
|
||
|
|
1
|
||
|
|
) AS utilisation_pct
|
||
|
|
FROM dwh_gold.fact_daily_fleet_metrics f
|
||
|
|
JOIN dwh_gold.dim_vehicles dv ON dv.vehicle_key = f.vehicle_key
|
||
|
|
JOIN tracksolid.devices d ON d.imei = dv.imei;
|
||
|
|
|
||
|
|
COMMENT ON VIEW tracksolid.v_utilisation_daily IS
|
||
|
|
'01_BusinessAnalytics.md §7 Panel 8 Utilisation Heatmap. Empty until nightly ETL runs.';
|
||
|
|
|
||
|
|
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
-- v_sla_inflight
|
||
|
|
-- In-flight field-service tickets with SLA timers, joined to dispatch_log.
|
||
|
|
-- Empty until ops.tickets is populated from Zoho/Freshdesk or equivalent.
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
CREATE OR REPLACE VIEW tracksolid.v_sla_inflight AS
|
||
|
|
SELECT
|
||
|
|
t.ticket_id,
|
||
|
|
t.customer,
|
||
|
|
t.priority,
|
||
|
|
t.job_type,
|
||
|
|
t.status,
|
||
|
|
t.created_at,
|
||
|
|
t.assigned_at,
|
||
|
|
t.closed_at,
|
||
|
|
t.assigned_imei,
|
||
|
|
COALESCE(dl.driver_name, t.driver_name) AS driver_name,
|
||
|
|
dl.first_movement_at,
|
||
|
|
dl.on_site_at,
|
||
|
|
dl.resolved_at,
|
||
|
|
EXTRACT(EPOCH FROM (COALESCE(dl.first_movement_at, NOW()) - t.assigned_at)) / 60 AS dispatch_mins,
|
||
|
|
EXTRACT(EPOCH FROM (COALESCE(dl.on_site_at, NOW()) - dl.first_movement_at)) / 60 AS enroute_mins,
|
||
|
|
EXTRACT(EPOCH FROM (COALESCE(dl.resolved_at, NOW()) - dl.on_site_at)) / 60 AS onsite_mins,
|
||
|
|
EXTRACT(EPOCH FROM (COALESCE(dl.resolved_at, NOW()) - t.created_at)) / 60 AS resolution_mins,
|
||
|
|
CASE
|
||
|
|
WHEN dl.resolved_at IS NOT NULL THEN 'resolved'
|
||
|
|
WHEN dl.cancelled_at IS NOT NULL THEN 'cancelled'
|
||
|
|
WHEN dl.on_site_at IS NOT NULL THEN 'on_site'
|
||
|
|
WHEN dl.first_movement_at IS NOT NULL THEN 'en_route'
|
||
|
|
WHEN t.assigned_at IS NOT NULL THEN 'dispatched'
|
||
|
|
ELSE 'open'
|
||
|
|
END AS ticket_stage
|
||
|
|
FROM ops.tickets t
|
||
|
|
LEFT JOIN tracksolid.dispatch_log dl ON dl.ticket_id = t.ticket_id
|
||
|
|
WHERE t.status NOT IN ('resolved', 'cancelled')
|
||
|
|
OR t.closed_at > NOW() - INTERVAL '24 hours';
|
||
|
|
|
||
|
|
COMMENT ON VIEW tracksolid.v_sla_inflight IS
|
||
|
|
'01_BusinessAnalytics.md §4.5 Field-Service SLA Metrics. Open tickets + last 24h resolved.';
|
||
|
|
|
||
|
|
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
-- Read access for Grafana
|
||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
|
GRANT USAGE ON SCHEMA ops TO grafana_ro;
|
||
|
|
|
||
|
|
GRANT SELECT ON tracksolid.v_fleet_today TO grafana_ro;
|
||
|
|
GRANT SELECT ON tracksolid.v_vehicles_not_moved_today TO grafana_ro;
|
||
|
|
GRANT SELECT ON tracksolid.v_active_dispatch_map TO grafana_ro;
|
||
|
|
GRANT SELECT ON tracksolid.v_currently_idle TO grafana_ro;
|
||
|
|
GRANT SELECT ON tracksolid.v_driver_aggregates_daily TO grafana_ro;
|
||
|
|
GRANT SELECT ON tracksolid.v_fleet_km_daily TO grafana_ro;
|
||
|
|
GRANT SELECT ON tracksolid.v_alarms_daily TO grafana_ro;
|
||
|
|
GRANT SELECT ON tracksolid.v_utilisation_daily TO grafana_ro;
|
||
|
|
GRANT SELECT ON tracksolid.v_sla_inflight TO grafana_ro;
|
||
|
|
|
||
|
|
COMMIT;
|