-- 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;
