tracksolid_timescale_grafan.../tracksolid_DB_manual.md
David Kiania 05993100e9 Enhance tracksolid_DB_manual.md with full analytics suite
- Add sections 16–21: Daily, Weekly, Monthly, Quarterly analytics,
  new table docs (device_events, fuel_readings, temperature_readings,
  lbs_readings, geofences), and updated Known Data Issues
- Fix all distance queries: remove erroneous /1000000.0 division
  (column is now distance_km in kilometres after migration 04)
- Update alarms section to reflect BUG-01 field mapping fix
- Update parking section to reflect POLL-02 acc_type/durSecond fix
- Rewrite "verify distance" section as accuracy cross-check query
- Expand row count query to include 5 new tables from migration 05

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 22:33:06 +03:00

65 KiB
Raw Blame History

Tracksolid Database Manual

Database: tracksolid_db
Host: kianiadee@stage.rahamafresh.com
Container: timescale_db-bo3nov2ija7g8wn9b1g2paxs-210508774107
Engine: TimescaleDB (PostgreSQL 16 + TimescaleDB 2.15)
Timezone note: All timestamps are stored in UTC. Always cast to AT TIME ZONE 'Africa/Nairobi' (EAT = UTC+3) when displaying to users or building reports.

To connect from the host server:

docker exec timescale_db-bo3nov2ija7g8wn9b1g2paxs-210508774107 psql -U postgres -d tracksolid_db

Table of Contents

  1. Schema Overview
  2. tracksolid.devices
  3. tracksolid.position_history
  4. tracksolid.trips
  5. tracksolid.live_positions
  6. tracksolid.alarms
  7. tracksolid.ingestion_log
  8. tracksolid.heartbeats
  9. tracksolid.obd_readings
  10. tracksolid.parking_events
  11. tracksolid.fault_codes
  12. dwh_gold.dim_vehicles
  13. dwh_gold.fact_daily_fleet_metrics
  14. Business Intelligence Queries
  15. Today's Metrics — From 00:00 Nairobi Time to Now
  16. Daily Analytics
  17. Weekly Analytics
  18. Monthly Analytics
  19. Quarterly Analytics
  20. New Tables from Migration 05
  21. Known Data Issues

1. Schema Overview

The database is organised into three schemas:

Schema Purpose
tracksolid Raw operational data ingested from the Tracksolid/Jimi Open Platform API
dwh_gold Pre-aggregated data warehouse layer for reporting and dashboards
public PostGIS spatial reference tables (system-managed)

Data is pulled from the Jimi/Tracksolid API on a continuous polling schedule. The ingestion_log table records every API call so you can audit pipeline health.

List all tables with sizes

This query gives a quick inventory of every table in the two business schemas along with its on-disk size. Useful as a first step when connecting to the database to understand what exists and which tables hold the most data.

SELECT
  schemaname,
  tablename,
  pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS size
FROM pg_tables
WHERE schemaname IN ('tracksolid', 'dwh_gold')
ORDER BY pg_total_relation_size(schemaname || '.' || tablename) DESC;

Count rows in all tables

A fast sanity check to see which tables are populated and which are still empty. Run this after deployments or data migrations to confirm data is flowing as expected.

SELECT 'tracksolid.trips'                   AS tbl, COUNT(*) FROM tracksolid.trips
UNION ALL SELECT 'tracksolid.position_history',       COUNT(*) FROM tracksolid.position_history
UNION ALL SELECT 'tracksolid.alarms',                 COUNT(*) FROM tracksolid.alarms
UNION ALL SELECT 'tracksolid.devices',                COUNT(*) FROM tracksolid.devices
UNION ALL SELECT 'tracksolid.heartbeats',             COUNT(*) FROM tracksolid.heartbeats
UNION ALL SELECT 'tracksolid.obd_readings',           COUNT(*) FROM tracksolid.obd_readings
UNION ALL SELECT 'tracksolid.parking_events',         COUNT(*) FROM tracksolid.parking_events
UNION ALL SELECT 'tracksolid.live_positions',         COUNT(*) FROM tracksolid.live_positions
UNION ALL SELECT 'tracksolid.fault_codes',            COUNT(*) FROM tracksolid.fault_codes
UNION ALL SELECT 'tracksolid.device_events',          COUNT(*) FROM tracksolid.device_events
UNION ALL SELECT 'tracksolid.fuel_readings',          COUNT(*) FROM tracksolid.fuel_readings
UNION ALL SELECT 'tracksolid.temperature_readings',   COUNT(*) FROM tracksolid.temperature_readings
UNION ALL SELECT 'tracksolid.lbs_readings',           COUNT(*) FROM tracksolid.lbs_readings
UNION ALL SELECT 'tracksolid.geofences',              COUNT(*) FROM tracksolid.geofences
UNION ALL SELECT 'tracksolid.ingestion_log',          COUNT(*) FROM tracksolid.ingestion_log
UNION ALL SELECT 'dwh_gold.dim_vehicles',             COUNT(*) FROM dwh_gold.dim_vehicles
UNION ALL SELECT 'dwh_gold.fact_daily_fleet_metrics', COUNT(*) FROM dwh_gold.fact_daily_fleet_metrics;

2. tracksolid.devices

This table is the master registry of all GPS tracking devices in the fleet. Each row represents one physical GPS tracker unit installed in a vehicle. Key fields include the device's IMEI (its unique hardware identifier), vehicle registration details (name, plate number, brand, model), driver assignment, SIM card details, and subscription lifecycle timestamps. The current_mileage_km field reflects the latest odometer reading received from the device. At time of audit, 63 devices were registered, all in a single "Default group", but vehicle_name, vehicle_number, and driver_name were not yet populated — these need to be filled in for reports to be human-readable.

The fuel_100km column is particularly important: it is the reference value used to estimate fuel_consumed_l in the trips table. If it is null, fuel calculations will not work.

Describe table structure

\d tracksolid.devices

List all active devices with odometer readings

Returns all enabled devices ordered by highest mileage. Use this to identify high-utilisation vehicles that may need servicing, and to verify that vehicle metadata (name, plate, driver) has been filled in. Vehicles with very high odometer readings but blank names are a data quality flag.

SELECT
  imei,
  vehicle_name,
  vehicle_number,
  driver_name,
  mc_type,
  device_group,
  status,
  current_mileage_km,
  last_synced_at AT TIME ZONE 'Africa/Nairobi' AS last_synced_nairobi
FROM tracksolid.devices
WHERE enabled_flag = 1
ORDER BY current_mileage_km DESC NULLS LAST;

3. tracksolid.position_history

This is the core time-series table and is implemented as a TimescaleDB hypertable, automatically partitioned by time into chunks for efficient storage and querying of large volumes of GPS data. Every time the ingestion service polls the Tracksolid API (approximately every 1 minute), it writes a GPS breadcrumb for each active device into this table. Each row captures the device's location (latitude, longitude, and PostGIS geometry), speed, heading, ignition status, satellite count, and running odometer.

The acc_status field indicates whether the vehicle's ignition/accessory circuit is on (1) or off (0) at the time of the ping. The table uses a composite primary key of (imei, gps_time), ensuring no duplicate pings are stored. Older chunks are transparently compressed by TimescaleDB to save disk space.

Important: altitude is present in the schema but not currently populated by the ingestion pipeline.

Describe table structure

\d tracksolid.position_history

Most recent GPS pings per vehicle (Nairobi time)

Returns the latest position record for each device. Use this to quickly check which vehicles are currently active, where they last reported from, and whether the GPS lock is good (satellite count ≥ 8 is acceptable; ≥ 12 is excellent).

SELECT
  ph.imei,
  ph.gps_time AT TIME ZONE 'Africa/Nairobi' AS gps_nairobi,
  ph.lat,
  ph.lng,
  ph.speed,
  ph.direction,
  ph.acc_status,
  ph.current_mileage,
  ph.altitude,
  ph.satellite
FROM tracksolid.position_history ph
ORDER BY ph.gps_time DESC
LIMIT 15;

All GPS pings up to the current moment

Returns position history records with a gps_time strictly before NOW(). NOW() returns the current timestamp in UTC, which matches the stored timezone of the column directly — no conversion needed in the WHERE clause. This filter is useful when building live queries or scheduled reports that must not accidentally include future-dated records from a faulty device clock. Combine with a lower-bound interval to avoid scanning the entire table; the example below limits results to the last 24 hours.

SELECT
  ph.imei,
  ph.gps_time AT TIME ZONE 'Africa/Nairobi' AS gps_nairobi,
  ph.lat,
  ph.lng,
  ph.speed,
  ph.direction,
  ph.acc_status,
  ph.current_mileage,
  ph.altitude,
  ph.satellite
FROM tracksolid.position_history ph
WHERE ph.gps_time > NOW() - INTERVAL '24 hours'
  AND ph.gps_time < NOW()
ORDER BY ph.gps_time DESC;

To widen or narrow the window, replace the interval:

Interval Example
Last hour NOW() - INTERVAL '1 hour'
Last 24 hours NOW() - INTERVAL '24 hours'
Last 7 days NOW() - INTERVAL '7 days'
Since midnight Nairobi time See today's metrics section below

Full position trail for a specific vehicle on a given day

Replaces 'YOUR_IMEI_HERE' and the date with actual values. Useful for replaying a vehicle's route through the day, debugging missing trip segments, or feeding data into a mapping tool.

SELECT
  gps_time AT TIME ZONE 'Africa/Nairobi' AS gps_nairobi,
  lat,
  lng,
  speed,
  direction,
  acc_status,
  current_mileage,
  satellite
FROM tracksolid.position_history
WHERE imei = 'YOUR_IMEI_HERE'
  AND gps_time >= '2026-04-10 00:00:00+03'
  AND gps_time <  '2026-04-11 00:00:00+03'
ORDER BY gps_time ASC;

4. tracksolid.trips

The trips table stores auto-detected journey segments. A trip begins when the vehicle's ignition turns on and ends when it turns off again (or after a prolonged stationary period). Each row summarises one journey: start and end times, start and end coordinates, total distance, average and maximum speed, driving time, idle time, and estimated fuel consumption.

Note on distance_km: Stores trip distance directly in kilometres. Prior to migration 04 this column was named distance_m and incorrectly held millimetres due to an erroneous × 1000 in the ingestion code. Migration 04 corrected all historical rows (÷ 1,000,000) and renamed the column. Use the raw distance_km value in queries — no further division is needed.

At the time of audit, max_speed_kmh was null on all trips (not yet computed by the trip detection logic) and fuel_consumed_l was null because fuel_100km is not set on the devices.

Describe table structure

\d tracksolid.trips

Recent trips with full detail (Nairobi time)

Lists the 20 most recent trip records joined with vehicle metadata. Replace LIMIT 20 with a larger number or remove it to see more history. This is the primary query for reviewing daily driving activity.

SELECT
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  t.imei,
  t.start_time  AT TIME ZONE 'Africa/Nairobi' AS start_nairobi,
  t.end_time    AT TIME ZONE 'Africa/Nairobi' AS end_nairobi,
  ROUND(t.distance_km, 3)          AS distance_km,
  t.avg_speed_kmh,
  t.max_speed_kmh,
  ROUND(t.driving_time_s / 60.0, 1)           AS drive_min,
  ROUND(t.idle_time_s    / 60.0, 1)           AS idle_min,
  t.fuel_consumed_l
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
ORDER BY t.start_time DESC
LIMIT 20;

Verify distance accuracy (cross-check via speed × time)

Sanity-check query confirming that distance_km values are reasonable by comparing them against the expected distance derived from avg_speed_kmh × driving_time_s. After migration 04 the two columns should match closely (within a few percent). Run this after deploying updated ingestion containers or after any data correction.

SELECT
  t.imei,
  t.start_time AT TIME ZONE 'Africa/Nairobi'               AS start_nbi,
  t.end_time   AT TIME ZONE 'Africa/Nairobi'               AS end_nbi,
  ROUND(t.distance_km::numeric, 3)                          AS distance_km,
  t.avg_speed_kmh,
  t.driving_time_s,
  ROUND((t.avg_speed_kmh * t.driving_time_s / 3600.0), 3)  AS expected_km_from_speed,
  ROUND(
    ABS(t.distance_km - (t.avg_speed_kmh * t.driving_time_s / 3600.0))
    / NULLIF(t.avg_speed_kmh * t.driving_time_s / 3600.0, 0) * 100
  , 1)                                                      AS variance_pct
FROM tracksolid.trips t
WHERE t.avg_speed_kmh IS NOT NULL
  AND t.driving_time_s IS NOT NULL
  AND t.distance_km > 0
ORDER BY t.start_time DESC
LIMIT 10;

Speed band distribution across all trips

Categorises trips by average speed to understand whether the fleet primarily operates in congested urban conditions, normal urban flow, or open highway. The result helps benchmark expected journey times and assess whether vehicles are being used efficiently.

SELECT
  CASE
    WHEN avg_speed_kmh < 20  THEN '020 km/h  (slow / heavy traffic)'
    WHEN avg_speed_kmh < 40  THEN '2040 km/h (normal urban)'
    WHEN avg_speed_kmh < 60  THEN '4060 km/h (highway)'
    WHEN avg_speed_kmh < 80  THEN '6080 km/h (fast highway)'
    ELSE                          '80+ km/h   (very fast)'
  END AS speed_band,
  COUNT(*) AS trip_count
FROM tracksolid.trips
WHERE avg_speed_kmh IS NOT NULL
GROUP BY 1
ORDER BY trip_count DESC;

5. tracksolid.live_positions

This table holds one row per device and is continuously upserted with the latest known position every time the ingestion service polls the API. It is the equivalent of a "last known state" snapshot and is designed for real-time dashboard displays — you never need to scan the full position_history table just to find out where a vehicle currently is.

Beyond basic GPS coordinates, live_positions captures richer status fields than position_history: battery level (elec_quantity), external power voltage (power_value), device operational status (device_status), expiry and activation flags, and a human-readable address description (loc_desc). The tracker_oil field reflects whether the relay/immobiliser output is active.

Describe table structure

\d tracksolid.live_positions

Current status of all vehicles (Nairobi time)

Returns the latest known position and status for every device. Use this as the live fleet map feed. Vehicles with acc_status = '1' are currently running; '0' means engine off. Check expire_flag to catch devices whose subscriptions are about to lapse.

SELECT
  lp.imei,
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  lp.lat,
  lp.lng,
  lp.speed,
  lp.acc_status,
  lp.device_status,
  lp.gps_time     AT TIME ZONE 'Africa/Nairobi' AS gps_nairobi,
  lp.updated_at   AT TIME ZONE 'Africa/Nairobi' AS last_updated_nairobi,
  lp.elec_quantity,
  lp.power_value,
  lp.expire_flag,
  lp.loc_desc
FROM tracksolid.live_positions lp
LEFT JOIN tracksolid.devices d ON d.imei = lp.imei
ORDER BY lp.updated_at DESC;

6. tracksolid.alarms

The alarms table records every alarm event reported by the Tracksolid API for any device in the fleet. Alarm types can include overspeed, harsh braking, geofence violations, power disconnection, low battery, tampering, and more — the exact set depends on the tracker model and account configuration. Each alarm record captures the device IMEI, alarm type and name, the timestamp and GPS coordinates at which the alarm fired, the vehicle's speed at that moment, and ignition status.

Fixed [FIX-E06]: At the time of the initial audit all 1,054 records had alarm_type and alarm_name null because the polling code was reading webhook field names (alarmType, alarmName, alarmTime) instead of the poll API field names (alertTypeId, alarmTypeName, alertTime). This has been corrected in ingest_events_rev.py. New alarm records ingested after the fix will contain classified alarm types. Migration 05 also added severity, geofence_id, geofence_name, acknowledged_at, and acknowledged_by columns for richer alarm management.

Describe table structure

\d tracksolid.alarms

Recent alarms with Nairobi timestamp

Returns the most recent alarm events. Once alarm_type and alarm_name are being populated correctly, this query becomes the primary tool for investigating safety incidents, policy violations, and device health events.

SELECT
  a.imei,
  d.vehicle_name,
  d.driver_name,
  a.alarm_time AT TIME ZONE 'Africa/Nairobi' AS alarm_nairobi,
  a.alarm_type,
  a.alarm_name,
  a.speed,
  a.acc_status,
  a.lat,
  a.lng,
  a.source
FROM tracksolid.alarms a
LEFT JOIN tracksolid.devices d ON d.imei = a.imei
ORDER BY a.alarm_time DESC
LIMIT 50;

Alarm frequency by type

Once alarm types are populated, this aggregation shows which alarm categories are most common across the fleet. High overspeed counts flag driver behaviour issues; high power-disconnect counts may indicate tampering or device faults.

SELECT
  alarm_type,
  alarm_name,
  COUNT(*)                                              AS total_alarms,
  COUNT(DISTINCT imei)                                  AS vehicles_affected,
  MIN(alarm_time AT TIME ZONE 'Africa/Nairobi')         AS first_seen,
  MAX(alarm_time AT TIME ZONE 'Africa/Nairobi')         AS last_seen
FROM tracksolid.alarms
GROUP BY alarm_type, alarm_name
ORDER BY total_alarms DESC;

7. tracksolid.ingestion_log

The ingestion log is the pipeline health ledger. Every time the ingestion service calls a Tracksolid API endpoint, it writes one row here recording: which endpoint was called, how many device IMEIs were processed, how many rows were inserted or upserted into the database, how long the call took (in milliseconds), and whether it succeeded. If a call fails, success is set to false and the error details are captured in error_code and error_message.

This table is the first place to check when troubleshooting missing data or unexpected gaps in position history. It tells you definitively whether the pipeline ran and what it ingested, as opposed to whether the GPS device itself was transmitting.

Describe table structure

\d tracksolid.ingestion_log

Pipeline health summary by endpoint

Aggregates the ingestion log by endpoint to show total run counts, data volumes, average call duration, and failure rate. A non-zero failure_count needs investigation. A sudden drop in avg_rows_per_run compared to historical baseline may indicate the API rate limit was hit or devices went offline.

SELECT
  endpoint,
  COUNT(*)                                              AS runs,
  SUM(rows_inserted)                                    AS total_inserted,
  SUM(rows_upserted)                                    AS total_upserted,
  ROUND(AVG(rows_inserted))                             AS avg_rows_per_run,
  ROUND(AVG(duration_ms))                               AS avg_duration_ms,
  MAX(duration_ms)                                      AS max_duration_ms,
  SUM(CASE WHEN NOT success THEN 1 ELSE 0 END)          AS failure_count,
  MIN(run_at AT TIME ZONE 'Africa/Nairobi')              AS first_run,
  MAX(run_at AT TIME ZONE 'Africa/Nairobi')              AS last_run
FROM tracksolid.ingestion_log
GROUP BY endpoint
ORDER BY runs DESC;

Recent pipeline failures

Filters the log to show only failed ingestion runs. Run this immediately when investigating data gaps or alert emails from the pipeline. The error_message column usually contains the HTTP status code and API error body.

SELECT
  run_at AT TIME ZONE 'Africa/Nairobi' AS run_nairobi,
  endpoint,
  imei_count,
  rows_inserted,
  duration_ms,
  error_code,
  error_message
FROM tracksolid.ingestion_log
WHERE success = false
ORDER BY run_at DESC
LIMIT 50;

8. tracksolid.heartbeats

The heartbeats table is designed to record periodic keep-alive signals sent by GPS devices when they are stationary for extended periods. Rather than inserting a full position record every minute even when nothing has changed, some tracker firmware sends a lightweight heartbeat ping to confirm the device is still powered on and connected. This can be used to distinguish "vehicle parked with tracker alive" from "tracker lost power or signal".

At the time of audit this table contained 0 rows. Either the tracker models currently deployed do not send heartbeat packets, or the heartbeat ingestion endpoint has not yet been implemented.

Describe table structure

\d tracksolid.heartbeats

9. tracksolid.obd_readings

The OBD (On-Board Diagnostics) readings table is intended to store data pulled from a vehicle's OBD-II port via a compatible tracker. OBD data can include engine RPM, coolant temperature, throttle position, fuel level, battery voltage, and diagnostic trouble codes. This information is valuable for predictive maintenance and driver behaviour scoring beyond what GPS alone provides.

At the time of audit this table contained 0 rows. The JC400P and X3 tracker models in the fleet may support OBD connectivity, but either the OBD cables are not installed or the OBD data ingestion pipeline has not been built yet.

Describe table structure

\d tracksolid.obd_readings

10. tracksolid.parking_events

This table is designed to store discrete parking events — records of when a vehicle stopped and for how long. Rather than deriving parking from position history (which requires scanning many rows), the Tracksolid API's jimi.open.platform.report.parking endpoint provides pre-computed parking events. These are useful for calculating driver wait times, identifying vehicles left idle in locations for long periods, and auditing whether vehicles are parked at authorised locations overnight.

Fixed [FIX-M13]: At the time of the initial audit this table contained 0 rows despite 358 successful API calls. The root cause was two missing parameters in the request — account (required to scope results to the fleet) and acc_type=0 (required to include all stop types). Additionally, the response field durSecond was being read as seconds, causing duration to always be null. All three issues are corrected in ingest_movement_rev.py. Parking events should now populate on the next poll cycle.

Describe table structure

\d tracksolid.parking_events

11. tracksolid.fault_codes

The fault codes table stores vehicle diagnostic trouble codes (DTCs) received from OBD-connected trackers. When a vehicle's engine management system logs a fault (e.g. P0300 for a misfire, P0171 for a lean fuel mixture), the tracker reads it via the OBD port and transmits it to the Tracksolid platform, from where it is ingested into this table. This enables remote fleet health monitoring without requiring drivers to visit a workshop.

At the time of audit this table contained 0 rows, which is consistent with the OBD readings table also being empty. Fault code ingestion depends on OBD connectivity being established first.

Describe table structure

\d tracksolid.fault_codes

12. dwh_gold.dim_vehicles

This is the vehicles dimension table in the data warehouse gold layer. It is intended to be a clean, enriched, business-friendly view of the fleet — combining device metadata from tracksolid.devices with any additional attributes needed for reporting (cost centre, vehicle category, assigned route, etc.). Dimension tables in a star schema are typically populated by an ETL job that joins and transforms raw operational tables.

At the time of audit this table contained 0 rows. The ETL pipeline that populates the gold layer has not yet been run.

Describe table structure

\d dwh_gold.dim_vehicles

13. dwh_gold.fact_daily_fleet_metrics

This is the central fact table of the data warehouse. It is designed to hold one pre-aggregated row per vehicle per day, summarising distance driven, fuel consumed, driving time, idle time, trip count, first departure time, last return time, and alarm counts. Pre-aggregating at this level makes dashboards and management reports extremely fast — a full month's fleet summary requires scanning at most 63 vehicles × 31 days = ~2,000 rows rather than hundreds of thousands of raw trip and position records.

At the time of audit this table contained 0 rows. Once the ETL job is running, this should be the primary data source for all Grafana dashboards and business reports.

Describe table structure

\d dwh_gold.fact_daily_fleet_metrics

14. Business Intelligence Queries

Daily work start/end times and driving summary per vehicle (Nairobi time)

This is the primary operational report query. For each vehicle on each working day, it computes: the time the first trip of the day began (proxy for "driver started work"), the time the last trip ended (proxy for "driver finished work"), total distance driven, number of trips made, total driving time, and total idle time. Join with tracksolid.devices to add vehicle names and driver names once those fields are populated.

The query groups by IMEI and calendar date in Nairobi time, so a trip that starts at 23:58 EAT correctly belongs to that day rather than rolling over to the next UTC day.

WITH daily AS (
  SELECT
    t.imei,
    d.vehicle_name,
    d.vehicle_number,
    d.driver_name,
    DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')          AS work_date,
    MIN(t.start_time AT TIME ZONE 'Africa/Nairobi')            AS first_trip_start,
    MAX(t.end_time   AT TIME ZONE 'Africa/Nairobi')            AS last_trip_end,
    ROUND(SUM(t.distance_km)::numeric, 2)                   AS total_km,
    COUNT(*)                                                   AS trip_count,
    ROUND(SUM(t.driving_time_s) / 60.0, 1)                    AS total_drive_min,
    ROUND(SUM(t.idle_time_s)    / 60.0, 1)                    AS total_idle_min,
    ROUND(AVG(t.avg_speed_kmh)::numeric, 2)                   AS avg_speed_kmh,
    MAX(t.max_speed_kmh)                                       AS peak_speed_kmh
  FROM tracksolid.trips t
  JOIN tracksolid.devices d ON d.imei = t.imei
  WHERE t.end_time IS NOT NULL
  GROUP BY
    t.imei, d.vehicle_name, d.vehicle_number, d.driver_name,
    DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')
)
SELECT
  work_date,
  imei,
  vehicle_name,
  vehicle_number,
  driver_name,
  TO_CHAR(first_trip_start, 'HH24:MI')  AS day_start,
  TO_CHAR(last_trip_end,    'HH24:MI')  AS day_end,
  EXTRACT(EPOCH FROM (last_trip_end - first_trip_start)) / 3600.0 AS operational_hours,
  total_km,
  trip_count,
  total_drive_min,
  total_idle_min,
  avg_speed_kmh,
  peak_speed_kmh
FROM daily
ORDER BY work_date DESC, imei;

Fleet summary for a date range

Aggregates across all vehicles and all days within a date range to give a high-level fleet utilisation report. Useful for weekly or monthly management summaries. Adjust the date range in the WHERE clause as needed.

SELECT
  DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')  AS work_date,
  COUNT(DISTINCT t.imei)                             AS active_vehicles,
  COUNT(*)                                           AS total_trips,
  ROUND(SUM(t.distance_km)::numeric, 2)           AS total_fleet_km,
  ROUND(AVG(t.avg_speed_kmh)::numeric, 2)            AS fleet_avg_speed_kmh,
  ROUND(SUM(t.driving_time_s) / 3600.0, 2)          AS total_drive_hours,
  ROUND(SUM(t.idle_time_s)    / 3600.0, 2)          AS total_idle_hours
FROM tracksolid.trips t
WHERE t.start_time >= '2026-04-09 00:00:00+03'
  AND t.start_time <  '2026-04-11 00:00:00+03'
  AND t.end_time IS NOT NULL
GROUP BY DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')
ORDER BY work_date;

Idle time analysis — vehicles spending excessive time idling

Identifies trips where the vehicle spent more than 20% of its time stationary with the engine running. Excessive idling wastes fuel and is often a sign of drivers waiting in traffic, sitting at customer sites, or running the air conditioning while parked. The threshold (20%) can be adjusted.

SELECT
  t.imei,
  d.vehicle_name,
  d.driver_name,
  t.start_time AT TIME ZONE 'Africa/Nairobi'   AS trip_start,
  ROUND(t.driving_time_s / 60.0, 1)            AS drive_min,
  ROUND(t.idle_time_s    / 60.0, 1)            AS idle_min,
  ROUND(
    100.0 * t.idle_time_s /
    NULLIF(t.driving_time_s + t.idle_time_s, 0)
  , 1)                                         AS idle_pct,
  ROUND(t.distance_km, 3)           AS distance_km
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
WHERE t.idle_time_s IS NOT NULL
  AND t.driving_time_s IS NOT NULL
  AND (t.idle_time_s + t.driving_time_s) > 0
  AND (100.0 * t.idle_time_s / (t.driving_time_s + t.idle_time_s)) > 20
ORDER BY idle_pct DESC;

Vehicles with no activity today (Nairobi time)

Returns devices that are registered and active but have not started a trip today. Useful for daily fleet readiness checks — if a vehicle was expected to be operational but appears here, investigate whether the device is offline, the vehicle is in the workshop, or the driver has not started work.

SELECT
  d.imei,
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  d.current_mileage_km,
  d.last_synced_at AT TIME ZONE 'Africa/Nairobi' AS last_synced_nairobi
FROM tracksolid.devices d
WHERE d.enabled_flag = 1
  AND d.imei NOT IN (
    SELECT DISTINCT imei
    FROM tracksolid.trips
    WHERE DATE(start_time AT TIME ZONE 'Africa/Nairobi') = CURRENT_DATE AT TIME ZONE 'Africa/Nairobi'
  )
ORDER BY d.vehicle_name NULLS LAST;

15. Today's Metrics — From 00:00 Nairobi Time to Now

All queries in this section use a dynamic time window that opens at midnight Nairobi time on the current calendar day and closes at NOW(). This means they are safe to run at any point during the day and will always reflect activity from the start of the working day up to the present moment. The anchor expression used throughout is:

DATE_TRUNC('day', NOW() AT TIME ZONE 'Africa/Nairobi') AT TIME ZONE 'Africa/Nairobi'

This evaluates to today's 00:00:00 +03:00 in UTC, regardless of when the query is run. Pairing it with < NOW() ensures only records that have actually arrived are included.

GPS pings since midnight (position_history)

Returns every position breadcrumb recorded today for all vehicles, from the first ping after midnight Nairobi time up to the current moment. Useful for replaying today's movement on a map or confirming that all devices are actively reporting.

SELECT
  ph.imei,
  ph.gps_time AT TIME ZONE 'Africa/Nairobi' AS gps_nairobi,
  ph.lat,
  ph.lng,
  ph.speed,
  ph.direction,
  ph.acc_status,
  ph.current_mileage,
  ph.satellite
FROM tracksolid.position_history ph
WHERE ph.gps_time >= DATE_TRUNC('day', NOW() AT TIME ZONE 'Africa/Nairobi')
                     AT TIME ZONE 'Africa/Nairobi'
  AND ph.gps_time <  NOW()
ORDER BY ph.gps_time DESC;

Today's trips per vehicle (trips)

Lists every completed trip recorded today, joined with device metadata. Because the day is still in progress, some vehicles may have open trips (where end_time IS NULL) — the WHERE clause includes both completed and in-progress trips so nothing is missed. The end_time IS NULL check in the display makes it easy to spot which trips are still open.

SELECT
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  t.imei,
  t.start_time AT TIME ZONE 'Africa/Nairobi'       AS start_nairobi,
  t.end_time   AT TIME ZONE 'Africa/Nairobi'       AS end_nairobi,
  CASE WHEN t.end_time IS NULL THEN 'IN PROGRESS' ELSE 'COMPLETE' END AS status,
  ROUND(t.distance_km, 3)               AS distance_km,
  t.avg_speed_kmh,
  ROUND(t.driving_time_s / 60.0, 1)                AS drive_min,
  ROUND(t.idle_time_s    / 60.0, 1)                AS idle_min
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
WHERE t.start_time >= DATE_TRUNC('day', NOW() AT TIME ZONE 'Africa/Nairobi')
                      AT TIME ZONE 'Africa/Nairobi'
  AND t.start_time <  NOW()
ORDER BY t.start_time DESC;

Today's driving summary per vehicle (aggregated)

Rolls up all of today's trips into one row per vehicle. Shows the time the driver first moved (day start), the most recent trip end time (last known activity), total kilometres driven so far, total trips made, and cumulative driving and idle minutes. The MAX(t.end_time) will reflect the end of the last completed trip — if the vehicle is currently mid-trip that trip's distance and time will not yet appear here until the trip closes.

WITH today_start AS (
  SELECT DATE_TRUNC('day', NOW() AT TIME ZONE 'Africa/Nairobi')
         AT TIME ZONE 'Africa/Nairobi' AS ts
)
SELECT
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  t.imei,
  TO_CHAR(MIN(t.start_time AT TIME ZONE 'Africa/Nairobi'), 'HH24:MI') AS day_start,
  TO_CHAR(MAX(t.end_time   AT TIME ZONE 'Africa/Nairobi'), 'HH24:MI') AS last_activity,
  COUNT(*)                                                              AS trips_so_far,
  ROUND(SUM(t.distance_km)::numeric, 2)                             AS total_km,
  ROUND(SUM(t.driving_time_s) / 60.0, 1)                              AS total_drive_min,
  ROUND(SUM(t.idle_time_s)    / 60.0, 1)                              AS total_idle_min,
  ROUND(AVG(t.avg_speed_kmh)::numeric, 2)                             AS avg_speed_kmh
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
CROSS JOIN today_start ts
WHERE t.start_time >= ts.ts
  AND t.start_time <  NOW()
GROUP BY t.imei, d.vehicle_name, d.vehicle_number, d.driver_name
ORDER BY total_km DESC;

Today's alarms so far

Returns all alarm events triggered from midnight Nairobi time to the present moment. Once alarm_type is being populated correctly this becomes the real-time safety dashboard — a high count of overspeed or harsh-braking alarms early in the day is an early warning signal.

SELECT
  a.imei,
  d.vehicle_name,
  d.driver_name,
  a.alarm_time AT TIME ZONE 'Africa/Nairobi' AS alarm_nairobi,
  a.alarm_type,
  a.alarm_name,
  a.speed,
  a.lat,
  a.lng
FROM tracksolid.alarms a
LEFT JOIN tracksolid.devices d ON d.imei = a.imei
WHERE a.alarm_time >= DATE_TRUNC('day', NOW() AT TIME ZONE 'Africa/Nairobi')
                      AT TIME ZONE 'Africa/Nairobi'
  AND a.alarm_time <  NOW()
ORDER BY a.alarm_time DESC;

Fleet totals for today at a glance

A single-row summary of the entire fleet's activity since midnight. Designed for a top-of-dashboard KPI card: how many vehicles have moved today, how many trips have been completed, total fleet kilometres, and total driving hours so far.

WITH today_start AS (
  SELECT DATE_TRUNC('day', NOW() AT TIME ZONE 'Africa/Nairobi')
         AT TIME ZONE 'Africa/Nairobi' AS ts
)
SELECT
  COUNT(DISTINCT t.imei)                           AS active_vehicles_today,
  COUNT(*)                                         AS total_trips_today,
  ROUND(SUM(t.distance_km)::numeric, 2)         AS total_fleet_km,
  ROUND(SUM(t.driving_time_s) / 3600.0, 2)        AS total_drive_hours,
  ROUND(SUM(t.idle_time_s)    / 3600.0, 2)        AS total_idle_hours,
  ROUND(AVG(t.avg_speed_kmh)::numeric, 2)          AS fleet_avg_speed_kmh,
  TO_CHAR(NOW() AT TIME ZONE 'Africa/Nairobi', 'HH24:MI') AS as_at_nairobi
FROM tracksolid.trips t
CROSS JOIN today_start ts
WHERE t.start_time >= ts.ts
  AND t.start_time <  NOW();

20. New Tables from Migration 05

Migration 05 adds the following tables to support expanded webhook ingestion and enriched reporting. All tables have corresponding handlers in webhook_receiver_rev.py or are populated via ETL.

tracksolid.device_events

Records device network login and logout events received via the /pushevent webhook. Distinguishes "vehicle parked, tracker alive" from "tracker lost power or was disconnected". Each row captures the device IMEI, whether it connected (LOGIN) or disconnected (LOGOUT), the event timestamp, and the device's reported timezone. Use this table to calculate true uptime per device and to detect potential tampering (unexpected LOGOUT during operational hours).

SELECT
  de.imei,
  d.vehicle_name,
  de.event_type,
  de.event_time AT TIME ZONE 'Africa/Nairobi' AS event_nairobi,
  de.timezone
FROM tracksolid.device_events de
LEFT JOIN tracksolid.devices d ON d.imei = de.imei
ORDER BY de.event_time DESC
LIMIT 50;

tracksolid.fuel_readings

Stores fuel and oil sensor readings from the /pushoil webhook. Relevant for vehicles equipped with a direct fuel tank sensor — these report tank level (in cm depth, percentage, voltage, or litres depending on sensor type). Sudden unexplained drops in fuel level between readings indicate possible fuel theft. Each reading includes the sensor channel ID (sensor_path), the measured value, the unit, and the GPS position at time of reading.

SELECT
  fr.imei,
  d.vehicle_name,
  fr.reading_time AT TIME ZONE 'Africa/Nairobi' AS reading_nairobi,
  fr.sensor_path,
  fr.value,
  fr.unit,
  fr.lat,
  fr.lng
FROM tracksolid.fuel_readings fr
LEFT JOIN tracksolid.devices d ON d.imei = fr.imei
ORDER BY fr.reading_time DESC
LIMIT 50;

tracksolid.temperature_readings

Stores cabin or cargo temperature and humidity readings from the /pushtem webhook. Critical for cold-chain logistics where temperature excursions during transit can compromise cargo. Each row holds the sensor reading at a point in time per device. Pair with position_history to map temperature along a route.

SELECT
  tr.imei,
  d.vehicle_name,
  tr.reading_time AT TIME ZONE 'Africa/Nairobi' AS reading_nairobi,
  tr.temperature,
  tr.humidity_pct
FROM tracksolid.temperature_readings tr
LEFT JOIN tracksolid.devices d ON d.imei = tr.imei
ORDER BY tr.reading_time DESC
LIMIT 50;

tracksolid.lbs_readings

Stores cell tower and WiFi-based positioning data from the /pushlbs webhook. Used as a fallback when GPS signal is unavailable (tunnels, underground parking, dense urban areas). The raw cell data is stored as JSONB in lbs_data containing MCC, MNC, and cell tower list which can be forwarded to a geocell API (e.g. Google Geolocation, HERE) for approximate coordinate resolution.

SELECT
  lr.imei,
  d.vehicle_name,
  lr.gate_time AT TIME ZONE 'Africa/Nairobi' AS gate_nairobi,
  lr.post_type,
  lr.lbs_data
FROM tracksolid.lbs_readings lr
LEFT JOIN tracksolid.devices d ON d.imei = lr.imei
ORDER BY lr.gate_time DESC
LIMIT 50;

tracksolid.geofences

Stores geofence boundary definitions synced from the Tracksolid platform. Each row represents a named geographic boundary (circle or polygon) used for arrival/departure alerting. Once populated, this table enables joining alarm events to geofence names so that tracksolid.alarms.geofence_name is human-readable rather than just an ID.

SELECT
  fence_id,
  fence_name,
  fence_type,
  radius_m,
  description,
  created_at AT TIME ZONE 'Africa/Nairobi' AS created_nairobi
FROM tracksolid.geofences
ORDER BY created_at DESC;

16. Daily Analytics

Daily analytics answer the question: "What happened across the fleet today?" All queries use Africa/Nairobi timezone so that a working day (typically 08:0020:00 EAT) maps cleanly to a single calendar date. The dwh_gold.refresh_daily_metrics() function pre-aggregates these into fact_daily_fleet_metrics for fast dashboard queries — run it nightly for the previous day.

Day summary — all vehicles, single date

Replace the date literal with any past date or use CURRENT_DATE - 1 for yesterday. This is the primary end-of-day report: who drove, how far, when they started and finished, and how much time was wasted idling.

SELECT
  DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')          AS work_date,
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  t.imei,
  TO_CHAR(MIN(t.start_time AT TIME ZONE 'Africa/Nairobi'), 'HH24:MI') AS day_start,
  TO_CHAR(MAX(t.end_time   AT TIME ZONE 'Africa/Nairobi'), 'HH24:MI') AS day_end,
  ROUND(
    EXTRACT(EPOCH FROM (MAX(t.end_time) - MIN(t.start_time))) / 3600.0
  , 2)                                                       AS operational_hours,
  COUNT(*)                                                   AS trips,
  ROUND(SUM(t.distance_km)::numeric, 2)                     AS total_km,
  ROUND(SUM(t.driving_time_s) / 3600.0, 2)                  AS drive_hours,
  ROUND(SUM(t.idle_time_s)    / 3600.0, 2)                  AS idle_hours,
  ROUND(
    100.0 * SUM(t.idle_time_s) /
    NULLIF(SUM(t.driving_time_s) + SUM(t.idle_time_s), 0)
  , 1)                                                       AS idle_pct,
  ROUND(AVG(t.avg_speed_kmh)::numeric, 2)                   AS avg_speed_kmh,
  SUM(t.fuel_consumed_l)                                     AS fuel_l
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
WHERE DATE(t.start_time AT TIME ZONE 'Africa/Nairobi') = '2026-04-10'
  AND t.end_time IS NOT NULL
GROUP BY 1, d.vehicle_name, d.vehicle_number, d.driver_name, t.imei
ORDER BY total_km DESC;

Daily alarm summary

Counts alarm events per vehicle per type for a single day. Used in end-of-day driver behaviour reporting. Sort by total alarms to surface the most at-risk drivers.

SELECT
  DATE(a.alarm_time AT TIME ZONE 'Africa/Nairobi') AS alarm_date,
  d.vehicle_name,
  d.driver_name,
  a.imei,
  a.alarm_type,
  a.alarm_name,
  COUNT(*)                                          AS alarm_count,
  MAX(a.speed)                                      AS max_speed_at_alarm
FROM tracksolid.alarms a
LEFT JOIN tracksolid.devices d ON d.imei = a.imei
WHERE DATE(a.alarm_time AT TIME ZONE 'Africa/Nairobi') = '2026-04-10'
GROUP BY 1, d.vehicle_name, d.driver_name, a.imei, a.alarm_type, a.alarm_name
ORDER BY alarm_count DESC;

Vehicles not used today

Flags registered vehicles that had zero trips on a given day. Cross-reference with heartbeats and live_positions to determine if the device was online (vehicle was parked) or offline (device problem).

SELECT
  d.imei,
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  d.current_mileage_km,
  lp.device_status,
  lp.updated_at AT TIME ZONE 'Africa/Nairobi' AS last_seen_nairobi
FROM tracksolid.devices d
LEFT JOIN tracksolid.live_positions lp ON lp.imei = d.imei
WHERE d.enabled_flag = 1
  AND d.imei NOT IN (
    SELECT DISTINCT imei FROM tracksolid.trips
    WHERE DATE(start_time AT TIME ZONE 'Africa/Nairobi') = '2026-04-10'
  )
ORDER BY d.vehicle_name NULLS LAST;

Daily parking events summary

Lists all parking stops recorded in a day with duration and location. Highlights vehicles parked for extended periods at unplanned locations. A stop longer than 2 hours away from the depot during working hours warrants investigation.

SELECT
  d.vehicle_name,
  d.driver_name,
  p.imei,
  p.start_time AT TIME ZONE 'Africa/Nairobi' AS parked_at,
  p.end_time   AT TIME ZONE 'Africa/Nairobi' AS departed_at,
  ROUND(p.duration_seconds / 60.0, 1)         AS duration_min,
  p.address
FROM tracksolid.parking_events p
LEFT JOIN tracksolid.devices d ON d.imei = p.imei
WHERE DATE(p.start_time AT TIME ZONE 'Africa/Nairobi') = '2026-04-10'
ORDER BY p.duration_seconds DESC;

First departure and last return — all vehicles

Gives the operational window for each vehicle: when the first trip began (proxy for driver start time) and when the last trip ended (proxy for sign-off time). Used to enforce working-hours policy and detect unauthorised after-hours use.

SELECT
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  t.imei,
  TO_CHAR(MIN(t.start_time AT TIME ZONE 'Africa/Nairobi'), 'HH24:MI') AS first_departure,
  TO_CHAR(MAX(t.end_time   AT TIME ZONE 'Africa/Nairobi'), 'HH24:MI') AS last_return,
  ROUND(
    EXTRACT(EPOCH FROM (MAX(t.end_time) - MIN(t.start_time))) / 3600.0
  , 2)                                                                  AS operational_hours
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
WHERE DATE(t.start_time AT TIME ZONE 'Africa/Nairobi') = '2026-04-10'
  AND t.end_time IS NOT NULL
GROUP BY d.vehicle_name, d.vehicle_number, d.driver_name, t.imei
ORDER BY first_departure;

17. Weekly Analytics

Weekly analytics answer: "How did the fleet perform over the past 7 days?" Replace the date range with the desired ISO week or rolling window. For MondaySunday weeks use DATE_TRUNC('week', ...).

Weekly driving summary per vehicle

One row per vehicle per week. Shows total km, trips, drive hours, idle ratio, and average speed. Useful for identifying vehicles consistently underperforming or overloading drivers.

WITH week_range AS (
  SELECT
    DATE_TRUNC('week', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
      AT TIME ZONE 'Africa/Nairobi'                          AS week_start,
    DATE_TRUNC('week', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
      AT TIME ZONE 'Africa/Nairobi' + INTERVAL '7 days'     AS week_end
)
SELECT
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  t.imei,
  COUNT(*)                                                   AS total_trips,
  ROUND(SUM(t.distance_km)::numeric, 2)                     AS total_km,
  ROUND(SUM(t.driving_time_s) / 3600.0, 2)                  AS drive_hours,
  ROUND(SUM(t.idle_time_s)    / 3600.0, 2)                  AS idle_hours,
  ROUND(
    100.0 * SUM(t.idle_time_s) /
    NULLIF(SUM(t.driving_time_s) + SUM(t.idle_time_s), 0)
  , 1)                                                       AS idle_pct,
  ROUND(AVG(t.avg_speed_kmh)::numeric, 2)                   AS avg_speed_kmh,
  COUNT(DISTINCT DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')) AS active_days,
  ROUND(SUM(t.fuel_consumed_l)::numeric, 2)                 AS fuel_l
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
CROSS JOIN week_range wr
WHERE t.start_time >= wr.week_start
  AND t.start_time <  wr.week_end
  AND t.end_time IS NOT NULL
GROUP BY d.vehicle_name, d.vehicle_number, d.driver_name, t.imei
ORDER BY total_km DESC;

Weekly alarm league table — by driver

Ranks drivers by total alarm count for the week. Helps safety managers identify drivers needing coaching. Break down by alarm_type to differentiate overspeed from geofence violations vs. device health alerts.

WITH week_range AS (
  SELECT
    DATE_TRUNC('week', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
      AT TIME ZONE 'Africa/Nairobi'                          AS week_start,
    DATE_TRUNC('week', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
      AT TIME ZONE 'Africa/Nairobi' + INTERVAL '7 days'     AS week_end
)
SELECT
  d.driver_name,
  d.vehicle_name,
  a.imei,
  COUNT(*)                                                   AS total_alarms,
  COUNT(*) FILTER (WHERE a.alarm_type ILIKE '%speed%')       AS overspeed,
  COUNT(*) FILTER (WHERE a.alarm_type ILIKE '%geofence%'
                      OR a.alarm_type ILIKE '%fence%')       AS geofence,
  COUNT(*) FILTER (WHERE a.alarm_type ILIKE '%power%'
                      OR a.alarm_type ILIKE '%battery%')     AS device_health
FROM tracksolid.alarms a
LEFT JOIN tracksolid.devices d ON d.imei = a.imei
CROSS JOIN week_range wr
WHERE a.alarm_time >= wr.week_start
  AND a.alarm_time <  wr.week_end
GROUP BY d.driver_name, d.vehicle_name, a.imei
ORDER BY total_alarms DESC;

Weekly idle time analysis — worst offenders

Surfaces vehicles where idle time exceeded 25% of total engine-on time across the week. Sustained high idle ratios point to route inefficiencies, long customer wait times, or drivers keeping engines running while stationary.

WITH week_range AS (
  SELECT
    DATE_TRUNC('week', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
      AT TIME ZONE 'Africa/Nairobi'                         AS week_start,
    DATE_TRUNC('week', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
      AT TIME ZONE 'Africa/Nairobi' + INTERVAL '7 days'    AS week_end
)
SELECT
  d.vehicle_name,
  d.driver_name,
  t.imei,
  ROUND(SUM(t.driving_time_s) / 3600.0, 2)                 AS drive_hours,
  ROUND(SUM(t.idle_time_s)    / 3600.0, 2)                 AS idle_hours,
  ROUND(
    100.0 * SUM(t.idle_time_s) /
    NULLIF(SUM(t.driving_time_s) + SUM(t.idle_time_s), 0)
  , 1)                                                      AS idle_pct,
  ROUND(SUM(t.distance_km)::numeric, 2)                    AS total_km
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
CROSS JOIN week_range wr
WHERE t.start_time >= wr.week_start
  AND t.start_time <  wr.week_end
  AND t.end_time IS NOT NULL
GROUP BY d.vehicle_name, d.driver_name, t.imei
HAVING
  100.0 * SUM(t.idle_time_s) /
  NULLIF(SUM(t.driving_time_s) + SUM(t.idle_time_s), 0) > 25
ORDER BY idle_pct DESC;

Weekly day-by-day fleet activity

Shows fleet-wide totals for each day of the current week. Good for identifying quiet days (public holidays, weather events) or usage patterns (e.g. Saturdays consistently low).

WITH week_range AS (
  SELECT
    DATE_TRUNC('week', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
      AT TIME ZONE 'Africa/Nairobi'                         AS week_start,
    DATE_TRUNC('week', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
      AT TIME ZONE 'Africa/Nairobi' + INTERVAL '7 days'    AS week_end
)
SELECT
  DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')         AS work_date,
  TO_CHAR(DATE(t.start_time AT TIME ZONE 'Africa/Nairobi'), 'Day') AS day_name,
  COUNT(DISTINCT t.imei)                                    AS active_vehicles,
  COUNT(*)                                                   AS trips,
  ROUND(SUM(t.distance_km)::numeric, 2)                    AS fleet_km,
  ROUND(SUM(t.driving_time_s) / 3600.0, 2)                 AS drive_hours,
  ROUND(SUM(t.idle_time_s)    / 3600.0, 2)                 AS idle_hours
FROM tracksolid.trips t
CROSS JOIN week_range wr
WHERE t.start_time >= wr.week_start
  AND t.start_time <  wr.week_end
  AND t.end_time IS NOT NULL
GROUP BY DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')
ORDER BY work_date;

18. Monthly Analytics

Monthly analytics answer: "How did the fleet perform this month?" Queries use DATE_TRUNC('month', ...) to anchor to the first of the current month. For historical months replace with a specific date literal. When dwh_gold.fact_daily_fleet_metrics is fully populated these queries can be rewritten against that table for much faster execution.

Monthly summary per vehicle

The primary monthly management report. One row per vehicle for the selected month, showing utilisation, distance, fuel, and alarm counts side by side. Replace the date with the first day of the target month.

SELECT
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  t.imei,
  COUNT(DISTINCT DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')) AS active_days,
  COUNT(*)                                                          AS total_trips,
  ROUND(SUM(t.distance_km)::numeric, 2)                           AS total_km,
  ROUND(SUM(t.driving_time_s) / 3600.0, 2)                        AS drive_hours,
  ROUND(SUM(t.idle_time_s)    / 3600.0, 2)                        AS idle_hours,
  ROUND(
    100.0 * SUM(t.idle_time_s) /
    NULLIF(SUM(t.driving_time_s) + SUM(t.idle_time_s), 0)
  , 1)                                                             AS idle_pct,
  ROUND(AVG(t.avg_speed_kmh)::numeric, 2)                         AS avg_speed_kmh,
  ROUND(SUM(t.fuel_consumed_l)::numeric, 2)                       AS fuel_l,
  ROUND(
    SUM(t.fuel_consumed_l) / NULLIF(SUM(t.distance_km), 0) * 100
  , 2)                                                             AS fuel_per_100km
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
WHERE t.start_time >= DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                        AT TIME ZONE 'Africa/Nairobi'
  AND t.start_time <  DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                        AT TIME ZONE 'Africa/Nairobi' + INTERVAL '1 month'
  AND t.end_time IS NOT NULL
GROUP BY d.vehicle_name, d.vehicle_number, d.driver_name, t.imei
ORDER BY total_km DESC;

Monthly odometer progression

Shows how much each vehicle's cumulative mileage grew during the month, derived from the difference between the earliest and latest current_mileage in position_history. Cross-check against trips.distance_km totals to validate data consistency.

SELECT
  ph.imei,
  d.vehicle_name,
  MIN(ph.current_mileage)                                          AS mileage_start,
  MAX(ph.current_mileage)                                          AS mileage_end,
  ROUND((MAX(ph.current_mileage) - MIN(ph.current_mileage))::numeric, 2) AS km_added
FROM tracksolid.position_history ph
JOIN tracksolid.devices d ON d.imei = ph.imei
WHERE ph.gps_time >= DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                      AT TIME ZONE 'Africa/Nairobi'
  AND ph.gps_time <  DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                      AT TIME ZONE 'Africa/Nairobi' + INTERVAL '1 month'
GROUP BY ph.imei, d.vehicle_name
ORDER BY km_added DESC;

Breaks the month into ISO weeks to show whether alarm frequency is increasing or decreasing. A rising trend requires management intervention; a falling trend after coaching sessions confirms improvement.

SELECT
  DATE_TRUNC('week', a.alarm_time AT TIME ZONE 'Africa/Nairobi')  AS week_start,
  a.alarm_type,
  COUNT(*)                                                          AS alarm_count,
  COUNT(DISTINCT a.imei)                                            AS vehicles_affected
FROM tracksolid.alarms a
WHERE a.alarm_time >= DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                        AT TIME ZONE 'Africa/Nairobi'
  AND a.alarm_time <  DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                        AT TIME ZONE 'Africa/Nairobi' + INTERVAL '1 month'
  AND a.alarm_type IS NOT NULL
GROUP BY 1, a.alarm_type
ORDER BY week_start, alarm_count DESC;

Monthly utilisation rate — fleet availability vs. usage

Compares the number of days each vehicle was used against the number of working days in the month. A utilisation rate below 60% may indicate excess fleet capacity; above 95% may indicate a vehicle at risk of missing maintenance.

WITH month_days AS (
  SELECT
    DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
      AT TIME ZONE 'Africa/Nairobi'                                AS month_start,
    DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
      AT TIME ZONE 'Africa/Nairobi' + INTERVAL '1 month'          AS month_end,
    -- Approximate working days: calendar days minus weekends
    (EXTRACT(DAY FROM
      (DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
         + INTERVAL '1 month'
       - DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi'))
    ) * 5 / 7)::int                                               AS working_days
)
SELECT
  d.vehicle_name,
  d.vehicle_number,
  t.imei,
  COUNT(DISTINCT DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')) AS days_used,
  md.working_days,
  ROUND(
    100.0 * COUNT(DISTINCT DATE(t.start_time AT TIME ZONE 'Africa/Nairobi'))
    / NULLIF(md.working_days, 0)
  , 1)                                                             AS utilisation_pct
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
CROSS JOIN month_days md
WHERE t.start_time >= md.month_start
  AND t.start_time <  md.month_end
GROUP BY d.vehicle_name, d.vehicle_number, t.imei, md.working_days
ORDER BY utilisation_pct DESC;

Monthly speed compliance summary

Calculates the share of trips within each speed band for the month. Feeds directly into a safety compliance report. A healthy fleet should have the majority of trips below 60 km/h average for urban operations.

SELECT
  CASE
    WHEN avg_speed_kmh < 20  THEN '020 km/h  (slow / congested)'
    WHEN avg_speed_kmh < 40  THEN '2040 km/h (normal urban)'
    WHEN avg_speed_kmh < 60  THEN '4060 km/h (arterial / highway)'
    WHEN avg_speed_kmh < 80  THEN '6080 km/h (fast highway)'
    ELSE                          '80+ km/h   (excessive)'
  END                                                              AS speed_band,
  COUNT(*)                                                         AS trips,
  ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1)              AS pct_of_total,
  ROUND(SUM(t.distance_km)::numeric, 2)                           AS km_in_band
FROM tracksolid.trips t
WHERE t.start_time >= DATE_TRUNC('month', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                        AT TIME ZONE 'Africa/Nairobi'
  AND t.end_time IS NOT NULL
  AND t.avg_speed_kmh IS NOT NULL
GROUP BY 1
ORDER BY MIN(avg_speed_kmh);

19. Quarterly Analytics

Quarterly analytics answer: "What are the trends over the past three months?" These queries are designed to surface patterns that are invisible at the daily or weekly level — vehicle degradation, seasonal demand shifts, cumulative fuel costs, and driver behaviour trajectories. Use DATE_TRUNC('quarter', ...) for clean calendar quarters or specify explicit date ranges for financial quarters.

Quarterly fleet KPI dashboard row

A single aggregated row per quarter giving headline KPIs: total km, fuel, drive hours, idle ratio, and alarm rate per 1,000 km. Feed this into a year-over-year comparison table.

SELECT
  DATE_TRUNC('quarter', t.start_time AT TIME ZONE 'Africa/Nairobi') AS quarter,
  COUNT(DISTINCT t.imei)                                              AS avg_active_vehicles,
  COUNT(*)                                                            AS total_trips,
  ROUND(SUM(t.distance_km)::numeric, 0)                             AS total_km,
  ROUND(SUM(t.driving_time_s) / 3600.0, 0)                          AS drive_hours,
  ROUND(SUM(t.idle_time_s)    / 3600.0, 0)                          AS idle_hours,
  ROUND(
    100.0 * SUM(t.idle_time_s) /
    NULLIF(SUM(t.driving_time_s) + SUM(t.idle_time_s), 0)
  , 1)                                                               AS idle_pct,
  ROUND(SUM(t.fuel_consumed_l)::numeric, 0)                         AS fuel_l,
  ROUND(
    SUM(t.fuel_consumed_l) / NULLIF(SUM(t.distance_km), 0) * 100
  , 2)                                                               AS fuel_per_100km
FROM tracksolid.trips t
WHERE t.end_time IS NOT NULL
GROUP BY DATE_TRUNC('quarter', t.start_time AT TIME ZONE 'Africa/Nairobi')
ORDER BY quarter;

Quarterly vehicle performance ranking

Ranks each vehicle across the quarter by distance, drive hours, and idle ratio. Highlights top performers and outliers. Vehicles consistently ranking at the bottom may need reassignment, maintenance, or driver change.

SELECT
  d.vehicle_name,
  d.vehicle_number,
  d.driver_name,
  t.imei,
  COUNT(DISTINCT DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')) AS active_days,
  ROUND(SUM(t.distance_km)::numeric, 2)                            AS total_km,
  ROUND(SUM(t.driving_time_s) / 3600.0, 2)                         AS drive_hours,
  ROUND(
    100.0 * SUM(t.idle_time_s) /
    NULLIF(SUM(t.driving_time_s) + SUM(t.idle_time_s), 0)
  , 1)                                                              AS idle_pct,
  ROUND(SUM(t.fuel_consumed_l)::numeric, 2)                        AS fuel_l,
  ROUND(
    SUM(t.fuel_consumed_l) / NULLIF(SUM(t.distance_km), 0) * 100
  , 2)                                                              AS fuel_per_100km,
  RANK() OVER (ORDER BY SUM(t.distance_km) DESC)                   AS km_rank
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
WHERE t.start_time >= DATE_TRUNC('quarter', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                        AT TIME ZONE 'Africa/Nairobi'
  AND t.end_time IS NOT NULL
GROUP BY d.vehicle_name, d.vehicle_number, d.driver_name, t.imei
ORDER BY total_km DESC;

Quarterly alarm frequency trend — month over month

Shows whether safety events are improving or worsening over the quarter. Break down by alarm_type to distinguish overspeed trends from geofence or mechanical alarms.

SELECT
  TO_CHAR(DATE_TRUNC('month', a.alarm_time AT TIME ZONE 'Africa/Nairobi'), 'YYYY-MM') AS month,
  a.alarm_type,
  COUNT(*)                                                          AS alarm_count,
  COUNT(DISTINCT a.imei)                                           AS vehicles_affected,
  ROUND(
    COUNT(*) * 1000.0 /
    NULLIF((
      SELECT SUM(t2.distance_km) FROM tracksolid.trips t2
      WHERE DATE_TRUNC('month', t2.start_time AT TIME ZONE 'Africa/Nairobi')
          = DATE_TRUNC('month', a.alarm_time   AT TIME ZONE 'Africa/Nairobi')
    ), 0)
  , 2)                                                              AS alarms_per_1000km
FROM tracksolid.alarms a
WHERE a.alarm_time >= DATE_TRUNC('quarter', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                        AT TIME ZONE 'Africa/Nairobi'
  AND a.alarm_type IS NOT NULL
GROUP BY 1, a.alarm_type
ORDER BY month, alarm_count DESC;

Quarterly high-mileage vehicles — service interval flag

Flags vehicles that have accumulated more than a configurable kilometre threshold in the quarter. At 5,000 km/quarter a vehicle has driven ~20,000 km/year. Adjust the HAVING threshold to match your service interval policy (e.g. every 5,000 km or every 10,000 km).

SELECT
  d.vehicle_name,
  d.vehicle_number,
  d.imei,
  d.current_mileage_km                                             AS current_odometer,
  ROUND(SUM(t.distance_km)::numeric, 2)                           AS km_this_quarter,
  CASE
    WHEN SUM(t.distance_km) > 10000 THEN 'URGENT SERVICE'
    WHEN SUM(t.distance_km) >  5000 THEN 'SERVICE DUE'
    ELSE 'OK'
  END                                                              AS service_flag
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
WHERE t.start_time >= DATE_TRUNC('quarter', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                        AT TIME ZONE 'Africa/Nairobi'
  AND t.end_time IS NOT NULL
GROUP BY d.vehicle_name, d.vehicle_number, d.imei, d.current_mileage_km
HAVING SUM(t.distance_km) > 5000
ORDER BY km_this_quarter DESC;

Quarterly work-hours pattern — average start and finish times

Calculates the average first-departure and last-return time per vehicle across the quarter. Surfaces systematic early-starters, late-finishers, and vehicles being used outside contracted hours. Shows the standard deviation to identify inconsistent schedules.

SELECT
  d.vehicle_name,
  d.driver_name,
  t.imei,
  TO_CHAR(
    TO_TIMESTAMP(AVG(EXTRACT(EPOCH FROM (MIN(t.start_time AT TIME ZONE 'Africa/Nairobi')::time))))
  , 'HH24:MI')                                                     AS avg_day_start,
  TO_CHAR(
    TO_TIMESTAMP(AVG(EXTRACT(EPOCH FROM (MAX(t.end_time AT TIME ZONE 'Africa/Nairobi')::time))))
  , 'HH24:MI')                                                     AS avg_day_end,
  ROUND(STDDEV(EXTRACT(EPOCH FROM (MIN(t.start_time AT TIME ZONE 'Africa/Nairobi')::time))) / 60, 0)
                                                                    AS start_time_std_min,
  COUNT(DISTINCT DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')) AS days_worked
FROM tracksolid.trips t
JOIN tracksolid.devices d ON d.imei = t.imei
WHERE t.start_time >= DATE_TRUNC('quarter', CURRENT_DATE::timestamptz AT TIME ZONE 'Africa/Nairobi')
                        AT TIME ZONE 'Africa/Nairobi'
  AND t.end_time IS NOT NULL
GROUP BY d.vehicle_name, d.driver_name, t.imei,
         DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')
-- Outer aggregation over the per-day rows
-- Wrap in a CTE if additional aggregation by vehicle is needed
ORDER BY avg_day_start;

21. Known Data Issues

The following issues were identified during the April 2026 audit. Each represents either a data pipeline gap or a missing enrichment step.

# Table Issue Impact Status
1 devices vehicle_name, vehicle_number, driver_name all null for all 63 devices All reports show blank vehicle identity Open — manually populate or sync from fleet management source
2 devices fuel_100km not set for any device fuel_consumed_l in trips will remain null Open — set fuel consumption rate per vehicle type
3 alarms alarm_type and alarm_name were null for all 1,054 records Alarm events could not be categorised Fixed in ingest_events_rev.py [FIX-E06] — poll API uses alertTypeId/alarmTypeName, not webhook field names
4 trips max_speed_kmh is null on all records Cannot identify speeding from trip summaries Openjimi.device.track.mileage may not return this field; verify API response
5 trips distance_km was stored in millimetres All distance queries returned values 1,000,000× too large Fixed — migration 04 divides historical data by 1,000,000 and renames column; ingestion code corrected [FIX-M11/M12]
6 heartbeats 0 rows Cannot distinguish parked-alive from powered-off Open — verify tracker firmware supports heartbeat push
7 obd_readings 0 rows No engine health data Open — requires OBD cable installation + /pushobd webhook registration in Tracksolid account
8 parking_events 0 rows despite 358 successful API calls No parking dwell-time reporting Fixed in ingest_movement_rev.py [FIX-M13] — added missing account and acc_type=0 params; fixed durSecond field mapping
9 dwh_gold.* Both tables empty Grafana dashboards have no data Fixed — migration 05 adds refresh_daily_metrics() ETL function; run nightly via cron or n8n