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>
This commit is contained in:
parent
c05b47abe2
commit
05993100e9
1 changed files with 688 additions and 27 deletions
|
|
@ -30,7 +30,12 @@ docker exec timescale_db-bo3nov2ija7g8wn9b1g2paxs-210508774107 psql -U postgres
|
|||
13. [dwh_gold.fact_daily_fleet_metrics](#13-dwh_goldfact_daily_fleet_metrics)
|
||||
14. [Business Intelligence Queries](#14-business-intelligence-queries)
|
||||
15. [Today's Metrics — From 00:00 Nairobi Time to Now](#15-todays-metrics--from-0000-nairobi-time-to-now)
|
||||
16. [Known Data Issues](#16-known-data-issues)
|
||||
16. [Daily Analytics](#16-daily-analytics)
|
||||
17. [Weekly Analytics](#17-weekly-analytics)
|
||||
18. [Monthly Analytics](#18-monthly-analytics)
|
||||
19. [Quarterly Analytics](#19-quarterly-analytics)
|
||||
20. [New Tables from Migration 05](#20-new-tables-from-migration-05)
|
||||
21. [Known Data Issues](#21-known-data-issues)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -65,18 +70,23 @@ ORDER BY pg_total_relation_size(schemaname || '.' || tablename) DESC;
|
|||
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.
|
||||
|
||||
```sql
|
||||
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.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;
|
||||
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;
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -244,20 +254,23 @@ ORDER BY t.start_time DESC
|
|||
LIMIT 20;
|
||||
```
|
||||
|
||||
### Verify distance units (cross-check via speed × time)
|
||||
### Verify distance accuracy (cross-check via speed × time)
|
||||
|
||||
This diagnostic query confirms that `distance_km` is stored in millimetres by comparing the raw value (divided by 1,000,000 to get km) against the expected distance calculated from average speed and driving time. The two `_km` columns should match closely. Run this whenever the distance figures seem implausible.
|
||||
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.
|
||||
|
||||
```sql
|
||||
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,
|
||||
t.distance_km AS raw_distance_kmm,
|
||||
ROUND(t.distance_km, 3) AS distance_km,
|
||||
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((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
|
||||
|
|
@ -332,7 +345,7 @@ ORDER BY lp.updated_at DESC;
|
|||
|
||||
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.
|
||||
|
||||
At the time of audit, all 1,054 records had `alarm_type` and `alarm_name` set to null, meaning the alarm classification is not yet being parsed from the API response. This is a data pipeline gap that needs to be fixed — the raw alarm events are being stored but are not yet actionable without the type label.
|
||||
**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
|
||||
|
||||
|
|
@ -468,7 +481,7 @@ At the time of audit this table contained **0 rows**. The JC400P and X3 tracker
|
|||
|
||||
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.
|
||||
|
||||
At the time of audit this table contained **0 rows**, despite the ingestion service successfully calling the parking endpoint 358 times. The API is returning empty results, suggesting parking detection thresholds may need to be adjusted in the Tracksolid account settings, or the vehicles have not yet triggered the platform's parking detection criteria.
|
||||
**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
|
||||
|
||||
|
|
@ -538,7 +551,7 @@ WITH daily AS (
|
|||
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) / 1000000.0, 2) AS total_km,
|
||||
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,
|
||||
|
|
@ -579,7 +592,7 @@ 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) / 1000000.0, 2) AS total_fleet_km,
|
||||
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
|
||||
|
|
@ -715,7 +728,7 @@ SELECT
|
|||
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) / 1000000.0, 2) AS total_km,
|
||||
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
|
||||
|
|
@ -763,7 +776,7 @@ WITH today_start AS (
|
|||
SELECT
|
||||
COUNT(DISTINCT t.imei) AS active_vehicles_today,
|
||||
COUNT(*) AS total_trips_today,
|
||||
ROUND(SUM(t.distance_km) / 1000000.0, 2) AS total_fleet_km,
|
||||
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,
|
||||
|
|
@ -776,7 +789,655 @@ WHERE t.start_time >= ts.ts
|
|||
|
||||
---
|
||||
|
||||
## 16. Known Data Issues
|
||||
## 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).
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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:00–20: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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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).
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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 Monday–Sunday 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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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).
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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;
|
||||
```
|
||||
|
||||
### Monthly alarm trends — week by week
|
||||
|
||||
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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
CASE
|
||||
WHEN avg_speed_kmh < 20 THEN '0–20 km/h (slow / congested)'
|
||||
WHEN avg_speed_kmh < 40 THEN '20–40 km/h (normal urban)'
|
||||
WHEN avg_speed_kmh < 60 THEN '40–60 km/h (arterial / highway)'
|
||||
WHEN avg_speed_kmh < 80 THEN '60–80 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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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).
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue