Compare commits
8 commits
main
...
quality-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f94d14864f | ||
|
|
144dedee90 | ||
|
|
898fd25a5a | ||
|
|
5418fc48c5 | ||
|
|
0b45f8d0f7 | ||
|
|
bf17d5fa80 | ||
|
|
80c0e6510f | ||
|
|
34f5fa1b9c |
42 changed files with 13290 additions and 107 deletions
|
|
@ -220,9 +220,9 @@ SELECT
|
|||
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'
|
||||
WHERE imei = '862798052708167'
|
||||
AND gps_time >= '2026-04-22 00:00:00+03'
|
||||
AND gps_time < '2026-04-23 00:00:00+03'
|
||||
ORDER BY gps_time ASC;
|
||||
```
|
||||
|
||||
|
|
@ -432,6 +432,27 @@ WHERE d.device_name = 'FRED KMGW 538W HULETI'
|
|||
ORDER BY ph.gps_time ASC;
|
||||
```
|
||||
|
||||
<!-- distinct routes per vehicle -->
|
||||
``` sql
|
||||
SELECT
|
||||
ph.gps_time AT TIME ZONE 'Africa/Nairobi' AS gps_time_eat,
|
||||
ROUND(ph.lat::numeric, 5) AS lat,
|
||||
ROUND(ph.lng::numeric, 5) AS lng,
|
||||
ph.speed,
|
||||
ph.direction,
|
||||
ph.acc_status,
|
||||
ph.source
|
||||
FROM tracksolid.position_history ph
|
||||
JOIN tracksolid.devices d ON d.imei = ph.imei
|
||||
WHERE d.device_name = 'FRED KMGW 538W HULETI'
|
||||
AND ph.gps_time > now() - interval '24 hours'
|
||||
ORDER BY ph.gps_time ASC;
|
||||
|
||||
'''
|
||||
|
||||
|
||||
|
||||
|
||||
### Fix density per device — last 24 hours
|
||||
```sql
|
||||
SELECT
|
||||
1
.claude/scheduled_tasks.lock
Normal file
1
.claude/scheduled_tasks.lock
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"sessionId":"c108db62-4596-4e11-a4d2-c706487747bf","pid":15863,"acquiredAt":1776761841807}
|
||||
121
08_analytics_config.sql
Normal file
121
08_analytics_config.sql
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
-- Migration 08 — Analytics Configuration Tables
|
||||
-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
-- Adds reference data driving the three-stakeholder analytics redesign
|
||||
-- (Phase 0.3 of the plan). These tables let downstream views monetise idle
|
||||
-- and fuel costs, and apply traffic-light targets to KPIs without hard-coding
|
||||
-- thresholds in SQL.
|
||||
--
|
||||
-- • ops.cost_rates — fuel price per litre by city, labour rate by role.
|
||||
-- • ops.kpi_targets — green / amber / red thresholds per KPI per scope.
|
||||
--
|
||||
-- Run after migration 07. Safe to re-run (CREATE TABLE IF NOT EXISTS,
|
||||
-- INSERT ... ON CONFLICT DO NOTHING).
|
||||
-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ── 1. ops.cost_rates ───────────────────────────────────────────────────────
|
||||
-- Reference rates that power monetisation in analytics views. Lookup pattern
|
||||
-- in views: WHERE scope_type = 'city' AND scope_value = <city> AND metric = ...
|
||||
-- ORDER BY effective_from DESC LIMIT 1.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ops.cost_rates (
|
||||
rate_key TEXT PRIMARY KEY,
|
||||
scope_type TEXT NOT NULL, -- 'city' | 'role' | 'global'
|
||||
scope_value TEXT, -- 'nairobi' | 'driver' | NULL for global
|
||||
metric TEXT NOT NULL, -- 'fuel_per_litre' | 'labour_per_hour'
|
||||
amount NUMERIC(12,2) NOT NULL,
|
||||
currency TEXT NOT NULL, -- 'KES' | 'UGX'
|
||||
effective_from DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cost_rates_lookup
|
||||
ON ops.cost_rates (scope_type, scope_value, metric, effective_from DESC);
|
||||
|
||||
COMMENT ON TABLE ops.cost_rates
|
||||
IS 'Reference rates for analytics monetisation: fuel price per litre by city, '
|
||||
'labour cost per hour by role. Resolution order in views: scope_type=city '
|
||||
'> scope_type=role > scope_type=global.';
|
||||
COMMENT ON COLUMN ops.cost_rates.metric
|
||||
IS 'fuel_per_litre | labour_per_hour';
|
||||
COMMENT ON COLUMN ops.cost_rates.scope_type
|
||||
IS 'city | role | global';
|
||||
|
||||
-- ── 2. ops.kpi_targets ──────────────────────────────────────────────────────
|
||||
-- Traffic-light thresholds per KPI. Same KPI can have global + per-CC + per-city
|
||||
-- rows; views use a CASE / COALESCE chain to pick the most specific match.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ops.kpi_targets (
|
||||
target_id BIGSERIAL PRIMARY KEY,
|
||||
kpi_key TEXT NOT NULL, -- e.g. 'utilisation_pct'
|
||||
scope_type TEXT NOT NULL, -- 'global' | 'city' | 'cost_centre' | 'vehicle_category'
|
||||
scope_value TEXT, -- NULL for global
|
||||
target_value NUMERIC(12,2) NOT NULL,
|
||||
amber_threshold NUMERIC(12,2), -- between target and red
|
||||
red_threshold NUMERIC(12,2), -- worse than amber
|
||||
direction TEXT NOT NULL DEFAULT 'higher_is_better',
|
||||
-- 'higher_is_better' | 'lower_is_better'
|
||||
effective_from DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (kpi_key, scope_type, scope_value, effective_from)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_kpi_targets_lookup
|
||||
ON ops.kpi_targets (kpi_key, scope_type, scope_value, effective_from DESC);
|
||||
|
||||
COMMENT ON TABLE ops.kpi_targets
|
||||
IS 'Traffic-light targets per KPI per scope. Resolution order in views: '
|
||||
'cost_centre > vehicle_category > city > global.';
|
||||
COMMENT ON COLUMN ops.kpi_targets.direction
|
||||
IS 'higher_is_better -> green when value >= target. '
|
||||
'lower_is_better -> green when value <= target.';
|
||||
|
||||
-- ── 3. Seed cost rates ──────────────────────────────────────────────────────
|
||||
-- Placeholder values — confirm with Finance and update via a follow-up insert
|
||||
-- with a later effective_from date (do NOT mutate historical rows).
|
||||
|
||||
INSERT INTO ops.cost_rates
|
||||
(rate_key, scope_type, scope_value, metric, amount, currency, notes)
|
||||
VALUES
|
||||
('fuel.nairobi', 'city', 'nairobi', 'fuel_per_litre', 195.00, 'KES',
|
||||
'Placeholder pump price — confirm with Finance.'),
|
||||
('fuel.mombasa', 'city', 'mombasa', 'fuel_per_litre', 195.00, 'KES',
|
||||
'Placeholder pump price — confirm with Finance.'),
|
||||
('fuel.kampala', 'city', 'kampala', 'fuel_per_litre', 5200.00, 'UGX',
|
||||
'Placeholder pump price — confirm with Finance.')
|
||||
ON CONFLICT (rate_key) DO NOTHING;
|
||||
|
||||
-- ── 4. Seed KPI targets ─────────────────────────────────────────────────────
|
||||
-- Initial Exco-relevant targets. Calibrate after one month of clean data.
|
||||
|
||||
INSERT INTO ops.kpi_targets
|
||||
(kpi_key, scope_type, scope_value, target_value, amber_threshold,
|
||||
red_threshold, direction, notes)
|
||||
VALUES
|
||||
('utilisation_pct', 'global', NULL, 70, 60, 50, 'higher_is_better',
|
||||
'Fleet utilisation: drive_hours / engine_on_hours.'),
|
||||
('idle_pct', 'global', NULL, 15, 20, 25, 'lower_is_better',
|
||||
'Idle as % of engine-on time.'),
|
||||
('idle_pct', 'cost_centre', 'osp patrol', 15, 20, 25, 'lower_is_better',
|
||||
'OSP patrol idle target — same as global until calibrated.'),
|
||||
('fuel_kes_per_100km', 'global', NULL, 12, 14, 16, 'lower_is_better',
|
||||
'Fuel litres per 100km equivalent — uses fuel_100km on devices.'),
|
||||
('mttr_hours', 'global', NULL, 4, 6, 8, 'lower_is_better',
|
||||
'Mean Time To Resolve, field-service ticket.'),
|
||||
('alarms_per_100km', 'global', NULL, 2, 3, 5, 'lower_is_better',
|
||||
'Safety event density.')
|
||||
ON CONFLICT (kpi_key, scope_type, scope_value, effective_from) DO NOTHING;
|
||||
|
||||
-- ── 5. Read access for Grafana ──────────────────────────────────────────────
|
||||
|
||||
GRANT USAGE ON SCHEMA ops TO grafana_ro;
|
||||
GRANT SELECT ON ops.cost_rates TO grafana_ro;
|
||||
GRANT SELECT ON ops.kpi_targets TO grafana_ro;
|
||||
|
||||
COMMIT;
|
||||
82
09_trips_enrichment.sql
Normal file
82
09_trips_enrichment.sql
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
-- Migration 09 — tracksolid.trips Enrichment
|
||||
-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
-- The polling endpoint jimi.device.track.mileage does not return start/end
|
||||
-- coordinates, fuel, idle, or trip sequence. This migration adds the columns
|
||||
-- needed to enrich every poll-ingested trip from data we already have:
|
||||
-- • start/end coordinates and full route polyline reconstructed from
|
||||
-- position_history at insert time (see ingest_movement_rev.py::poll_trips)
|
||||
-- • reverse-geocoded human-readable addresses (Nominatim)
|
||||
-- • denormalised vehicle_plate so trip displays don't need a join
|
||||
-- • waypoint count for audit / data-quality checks
|
||||
--
|
||||
-- Adds a v_trips_enriched view exposing daily_seq (Nth trip for IMEI on this
|
||||
-- Africa/Nairobi date) — replaces reliance on the device-supplied trip_seq
|
||||
-- which is only populated when the rarely-firing /pushtripreport webhook
|
||||
-- delivers a payload.
|
||||
--
|
||||
-- Run after migration 08. Safe to re-run (ADD COLUMN IF NOT EXISTS,
|
||||
-- CREATE INDEX IF NOT EXISTS, CREATE OR REPLACE VIEW).
|
||||
-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ── 1. New columns on tracksolid.trips ──────────────────────────────────────
|
||||
|
||||
ALTER TABLE tracksolid.trips
|
||||
ADD COLUMN IF NOT EXISTS route_geom geometry(LineString, 4326),
|
||||
ADD COLUMN IF NOT EXISTS start_address TEXT,
|
||||
ADD COLUMN IF NOT EXISTS end_address TEXT,
|
||||
ADD COLUMN IF NOT EXISTS vehicle_plate TEXT,
|
||||
ADD COLUMN IF NOT EXISTS waypoints_count INTEGER;
|
||||
|
||||
COMMENT ON COLUMN tracksolid.trips.route_geom IS
|
||||
'Full GPS route polyline built at ingest from position_history points '
|
||||
'where gps_time BETWEEN start_time AND end_time. NULL when fewer than '
|
||||
'2 fixes are available for the trip window.';
|
||||
COMMENT ON COLUMN tracksolid.trips.start_address IS
|
||||
'Reverse-geocoded human-readable address near start_geom (Nominatim). '
|
||||
'NULL on lookup failure; address is best-effort, not authoritative.';
|
||||
COMMENT ON COLUMN tracksolid.trips.end_address IS
|
||||
'Reverse-geocoded human-readable address near end_geom (Nominatim). '
|
||||
'NULL on lookup failure; address is best-effort, not authoritative.';
|
||||
COMMENT ON COLUMN tracksolid.trips.vehicle_plate IS
|
||||
'Denormalised tracksolid.devices.vehicle_number cached at trip-insert '
|
||||
'time. Avoids a join for trip displays; refreshed only on next ingest.';
|
||||
COMMENT ON COLUMN tracksolid.trips.waypoints_count IS
|
||||
'Number of position_history fixes that contributed to route_geom. '
|
||||
'Audit aid: 0 or 1 means route_geom is NULL or degenerate.';
|
||||
|
||||
-- ── 2. Spatial indexes for replay / map queries ─────────────────────────────
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_trips_route_geom
|
||||
ON tracksolid.trips USING GIST (route_geom);
|
||||
CREATE INDEX IF NOT EXISTS idx_trips_start_geom
|
||||
ON tracksolid.trips USING GIST (start_geom);
|
||||
CREATE INDEX IF NOT EXISTS idx_trips_end_geom
|
||||
ON tracksolid.trips USING GIST (end_geom);
|
||||
|
||||
-- ── 3. v_trips_enriched view ────────────────────────────────────────────────
|
||||
-- Adds trip_date_eat (Africa/Nairobi local date) and daily_seq (Nth trip on
|
||||
-- that date for the IMEI) without depending on the device-supplied trip_seq.
|
||||
|
||||
CREATE OR REPLACE VIEW tracksolid.v_trips_enriched AS
|
||||
SELECT
|
||||
t.*,
|
||||
(t.start_time AT TIME ZONE 'Africa/Nairobi')::date AS trip_date_eat,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY t.imei, (t.start_time AT TIME ZONE 'Africa/Nairobi')::date
|
||||
ORDER BY t.start_time
|
||||
) AS daily_seq
|
||||
FROM tracksolid.trips t;
|
||||
|
||||
COMMENT ON VIEW tracksolid.v_trips_enriched IS
|
||||
'tracksolid.trips with computed daily_seq (Nth trip per IMEI per local '
|
||||
'Africa/Nairobi day) and trip_date_eat. Replaces reliance on the '
|
||||
'device-supplied trip_seq column, which is NULL for poll-ingested trips.';
|
||||
|
||||
-- ── 4. Read access for grafana_ro ───────────────────────────────────────────
|
||||
|
||||
GRANT SELECT ON tracksolid.v_trips_enriched TO grafana_ro;
|
||||
|
||||
COMMIT;
|
||||
163
20260427_FSG_Vehicles_mitieng.csv
Normal file
163
20260427_FSG_Vehicles_mitieng.csv
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
imei,device_name,mc_type,mc_type_use_scope,vehicle_name,vehicle_number,vehicle_models,vehicle_icon,vin,engine_number,vehicle_brand,fuel_100km,driver_name,driver_phone,sim,iccid,imsi,account,customer_name,device_group_id,device_group,activation_time,expiration,enabled_flag,status,city,current_mileage_km,created_at,updated_at,last_synced_at,vehicle_category,cost_centre,assigned_route,depot_geom,depot_address,assigned_city
|
||||
353549090553685,Daniel Omondi - KMFF 099Z,AT4,personal,KMFF 099Z,KMFF 099Z,Motorbike,mtc,NULL,NULL,NULL,NULL,Robert,112794067,759336150,89254021334258404099,639021335840409,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-09-23 05:50:30+00,2040-09-23 23:59:59+00,1,1,mombasa,2354.7,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp patrol,NULL,NULL,NULL,mombasa
|
||||
353549090561720,Wireless_Git,AT4,personal,NULL,NULL,NULL,bus,NULL,NULL,NULL,NULL,NULL,NULL,701211913,89254021374215155053,639021371515505,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2025-06-09 09:12:50+00,2035-06-09 23:59:59+00,1,1,mombasa,5992.43,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,regional,NULL,NULL,NULL,mombasa
|
||||
353549090566281,KDR 592N,AT4,personal,NULL,NULL,NULL,bus,NULL,NULL,NULL,NULL,NULL,NULL,797680464,89254021334258159693,639021335815969,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2024-11-08 04:01:30+00,2034-11-08 23:59:59+00,1,1,nairobi,7771.9,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,nairobi
|
||||
353549090566885,Wireless GPS,AT4,personal,NULL,NULL,NULL,bus,NULL,NULL,NULL,NULL,NULL,NULL,768445963,89254021334212352574,639021331235257,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2024-10-15 13:16:57+00,2034-10-15 23:59:59+00,1,1,nairobi,17036.41,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,null,NULL,NULL,NULL,nairobi
|
||||
353549090567685,Daniel Kipkirui - KMFF 162Z,AT4,personal,KMFF 162Z,KMFF 162Z,Motorbike,mtc,NULL,NULL,NULL,NULL,Daniel Kipkirui,112795498,742532058,89254021264260388966,639021266038896,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-09-23 05:09:39+00,2040-09-23 23:59:59+00,1,1,mombasa,462.33,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp patrol,NULL,NULL,NULL,mombasa
|
||||
353549090567701,Wireless,AT4,personal,NULL,NULL,NULL,bus,NULL,NULL,NULL,NULL,NULL,NULL,790176094,89254021394215205906,639021391520590,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2024-11-08 04:04:44+00,2034-11-08 23:59:59+00,1,1,nairobi,16896.2,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,null,NULL,NULL,NULL,nairobi
|
||||
359857081885410,Allan Owana - KDK 780K,GT06E,automobile,KDK 780K,KDK 780K,Probox,automobile,NULL,NULL,NULL,NULL,Allan Owana,NULL,703616117,89254021234222499854,639021232249985,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2019-06-19 09:32:22+00,2039-06-19 23:59:59+00,1,1,nairobi,128853.11,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
359857081886467,Gideon Kiprono - KCQ 215F,GT06E,automobile,KCQ 215F,OHS,Probox,automobile,NULL,NULL,NULL,0,Gideon Kiprono,NULL,746763076,89254021084186499865,639021088649986,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2019-06-30 09:30:00+00,2039-06-30 23:59:59+00,1,1,mombasa,141057.46,2026-04-23 10:56:37.983314+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
359857081886871,Kamonde KBA 467S,GT06E,automobile,NULL,NULL,NULL,bus,NULL,NULL,NULL,NULL,NULL,NULL,746763083,89254021084186499873,639021088649987,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2019-06-30 09:09:14+00,2039-06-30 23:59:59+00,1,1,nairobi,74183.36,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,personal,NULL,NULL,NULL,nairobi
|
||||
359857081886905,Kennedy Chege - KCQ 618K,GT06E,automobile,KCQ 618K,KCQ 618K,Probox,automobile,NULL,NULL,NULL,NULL,Kennedy Chege,NULL,746763132,89254021084186499923,639021088649992,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2019-06-30 07:08:35+00,2039-06-30 23:59:59+00,1,1,mombasa,215608.19,2026-04-23 10:35:37.678371+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
359857081887069,Wright Oseko - KCG 668W,GT06E,automobile,KCG 668W,KCG 668W,Probox,automobile,NULL,NULL,NULL,NULL,Wright Oseko,NULL,746763106,89254021084186499915,639021088649991,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2019-06-30 06:17:43+00,2039-06-30 23:59:59+00,1,1,nairobi,239001.19,2026-04-23 11:00:08.769463+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
359857081887192,Ndegwa Dancun - KCG 669W,GT06E,automobile,KCG 669W,KCG 669W,Probox,automobile,NULL,NULL,NULL,NULL,Ndegwa Dancun,NULL,746760191,89254021084186499501,639021088649950,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2019-06-15 10:26:15+00,2039-06-15 23:59:59+00,1,1,mombasa,199191.85,2026-04-23 10:34:29.074112+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
359857081891566,Simon Kamau - KCE 090R,GT06E,automobile,KCE 090R,KCE 090R,Probox,automobile,NULL,NULL,NULL,NULL,Simon Kamau,NULL,746760404,89254021084186499527,639021088649952,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2019-06-16 07:06:15+00,2039-06-16 23:59:59+00,1,1,nairobi,215592.36,2026-04-23 10:30:55.739184+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
359857081891590,Garage - KCE 699F,GT06E,automobile,KCE 699F,KCE 699F,Probox,automobile,NULL,NULL,NULL,NULL,Garage,NULL,746760215,89254021084186499519,639021088649951,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2019-06-16 11:11:24+00,2039-06-16 23:59:59+00,1,1,nairobi,207814.05,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
359857081891632,Samuel Kamau - KCA 542Q,GT06E,automobile,KCA 542Q,KCA 542Q,Probox,automobile,NULL,NULL,NULL,NULL,John Ondego,NULL,746760038,89254021084186499485,639021088649948,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2019-06-15 09:17:53+00,2039-06-15 23:59:59+00,1,1,nairobi,178914.47,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,roll out,NULL,NULL,NULL,nairobi
|
||||
359857081891798,Garage - KCH 167M,GT06E,automobile,KCH 167M,KCH 167M,Probox,automobile,NULL,NULL,NULL,NULL,Garage,NULL,746760102,89254021084186499493,639021088649949,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2019-06-16 10:18:57+00,2039-06-16 23:59:59+00,1,1,nairobi,168840.95,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
359857081892101,Cornelius Kimutai - KCU 938R,GT06E,automobile,KCU 938R,KCU 938R,Van,automobile,NULL,NULL,NULL,NULL,Cornelius Kimutai,NULL,746759919,89254021084186499451,639021088649945,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2019-06-12 08:13:48+00,2039-06-12 23:59:59+00,1,1,nairobi,149558.5,2026-04-23 10:29:21.507861+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,nairobi
|
||||
359857081892309,Nicholas Erastus - KCQ 581M,GT06E,automobile,KCQ 581M,KCQ 581M,Probox,automobile,NULL,NULL,NULL,NULL,Nicholas Erastus,NULL,700023776,89254021084178504672,639021087850467,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2019-06-09 09:39:40+00,2039-06-09 23:59:59+00,1,1,nairobi,209105.89,2026-04-23 10:40:40.169684+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
359857081892440,KAZ 489Z,GT06E,automobile,NULL,NULL,NULL,bus,NULL,NULL,NULL,NULL,NULL,NULL,700023806,89254021084178504698,639021087850469,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2019-06-09 10:04:39+00,2039-06-09 23:59:59+00,1,1,nairobi,38197.2,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,personal,NULL,NULL,NULL,nairobi
|
||||
359857081892762,Nicholas,GT06E,automobile,NULL,NULL,Station Wagon,bus,NULL,NULL,Toyota,NULL,NULL,NULL,746760503,89254021274233125361,639021273312536,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2019-06-16 08:31:46+00,2039-06-16 23:59:59+00,1,1,mombasa,51048.97,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,personal,NULL,NULL,NULL,nairobi
|
||||
359857082037185,Amani Kazungu - KCY 084X,GT06E,automobile,KCY 084X,KCY 084X,Probox,automobile,NULL,NULL,NULL,NULL,Amani Kazungu,NULL,757338522,89254021154287000597,639021158700059,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-07-13 09:42:28+00,2040-07-13 23:59:59+00,1,1,mombasa,172298.81,2026-04-23 10:51:08.665273+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
359857082038977,Wilfred Kinyanjui - KCU 729C,GT06E,automobile,KCU 729C,KCU 729C,Crane,truck,NULL,NULL,NULL,NULL,Wilfred Kinyanjui,NULL,110094469,89254021164215938057,639021161593805,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-04-05 09:26:00+00,2040-04-05 23:59:59+00,1,1,nairobi,172487.09,2026-04-23 10:24:33.914628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,general,NULL,NULL,NULL,mombasa
|
||||
359857082040981,Amani Sulubu - KCY 090X,GT06E,automobile,KCY 090X,KCY 090X,Probox,automobile,NULL,NULL,NULL,NULL,Amani Sulubu,NULL,793375853,89254021064168004164,639021066800416,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-07-13 07:25:16+00,2040-07-13 23:59:59+00,1,1,mombasa,166028.15,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
359857082042052,Gabriel Musumba - KCE 690F,GT06E,automobile,KCE 690F,KCE 690F,Probox,automobile,NULL,NULL,NULL,NULL,Gabriel Musumba,NULL,110094466,89254021164215938024,639021161593802,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2020-04-03 17:30:13+00,2040-04-03 23:59:59+00,1,1,nairobi,192693.23,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
359857082042854,Elias Baya - KCZ 476E,GT06E,automobile,KCZ 476E,KCZ 476E,Probox,automobile,NULL,NULL,NULL,NULL,Elias Baya,NULL,110941187,89254021164224352993,639021162435299,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-08-09 05:06:42+00,2040-08-09 23:59:59+00,1,1,mombasa,217595.68,2026-04-23 10:33:56.216621+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
359857082042953,KCU 865Q Vanguard,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,unknown,null,NULL,2026-04-23 13:24:33.293453+00,2026-04-23 13:24:33.293453+00,NULL,NULL,null,NULL,NULL,NULL,null
|
||||
359857082044280,Lawrence Kijogi - KCY 080X,GT06E,automobile,KCY 080X,KCY 080X,Probox,automobile,NULL,NULL,NULL,NULL,Lawrence Kijogi,NULL,708155933,89254029851005131222,639029850513122,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-07-13 11:05:02+00,2040-07-13 11:05:02+00,1,1,mombasa,169740.37,2026-04-23 14:52:58.983571+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,roll out,NULL,NULL,NULL,mombasa
|
||||
359857082046145,Joseph Kabandi - KCY 076X,GT06E,automobile,KCY 076X,KCY 076X,Probox,automobile,NULL,NULL,NULL,NULL,Joseph Kabandi,NULL,110850007,89254021164223447158,639021162344715,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-07-13 08:31:26+00,2040-07-13 23:59:59+00,1,1,mombasa,122254.48,2026-04-23 10:47:40.895504+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
359857082896911,Hamisi Pande - KDD 689Y,GT06E,automobile,KDD 689Y,KDD 689Y,Probox,automobile,NULL,NULL,NULL,NULL,Hamisi Pande,NULL,112714612,89254021214211314660,639021211131466,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-09-17 11:50:53+00,2041-09-17 23:59:59+00,1,1,nairobi,163435.74,2026-04-23 10:26:09.922447+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
359857082897091,Peter Mbugua - KDK 728K,GT06E,automobile,KDK 728K,KDK 728K,Probox,automobile,NULL,NULL,NULL,NULL,Peter Mbugua,NULL,790262984,89254021234222500396,639021232250039,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-12-14 11:31:57+00,2042-12-14 23:59:59+00,1,1,nairobi,131109.26,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
359857082897257,Cassius Wakiyo - KDB 323M,GT06E,automobile,KDB 323M,KDB 323M,Probox,automobile,NULL,NULL,NULL,NULL,Cassius Wakiyo,NULL,746428882,89254021234222500818,639021232250081,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-08-29 15:07:26+00,2041-08-29 15:07:26+00,1,1,nairobi,121688.92,2026-04-23 10:28:26.388654+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
359857082897737,John Makori - KDB 585E,GT06E,automobile,KDB 585E,KDB 585E,Probox,automobile,NULL,NULL,NULL,NULL,John Makori,NULL,114596734,89254021214211145262,639021211114526,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-08-29 14:29:28+00,2041-08-29 14:29:28+00,1,1,nairobi,156765.03,2026-04-23 10:38:57.445964+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,planning,NULL,NULL,NULL,nairobi
|
||||
359857082897794,Mutuku Joseph - KDC 739F,GT06E,automobile,KDC 739F,KDC 739F,Probox,automobile,NULL,NULL,NULL,NULL,Mutuku Joseph,115019037,115019037,89254021224222632356,639021222263235,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-04-10 14:55:32+00,2041-04-10 14:55:32+00,1,1,nairobi,205169.79,2026-04-23 10:30:22.530563+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,nairobi
|
||||
359857082898008,Samuel Ng'ang'a - KDE 264M,GT06E,automobile,KDE 264M,KDE 264M,Probox,automobile,NULL,NULL,NULL,NULL,Samuel Ng'ang'a,NULL,711731539,89254021264260342245,639021266034224,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-10-28 09:43:11+00,2041-10-28 23:59:59+00,1,1,nairobi,126584.24,2026-04-23 11:35:59.816581+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
359857082898016,Job Ngare - KDM 309S,GT06E,automobile,KDM 309S,KDM 309S,Probox,automobile,NULL,NULL,NULL,NULL,Job Ngare,NULL,706895756,89254021324273007563,639021327300756,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-08-15 13:45:26+00,2033-08-15 23:59:59+00,1,1,mombasa,107726.56,2026-04-23 11:20:25.939244+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
359857082898073,Mutuku Antony - KDK 732K,GT06E,automobile,KDK 732K,KDK 732K,Probox,automobile,NULL,NULL,NULL,NULL,Mutuku Antony,NULL,793026954,89254021234222387539,639021232238753,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-12-20 06:33:12+00,2042-12-20 23:59:59+00,1,1,mombasa,82096.79,2026-04-23 14:52:07.094447+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
359857082898487,Dan Watila - KDE 638J,GT06E,automobile,KDE 638J,KDE 638J,Probox,automobile,NULL,NULL,NULL,NULL,Dan Watila,NULL,116242996,89254021334258404214,639021335840421,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-10-21 15:50:06+00,2041-10-21 23:59:59+00,1,1,nairobi,123872.36,2026-04-23 10:31:45.186653+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
359857082900168,KDD 913G_Ruth Mazda,NULL,NULL,KDD 913G,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,unknown,nairobi,NULL,2026-04-23 15:09:48.575568+00,2026-04-23 15:09:48.575568+00,NULL,NULL,personal,NULL,NULL,NULL,nairobi
|
||||
359857082900341,Simon Munda - KCZ 154S,GT06E,automobile,KCZ 154S,KCZ 154S,Probox,automobile,NULL,NULL,NULL,NULL,Simon Munda,NULL,757236135,89254021154296723312,639021159672331,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-09-23 17:12:51+00,2040-09-23 23:59:59+00,1,1,mombasa,186504.1,2026-04-23 10:45:21.454595+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
359857082900358,Geoffrey Too - KDM 308S,GT06E,automobile,KDM 308S,KDM 308S,Probox,automobile,NULL,NULL,NULL,NULL,Geoffrey Too,NULL,796527601,89254021264260126572,639021266012657,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-10-21 15:25:28+00,2041-10-21 23:59:59+00,1,1,nairobi,142216.91,2026-04-23 12:35:06.661934+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
359857082900697,George Ochieng' - KDD 684Y,GT06E,automobile,KDD 684Y,KDD 684Y,Probox,automobile,NULL,NULL,NULL,NULL,George Ochieng',NULL,114879518,89254021214211314678,639021211131467,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-09-17 10:53:11+00,2041-09-17 23:59:59+00,1,1,nairobi,152820.07,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
359857082902461,Sadique Wakayula - KDC 490Q,GT06E,automobile,KDC 490Q,KDC 490Q,Crane,truck,NULL,NULL,NULL,NULL,Sadique Wakayula,NULL,757556468,89254021154296722488,639021159672248,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-05-22 11:27:30+00,2041-05-22 11:27:30+00,1,1,mombasa,183009.52,2026-04-23 11:16:03.730519+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,general,NULL,NULL,NULL,mombasa
|
||||
359857082902503,Felix Andole - KDC 207R,GT06E,automobile,KDC 207R,KDC 207R,Probox,automobile,NULL,NULL,NULL,NULL,Felix Andole,NULL,794820817,89254021224270993254,639021227099325,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-05-15 11:38:24+00,2041-05-15 11:38:24+00,1,1,mombasa,208724.46,2026-04-23 15:32:46.935568+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
359857082907973,Felix Muema - KCZ 223P,GT06E,automobile,KCZ 223P,KCZ 223P,Probox,automobile,NULL,NULL,NULL,NULL,Felix Muema,NULL,757843826,89254021154287138371,639021158713837,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-08-22 14:01:25+00,2040-08-22 23:59:59+00,1,1,mombasa,222126.36,2026-04-23 10:26:48.220151+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
359857082908500,Santoes Omondi - KCZ 181P,GT06E,automobile,KCZ 181P,KCZ 181P,Pick-Up,automobile,NULL,NULL,NULL,NULL,Santoes Omondi,NULL,701211974,89254021374215155087,639021371515508,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-08-23 08:58:55+00,2040-08-23 23:59:59+00,1,1,mombasa,221339.62,2026-04-23 10:48:09.537346+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
359857082910589,Patric Bett - KDA 609E,GT06E,automobile,KDA 609E,KDA 609E,Probox,automobile,NULL,NULL,NULL,NULL,Patric Bett,NULL,797622637,89254021154296722496,639021159672249,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2020-10-26 15:46:41+00,2040-10-26 23:59:59+00,1,1,nairobi,194618.69,2026-04-23 10:34:25.350862+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
359857082910886,Makanda Andrew - KCZ 155P,GT06E,automobile,KCZ 155P,KCZ 155P,Pick-Up,automobile,NULL,NULL,NULL,NULL,Makanda Andrew,NULL,745067338,89254021154287138397,639021158713839,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-08-23 11:52:35+00,2040-08-23 23:59:59+00,1,1,mombasa,231065.89,2026-04-23 11:36:31.150282+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
359857082911983,Brian Ngetich - KDA 717B,GT06E,automobile,KDA 717B,KDA 717B,Probox,automobile,NULL,NULL,NULL,NULL,Brian Ngetich,795188807,795188807,89254021214211145288,639021211114528,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-08-29 07:21:43+00,2041-08-29 07:21:43+00,1,1,nairobi,145404.96,2026-04-23 10:36:11.774166+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
359857082912239,Dickson Jaoko - KDK 815R,GT06E,automobile,KDK 815R,KDK 815R,Probox,automobile,NULL,NULL,Probox,0,Sammy,NULL,706392117,89254021234296021287,639021239602128,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2023-06-21 07:14:51+00,2033-06-21 23:59:59+00,1,1,voi,77008.75,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
359857082912486,Moses Wambua - KCZ 751V,GT06E,automobile,KCZ 751V,KCZ 751V,Probox,automobile,NULL,NULL,NULL,NULL,Moses Wambua,792756503,792756503,89254021154296723437,639021159672343,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-09-23 10:14:28+00,2040-09-23 23:59:59+00,1,1,mombasa,139762.2,2026-04-23 10:41:00.207177+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
359857082916826,Denis Kazungu - KDM 794R,GT06E,automobile,KDM 794R,KDM 794R,Probox,automobile,NULL,NULL,NULL,NULL,Denis Kazungu,NULL,705700971,89254021324273006854,639021327300685,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2023-08-21 06:38:00+00,2033-08-21 23:59:59+00,1,1,mombasa,79639.71,2026-04-23 20:18:46.496567+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
359857082918012,Charles Nyambane - KCB 711C,GT06E,automobile,KCB 711C,KCB 711C,Probox,automobile,NULL,NULL,NULL,NULL,Charles Nyambane,NULL,793704231,89254021154287138363,639021158713836,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2020-09-21 10:48:35+00,2040-09-21 23:59:59+00,1,1,nairobi,159597.27,2026-04-23 10:25:52.843474+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
359857082918038,Mbuvi Kioko - KCC 199P,GT06E,automobile,KCC 199P,KCC 199P,Pick-Up,automobile,NULL,NULL,NULL,NULL,Mbuvi Kioko,NULL,797318126,89254021154287138389,639021158713838,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-08-22 15:26:27+00,2040-08-22 23:59:59+00,1,1,mombasa,222106.8,2026-04-23 12:09:05.609075+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
359857082918186,KDD 977T Fielder,NULL,NULL,KDD 977T,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,unknown,null,NULL,2026-04-23 10:36:25.832836+00,2026-04-23 10:36:25.832836+00,NULL,NULL,null,NULL,NULL,NULL,null
|
||||
359857082925330,Noel Merengeni - KCY 838X,GT06E,automobile,KCY 838X,KCY 838X,Probox,automobile,NULL,NULL,NULL,NULL,Noel Merengeni,NULL,794873610,89254021154296723429,639021159672342,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2020-10-26 16:36:37+00,2040-10-26 23:59:59+00,1,1,voi,194429.24,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
862798050288212,Nicholas Erastus - KCQ 581M,JC400P,automobile,KCQ 581M,KCQ 581M,Probox,automobile,NULL,NULL,NULL,NULL,Nicholas Erastus,NULL,746979531,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-11-02 09:07:41+00,2041-11-02 23:59:59+00,1,1,nairobi,40898.98,2026-04-23 13:05:18.326254+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798050288261,Patric Bett - KDA 609E,JC400P,automobile,KDA 609E,KDA 609E,Probox,automobile,NULL,NULL,NULL,NULL,Patric Bett,112693340,790176509,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2021-10-23 11:50:11+00,2041-10-23 23:59:59+00,1,1,nairobi,18538.42,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798050288303,Elias Baya - KCZ 476E,JC400P,automobile,KCZ 476E,KCZ 476E,Probox,automobile,NULL,NULL,NULL,NULL,Elias Baya,NULL,115870439,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-11-06 08:50:28+00,2041-11-06 23:59:59+00,1,1,mombasa,116091.42,2026-04-23 17:46:09.993791+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
862798050288345,Santoes Omondi - KCZ 181P,JC400P,automobile,KCZ 181P,KCZ 181P,Pick-Up,automobile,NULL,NULL,NULL,NULL,Santoes Omondi,NULL,768446105,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2021-11-06 10:17:51+00,2041-11-06 23:59:59+00,1,1,mombasa,107462.79,2026-04-23 10:29:45.563231+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
862798050288360,Brian Ngetich - KDA 717B,JC400P,automobile,KDA 717B,KDA 717B,Probox,automobile,NULL,NULL,NULL,NULL,Brian Ngetich,NULL,717867861,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2021-11-05 08:47:08+00,2041-11-05 23:59:59+00,1,1,nairobi,17808.56,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798050521521,John Kimeria - KDS 525D,JC400P,automobile,KDS 525D,KDS 525D,Crane,truck,NULL,NULL,NULL,NULL,John Kimeria,NULL,752958416,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-11-26 07:58:13+00,2033-11-26 23:59:59+00,1,1,nairobi,19354.92,2026-04-23 10:28:34.917147+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,general,NULL,NULL,NULL,nairobi
|
||||
862798050521612,Denis Kazungu - KDM 794R,JC400P,automobile,KDM 794R,KDM 794R,Probox,automobile,NULL,NULL,NULL,NULL,Denis Kazungu,NULL,704113731,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-22 07:52:12+00,2042-01-22 23:59:59+00,1,1,mombasa,4350.75,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
862798050521752,Simon Munda - KCZ 154S,JC400P,automobile,KCZ 154S,KCZ 154S,Probox,automobile,NULL,NULL,NULL,NULL,Simon Munda,NULL,113805921,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-16 08:14:37+00,2042-01-16 23:59:59+00,1,1,mombasa,4698.02,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
862798050522065,Gideon Kiprono - KCQ 215F,JC400P,automobile,KCQ 215F,KCQ 215F,Probox,automobile,NULL,NULL,NULL,NULL,Gideon Kiprono,NULL,113343715,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2022-01-16 07:10:16+00,2042-01-16 23:59:59+00,1,1,mombasa,8111.98,2026-04-23 18:23:51.445608+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
862798050522107,Cassius Wakiyo - KDB 323M,JC400P,automobile,KDB 323M,KDB 323M,Probox,automobile,NULL,NULL,NULL,NULL,Cassius Wakiyo,NULL,114149576,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-22 08:18:15+00,2042-01-22 23:59:59+00,1,1,nairobi,23316.09,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798050522719,Mbuvi Kioko - KCZ 199P,JC400P,automobile,KCZ 199P,KCZ 199P,Pick-Up,automobile,NULL,NULL,NULL,NULL,Mbuvi Kioko,NULL,768218655,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-16 22:07:24+00,2042-01-16 23:59:59+00,1,1,voi,16973.89,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
862798050522743,Charles Nyambane - KCB 711C,JC400P,automobile,KCB 711C,KCB 711C,Probox,automobile,NULL,NULL,NULL,NULL,Charles Nyambane,NULL,768657106,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2023-12-22 21:53:57+00,2033-12-22 23:59:59+00,1,1,nairobi,12133.75,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798050522859,Garage - KCH 167M,JC400P,automobile,KCH 167M,KCH 167M,Probox,automobile,NULL,NULL,NULL,NULL,Garage,NULL,706740252,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-15 08:23:21+00,2042-01-15 23:59:59+00,1,1,nairobi,6934.86,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798050522883,Dan Watila - KDE 638J,JC400P,automobile,KDE 638J,KDE 638J,Probox,automobile,NULL,NULL,NULL,NULL,Dan Watila,NULL,112615393,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-15 07:17:21+00,2042-01-15 23:59:59+00,1,1,nairobi,14483.01,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798050522891,Lawrence Kijogi - KCY 080X,JC400P,automobile,KCY 080X,KCY 080X,Pick-Up,automobile,NULL,NULL,NULL,NULL,Lawrence Kijogi,NULL,113287191,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-16 10:51:30+00,2042-01-16 23:59:59+00,1,1,mombasa,11585.33,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,roll out,NULL,NULL,NULL,mombasa
|
||||
862798050523014,Samuel Muriithy - KDR 594N,JC400P,automobile,KDR 594N,KDR 594N,Probox,automobile,NULL,NULL,NULL,NULL,Samuel Muriithy,NULL,790175423,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-12-21 15:54:32+00,2033-12-21 23:59:59+00,1,1,nairobi,27275.43,2026-04-23 10:26:17.747928+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,roll out,NULL,NULL,NULL,nairobi
|
||||
862798050523063,Kelvin Wambugu - KDR 594N,JC400P,automobile,KDR 594N,KDR 594N,Probox,automobile,NULL,NULL,NULL,NULL,Kelvin Wambugu,NULL,701211876,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-12-22 19:24:51+00,2043-12-22 19:24:51+00,1,1,nairobi,32698.94,2026-04-23 15:31:08.065856+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,nairobi
|
||||
862798050523139,Mike Wanaswa - KDT 724R,JC400P,automobile,KDT 724R,KDT 724R,Probox,automobile,NULL,NULL,NULL,NULL,Mike Wanaswa,NULL,790175045,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-12-22 22:28:09+00,2043-12-22 22:28:09+00,1,1,mombasa,29559.82,2026-04-23 11:16:37.277518+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
862798050523204,Amani Kazungu - KCY 084X,JC400P,automobile,KCY 084X,KCY 084X,Probox,automobile,NULL,NULL,NULL,NULL,Amani Kazungu,NULL,707892547,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-16 06:18:20+00,2042-01-16 23:59:59+00,1,1,mombasa,66955.7,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
862798050523295,Emmanuel Luseno - KDS 453 Y,JC400P,automobile,KDS 453 Y,KDS 453 Y,Pick-Up,automobile,NULL,NULL,NULL,NULL,Emmanuel Luseno,NULL,700242474,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-12-22 14:39:50+00,2033-12-22 23:59:59+00,1,1,nairobi,37098.35,2026-04-23 11:29:48.369147+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,general,NULL,NULL,NULL,nairobi
|
||||
862798050523337,Victor Kimutai - KDS 919Y,JC400P,automobile,KDS 919Y,KDS 919Y,Probox,automobile,NULL,NULL,NULL,NULL,Victor Kimutai,NULL,700242527,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-12-22 18:00:49+00,2043-12-22 18:00:49+00,1,1,mombasa,50756.64,2026-04-23 10:27:13.522675+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
862798050523386,George Ochieng' - KDD 684Y,JC400P,automobile,KDD 684Y,KDD 684Y,Probox,automobile,NULL,NULL,NULL,NULL,George Ochieng',NULL,785586834,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-22 06:36:08+00,2042-01-22 23:59:59+00,1,1,nairobi,33979.83,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798050523527,Allan Owana - KDK 780K,JC400P,automobile,KDK 780K,KDK 780K,Probox,automobile,NULL,NULL,NULL,NULL,Allan Owana,NULL,792375024,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2022-12-03 10:43:41+00,2042-12-03 23:59:59+00,1,1,nairobi,109564.95,2026-04-23 10:25:24.360765+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798050523618,Geoffrey Too - KDM 308S,JC400P,automobile,KDM 308S,KDM 308S,Probox,automobile,NULL,NULL,NULL,NULL,Geoffrey Too,NULL,701211625,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2023-08-15 15:42:32+00,2033-08-15 23:59:59+00,1,1,nairobi,26496.5,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798050523626,Major Simiyu - KDS 949Y,JC400P,automobile,KDS 949Y,KDS 949Y,Probox,automobile,NULL,NULL,NULL,NULL,Major Simiyu,NULL,701211892,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-12-22 18:05:16+00,2033-12-22 23:59:59+00,1,1,mombasa,37042.97,2026-04-23 10:51:18.245194+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
862798050523816,Job Ngare - KDM 309S,JC400P,automobile,KDM 309S,KDM 309S,Probox,automobile,NULL,NULL,NULL,NULL,Job Ngare,NULL,707936781,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2023-08-15 14:05:52+00,2033-08-15 23:59:59+00,1,1,mombasa,54320.21,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
862798050523949,Joseph Kabandi - KCY 076X,JC400P,automobile,KCY 076X,KCY 076X,Probox,automobile,NULL,NULL,NULL,NULL,Joseph Kabandi,NULL,113288492,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-16 05:52:54+00,2042-01-16 23:59:59+00,1,1,mombasa,14427.5,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
862798050524012,Moses Wambua - KCZ 751V,JC400P,automobile,KCZ 751V,KCZ 751V,Probox,automobile,NULL,NULL,NULL,NULL,Moses Wambua,NULL,113313797,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-16 07:40:10+00,2042-01-16 23:59:59+00,1,1,mombasa,26551.46,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
862798050524087,Felix Muema - KCZ 223P,JC400P,automobile,KCZ 223P,KCZ 223P,Pick-Up,automobile,NULL,NULL,NULL,NULL,Felix Muema,NULL,113973875,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-16 13:02:24+00,2042-01-16 23:59:59+00,1,1,mombasa,11543.26,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
862798050524368,862798050524368,JC400P,automobile,NULL,NULL,NULL,automobile,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-10-29 09:24:53+00,2042-10-29 23:59:59+00,1,1,null,169208.91,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,null,NULL,NULL,NULL,null
|
||||
862798050524384,Hamisi Pande - KDD 689Y,JC400P,automobile,KDD 689Y,KDD 689Y,Probox,automobile,NULL,NULL,NULL,0,Hamisi Pande,NULL,701211744,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-22 05:49:19+00,2042-01-22 23:59:59+00,1,1,nairobi,13685.18,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798050524392,Ndegwa Dancun - KCG 669W,JC400P,automobile,KCG 669W,KCG 669W,Probox,automobile,NULL,NULL,NULL,NULL,Ndegwa Dancun,NULL,113799173,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-16 09:43:10+00,2042-01-16 23:59:59+00,1,1,mombasa,13638.25,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
862798050524426,Amani Sulubu - KCY 090X,JC400P,automobile,KCY 090X,KCY 090X,Probox,automobile,NULL,NULL,NULL,NULL,Amani Sulubu,NULL,113823350,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2022-01-16 08:56:25+00,2042-01-16 23:59:59+00,1,1,mombasa,14243.83,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
862798050524533,Leonard Nzai - KDM 306S,JC400P,automobile,KDM 306S,KDM 306S,Probox,automobile,NULL,NULL,NULL,NULL,Leonard Nzai,NULL,703487162,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-08-21 08:22:12+00,2033-08-21 23:59:59+00,1,1,mombasa,68942.41,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
862798050524558,Mutuku Joseph - KDC 739F,JC400P,automobile,KDC 739F,KDC 739F,Probox,automobile,NULL,NULL,NULL,NULL,Mutuku Joseph,NULL,100858817,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-22 10:38:25+00,2042-01-22 23:59:59+00,1,1,nairobi,23711.63,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,nairobi
|
||||
862798050524566,Makanda Andrew - KCZ 155P,JC400P,automobile,KCZ 155P,KCZ 155P,Pick-Up,automobile,NULL,NULL,NULL,NULL,Makanda Andrew,NULL,758781444,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-22 09:47:33+00,2042-01-22 23:59:59+00,1,1,mombasa,31663.3,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
862798050524608,Peter Mbugua - KDK 728K,JC400P,automobile,KDK 728K,KDK 728K,Probox,automobile,NULL,NULL,NULL,NULL,Peter Mbugua,NULL,706742413,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-12-03 12:11:32+00,2042-12-03 23:59:59+00,1,1,nairobi,7219.31,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798050524657,Felix Andole - KDC 207R,JC400P,automobile,KDC 207R,KDC 207R,Probox,automobile,NULL,NULL,NULL,NULL,Felix Andole,NULL,758689195,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-22 07:17:47+00,2042-01-22 23:59:59+00,1,1,mombasa,46233.99,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
862798050524681,Mutuku Antony - KDK 732K,JC400P,automobile,KDK 732K,KDK 732K,Probox,automobile,NULL,NULL,NULL,NULL,Mutuku Antony,NULL,796275746,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-12-06 13:37:49+00,2042-12-06 23:59:59+00,1,1,mombasa,14993.36,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
862798050524707,Garage - KCE 699F,JC400P,automobile,KCE 699F,KCE 699F,Probox,automobile,NULL,NULL,NULL,NULL,Garage,NULL,110525751,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-15 07:58:49+00,2042-01-15 23:59:59+00,1,1,nairobi,34715.97,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,roll out,NULL,NULL,NULL,nairobi
|
||||
862798050524897,Cornelius Kimutai - KCU 938R,JC400P,automobile,KCU 938R,KCU 938R,Van,automobile,NULL,NULL,NULL,NULL,Cornelius Kimutai,NULL,114924404,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-22 09:03:40+00,2042-01-22 23:59:59+00,1,1,nairobi,12668.43,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,nairobi
|
||||
862798050525068,Samuel Ng'ang'a - KDE 264M,JC400P,automobile,KDE 264M,KDE 264M,Probox,automobile,NULL,NULL,NULL,NULL,Samuel Ng'ang'a,NULL,768658564,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2023-12-22 13:33:42+00,2033-12-22 23:59:59+00,1,1,nairobi,12299.13,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798050525225,Sadique Wakayula - KDC 490Q,JC400P,automobile,KDC 490Q,KDC 490Q,Crane,truck,NULL,NULL,NULL,NULL,Sadique Wakayula,NULL,768652386,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2023-12-22 20:52:08+00,2043-12-22 20:52:08+00,1,1,mombasa,19138.05,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,general,NULL,NULL,NULL,nairobi
|
||||
862798050525266,Dickson Jaoko - KDK 815R,JC400P,automobile,KDK 815R,KDK 815R,Probox,automobile,NULL,NULL,NULL,NULL,Dickson Jaoko,NULL,706665867,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-06-21 07:50:00+00,2033-06-21 23:59:59+00,1,1,mombasa,63754.71,2026-04-23 13:50:24.21992+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
862798050525423,Makori John - KDB 585E,JC400P,automobile,KDB 585E,KDB 585E,Probox,automobile,NULL,NULL,NULL,NULL,Makori John,NULL,701211724,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-15 10:59:19+00,2042-01-15 23:59:59+00,1,1,mombasa,48804.83,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,planning,NULL,NULL,NULL,mombasa
|
||||
862798050525589,Simon Kamau - KCE 090R,JC400P,automobile,KCE 090R,KCE 090R,Probox,automobile,NULL,NULL,NULL,NULL,Simon Kamau,NULL,796276387,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-19 10:10:04+00,2042-01-19 23:59:59+00,1,1,nairobi,15874.39,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798050525605,Samuel Kamau - KCA 542Q,JC400P,automobile,KCA 542Q,KCA 542Q,Probox,automobile,NULL,NULL,NULL,NULL,John Ondego,NULL,110526783,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-15 05:56:11+00,2042-01-15 23:59:59+00,1,1,nairobi,23976.94,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,roll out,NULL,NULL,NULL,nairobi
|
||||
862798050525613,Kennedy Chege - KCQ 618K,JC400P,automobile,KCQ 618K,KCQ 618K,Probox,automobile,NULL,NULL,NULL,NULL,Kennedy Chege,NULL,729994247,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-16 05:21:05+00,2042-01-16 23:59:59+00,1,1,mombasa,12804.24,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
862798050525670,Gabriel Musumba - KCE 690F,JC400P,automobile,KCE 690F,KCE 690F,Probox,automobile,NULL,NULL,NULL,NULL,Gabriel Musumba,NULL,701211996,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2022-01-15 06:40:01+00,2042-01-15 23:59:59+00,1,1,nairobi,20110.93,2026-04-24 05:34:23.167312+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798050525753,Noel Merengeni - KCY 838X,JC400P,automobile,KCY 838X,KCY 838X,Probox,automobile,NULL,NULL,NULL,NULL,Noel Merengeni,NULL,NULL,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2022-01-15 05:24:00+00,2042-01-15 23:59:59+00,1,1,voi,14596.59,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,voi
|
||||
862798050525837,Kennedy Ondieki - KCU 237Z,JC400P,automobile,KCU 237Z,KCU 237Z,Probox,automobile,NULL,NULL,NULL,NULL,Kennedy Ondieki,NULL,113669852,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2023-12-21 19:32:44+00,2033-12-21 23:59:59+00,1,1,nairobi,NULL,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798050525951,Wright Oseko - KCG 668W,JC400P,automobile,KCG 668W,KCG 668W,Probox,automobile,NULL,NULL,NULL,NULL,Wright Oseko,NULL,741943212,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2022-01-15 09:36:45+00,2042-01-15 23:59:59+00,1,1,nairobi,13116,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798050526165,Wilfred Kinyanjui - KCU 729C,JC400P,automobile,KCU 729C,KCU 729C,Crane,truck,NULL,NULL,NULL,NULL,Wilfred Kinyanjui,NULL,790564929,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2023-11-26 10:17:19+00,2033-11-26 23:59:59+00,1,1,nairobi,24270.2,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,general,NULL,NULL,NULL,nairobi
|
||||
862798050526231,Rashid Hassan - KDM 840V,JC400P,automobile,KDM 840V,KDM 840V,Probox,automobile,NULL,NULL,NULL,NULL,Rashid Hassan,NULL,790175526,NULL,NULL,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2023-12-22 22:36:15+00,2043-12-22 22:36:15+00,1,1,mombasa,45418.38,2026-04-23 10:29:41.575467+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
862798050526256,Ian Dancun - KDT 923R,JC400P,automobile,KDT 923R,KDT 923R,Probox,automobile,NULL,NULL,NULL,NULL,Ian Dancun,NULL,794873610,NULL,NULL,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2023-12-22 19:37:24+00,2043-12-22 19:37:24+00,1,1,mombasa,11093.11,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,qehs,NULL,NULL,NULL,mombasa
|
||||
862798052707888,Benjamin Ananda - KDV 438W,JC400P,automobile,KDV 438W,KDV 438W,Probox,automobile,NULL,NULL,NULL,NULL,Benjamin Ananda,NULL,758047312,89254021414206816980,639021410681698,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-12-15 07:39:23+00,2035-12-15 23:59:59+00,1,1,nairobi,8720.87,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,planning,NULL,NULL,NULL,nairobi
|
||||
862798052707896,John Mbugua - KDW 573B,JC400P,automobile,KDW 573B,KDW 573B,Probox,automobile,NULL,NULL,NULL,NULL,John Mbugua,NULL,NULL,89254021414206816725,639021410681672,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2026-01-30 14:48:17+00,2036-01-30 23:59:59+00,1,1,nairobi,515.16,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798052707946,Tom Wekesa/OSP-KCY 930Y_CAM,JC400P,automobile,KCY 930Y,NULL,NULL,automobile,NULL,NULL,NULL,NULL,NULL,NULL,758047806,89254021414206816766,639021410681676,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2026-01-20 21:02:13+00,2036-01-20 23:59:59+00,1,1,nairobi,10079.17,2026-04-23 10:25:24.363965+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798052708035,862798052708035,JC400P,automobile,NULL,NULL,Probox,automobile,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,NULL,NULL,1,1,null,NULL,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,null,NULL,NULL,NULL,null
|
||||
862798052708068,Dominic Wambua - KDV 683Z,JC400P,automobile,KDV 683Z,KDV 683Z,Probox,automobile,NULL,NULL,NULL,NULL,Dominic Wambua,NULL,758048043,89254021414206816964,639021410681696,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2026-01-24 09:20:09+00,2036-01-24 23:59:59+00,1,1,nairobi,4438.55,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,roll out,NULL,NULL,NULL,nairobi
|
||||
862798052708076,Albert Mutwiri - KDV 437W,JC400P,automobile,KDV 437W,KDV 437W,Probox,automobile,NULL,NULL,NULL,NULL,Albert Mutwiri,NULL,758047094,89254021414206816782,639021410681678,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-12-13 15:03:30+00,2035-12-13 23:59:59+00,1,1,nairobi,5575.64,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,nairobi
|
||||
862798052708167,Levine Wasike - KDV 439W,JC400P,automobile,KDV 439W,KDV 439W,Probox,automobile,NULL,NULL,NULL,NULL,Levine Wasike,NULL,758046738,89254021414206816741,639021410681674,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-12-13 19:49:29+00,2035-12-13 23:59:59+00,1,1,nairobi,4601.08,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798052708282,Godffrey Nandwa - KCN 496A,JC400P,automobile,KCN 496A,KCN 496A,Probox,automobile,NULL,NULL,NULL,NULL,Godffrey Nandwa,NULL,758047934,89254021414206816865,639021410681686,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2026-01-25 18:55:54+00,2036-01-25 23:59:59+00,1,1,nairobi,7040.6,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798052713654,Garage/ISP_KCL 502T_CAM,JC400P,automobile,KCL 502T,NULL,NULL,automobile,NULL,NULL,NULL,NULL,NULL,NULL,780215879,89254035061001753803,639035060175380,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-09-02 10:09:57+00,2035-09-02 23:59:59+00,1,1,nairobi,5199.72,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798052713696,862798052713696,JC400P,automobile,NULL,NULL,Probox,automobile,NULL,NULL,NULL,NULL,NULL,NULL,NULL,89254021394215205906,639021391520590,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-09-02 10:20:58+00,2035-09-02 23:59:59+00,1,1,null,6214.49,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,null,NULL,NULL,NULL,null
|
||||
862798052713761,Management_Mazda - KDU 613A,JC400P,automobile,KDU 613A,KDU 613A,Mazda,automobile,NULL,NULL,NULL,NULL,Management_Mazda,NULL,790176786,89254021394215205955,639021391520595,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-07-09 15:49:26+00,2035-07-09 23:59:59+00,1,1,nairobi,9262.78,2026-04-23 16:40:48.879666+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,management,NULL,NULL,NULL,nairobi
|
||||
862798052713779,Benard Kimutai - KDN 759G,JC400P,automobile,KDN 759G,KDN 759G,Probox,automobile,NULL,NULL,NULL,NULL,Benard Kimutai,NULL,752143258,89254035061001753860,639035060175386,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-08-23 11:15:59+00,2035-08-23 23:59:59+00,1,1,nairobi,5344.24,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
862798052713811,James Onyango - KDU 613B,JC400P,automobile,KDU 613B,KDU 613B,Probox,automobile,NULL,NULL,NULL,NULL,James Onyango,NULL,790176542,89254021394215205880,639021391520588,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-07-09 19:24:14+00,2035-07-09 23:59:59+00,1,1,nairobi,9657.42,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798052713837,Kennedy Ondieki - KCU 237Z,JC400P,automobile,KCU 237Z,KCU 237Z,Probox,automobile,NULL,NULL,NULL,NULL,Kennedy Ondieki,NULL,113669852,89254021414206327855,639021410632785,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-10-08 14:55:23+00,2035-10-08 23:59:59+00,1,1,nairobi,9346.02,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
862798052713985,Timothy Gitau - KDT 916R,JC400P,automobile,KDT 916R,KDT 916R,Probox,automobile,NULL,NULL,NULL,NULL,Timothy Gitau,NULL,768696668,89254021394274518892,639021397451889,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-08-02 18:21:23+00,2035-08-02 23:59:59+00,1,1,mombasa,19998.22,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,regional,NULL,NULL,NULL,mombasa
|
||||
862798052714066,862798052714066,JC400P,automobile,NULL,NULL,Probox,automobile,NULL,NULL,NULL,NULL,NULL,NULL,NULL,89254021414206378684,639021410637868,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-11-21 17:44:44+00,2035-11-21 23:59:59+00,1,1,null,10755.28,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,null,NULL,NULL,NULL,null
|
||||
862798052715220,Rofas Njagi - KDT 728R,JC400P,automobile,KDT 728R,KDT 728R,Probox,automobile,NULL,NULL,NULL,NULL,Rofas Njagi,NULL,704573658,89254021334258495873,639021335849587,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-07-16 07:09:25+00,2035-07-16 23:59:59+00,1,1,nairobi,16385.58,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,regional,NULL,NULL,NULL,nairobi
|
||||
865135061035133,Major Simiyu - KDS 949Y,X3,automobile,KDS 949Y,KDS 949Y,Probox,automobile,NULL,NULL,NULL,NULL,Major Simiyu,NULL,768696642,89254021394274518918,639021397451891,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2025-08-02 13:14:33+00,2035-08-02 23:59:59+00,1,1,mombasa,25089.98,2026-04-23 12:07:56.044395+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,mombasa
|
||||
865135061035653,Richardson Komu - KDT 923R,X3,automobile,KDT 923R,KDT 923R,Probox,automobile,NULL,NULL,NULL,NULL,Richardson Komu,NULL,768697292,89254021394274518942,639021397451894,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-08-02 08:11:46+00,2035-08-02 23:59:59+00,1,1,mombasa,23556.65,2026-04-23 10:24:50.340401+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
865135061035778,John Kimeria - KDS 525D,X3,automobile,KDS 525D,KDS 525D,Crane,truck,NULL,NULL,NULL,NULL,John Kimeria,NULL,790176738,89254021394215205922,639021391520592,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-07-11 05:50:36+00,2035-07-11 23:59:59+00,1,1,nairobi,17653.96,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,general,NULL,NULL,NULL,nairobi
|
||||
865135061036164,Brian Njenga - KMFF 113Z,X3,automobile,KMFF 113Z,KMFF 113Z,Motorbike,mtc,NULL,NULL,NULL,NULL,Brian Njenga,NULL,768696705,89254021394274518850,639021397451885,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-07-31 10:06:46+00,2035-07-31 23:59:59+00,1,1,nairobi,22990.33,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,deliveries,NULL,NULL,NULL,nairobi
|
||||
865135061037980,Emmanuel Luseno - KDS 453Y,X3,automobile,KDS 453Y,KDS 453Y,Pick-Up,automobile,NULL,NULL,NULL,NULL,Emmanuel Luseno,NULL,790176734,89254021394215205856,639021391520585,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-07-15 06:30:34+00,2035-07-15 23:59:59+00,1,1,nairobi,42609.03,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,general,NULL,NULL,NULL,nairobi
|
||||
865135061042261,Kelvin Wambugu - KDR 592N,X3,automobile,KDR 592N,KDR 592N,Probox,automobile,NULL,NULL,NULL,NULL,Kelvin Wambugu,NULL,797680464,89254021334258159693,639021335815969,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-07-10 10:23:44+00,2035-07-10 23:59:59+00,1,1,nairobi,18755.66,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,nairobi
|
||||
865135061043079,Mike Wanaswa - KDT 724R,X3,automobile,KDT 724R,KDT 724R,Probox,automobile,NULL,NULL,NULL,NULL,Mike Wanaswa,NULL,768696664,89254021394274518959,639021397451895,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2025-08-02 12:16:11+00,2035-08-02 23:59:59+00,1,1,mombasa,27470.11,2026-04-23 11:16:35.682194+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
865135061043426,Geoffrey Karanja - KMGS 239H,X3,automobile,KMGS 239H,KMGS 239H,Motorbike,mtc,NULL,NULL,NULL,NULL,Geoffrey Karanja,NULL,768696658,89254021394274518926,639021397451892,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-08-22 13:32:25+00,2035-08-22 23:59:59+00,1,1,nairobi,21267.01,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp patrol,NULL,NULL,NULL,nairobi
|
||||
865135061047435,Management_Mazda - KDU 613A,X3,automobile,KDU 613A,KDU 613A,Mazda,automobile,NULL,NULL,NULL,NULL,Management_Mazda,NULL,790175971,89254021394215205971,639021391520597,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-07-09 08:02:26+00,2035-07-09 23:59:59+00,1,1,nairobi,9761.38,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,management,NULL,NULL,NULL,nairobi
|
||||
865135061048276,Victor Kimutai - KDS 919Y,X3,automobile,KDS 919Y,KDS 919Y,Probox,automobile,NULL,NULL,NULL,NULL,Victor Kimutai,NULL,768696755,89254021394274518900,639021397451890,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2025-08-02 07:38:01+00,2035-08-02 23:59:59+00,1,1,mombasa,23296.79,2026-04-23 10:54:41.63532+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,mombasa
|
||||
865135061048300,KMGR 409U HENRY JAZZ,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,unknown,nairobi,NULL,2026-04-24 04:30:20.231102+00,2026-04-24 04:30:20.231102+00,NULL,NULL,personal,NULL,NULL,NULL,nairobi
|
||||
865135061048466,Samuel Muriithy - KDR 594N,X3,automobile,KDR 594N,KDR 594N,Probox,automobile,NULL,NULL,NULL,NULL,Samuel Muriithy,NULL,797680395,89254021334258159628,639021335815962,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-07-24 09:37:31+00,2035-07-24 23:59:59+00,1,1,nairobi,27634.1,2026-04-23 11:43:39.178819+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,roll out,NULL,NULL,NULL,nairobi
|
||||
865135061048615,Office-KMDG 902Z,X3,automobile,KMDG 902Z,NULL,NULL,automobile,NULL,NULL,NULL,NULL,NULL,NULL,768697276,89254021394274518876,639021397451887,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-07-31 09:59:43+00,2035-07-31 23:59:59+00,1,1,nairobi,5721.21,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp patrol,NULL,NULL,NULL,nairobi
|
||||
865135061048953,Timothy Gitau - KDT 916R,X3,automobile,KDT 916R,KDT 916R,Probox,automobile,NULL,NULL,NULL,NULL,Timothy Gitau,NULL,768697056,89254021394274518967,639021397451896,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2025-08-02 08:48:05+00,2035-08-02 23:59:59+00,1,1,mombasa,28536.23,2026-04-23 10:53:31.102315+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,regional,NULL,NULL,NULL,mombasa
|
||||
865135061049001,Parked - KMGK 596V,X3,automobile,KMGK 596V,KMGK 596V,Motorbike,mtc,NULL,NULL,NULL,NULL,Parked,NULL,768697064,89254021394274518884,639021397451888,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-07-31 08:40:18+00,2035-07-31 23:59:59+00,1,1,nairobi,20612.89,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,deliveries,NULL,NULL,NULL,nairobi
|
||||
865135061053714,Samuel Kihara - KMEL 225X,X3,automobile,KMEL 225X,KMEL 225X,Motorbike,mtc,NULL,NULL,NULL,NULL,Samuel Kihara,NULL,768696832,89254021394274518934,639021397451893,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-08-02 13:51:47+00,2035-08-02 23:59:59+00,1,1,nairobi,26897.18,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp patrol,NULL,NULL,NULL,nairobi
|
||||
865135061053748,Rashid Hassan - KDM 840V,X3,automobile,KDM 840V,KDM 840V,Probox,automobile,NULL,NULL,NULL,NULL,Rashid Hassan,NULL,768445963,89254021334212352574,639021331235257,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-07-10 13:54:11+00,2035-07-10 23:59:59+00,1,1,mombasa,26612.42,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,mombasa
|
||||
865135061054548,James Onyango - KDU 613B,X3,automobile,KDU 613B,KDU 613B,Probox,automobile,NULL,NULL,NULL,NULL,James Onyango,NULL,790175997,89254021394215205948,639021391520594,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-07-09 07:11:15+00,2035-07-09 23:59:59+00,1,1,nairobi,13446.05,2026-04-23 10:26:24.667167+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,isp,NULL,NULL,NULL,nairobi
|
||||
865135061054555,Rofas Njagi - KDT 728R,X3,automobile,KDT 728R,KDT 728R,Probox,automobile,NULL,NULL,NULL,NULL,Rofas Njagi,NULL,790176726,89254021394215205823,639021391520582,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-07-16 06:44:33+00,2035-07-16 23:59:59+00,1,1,nairobi,27250.8,2026-04-23 10:25:21.085437+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,regional,NULL,NULL,NULL,nairobi
|
||||
865135061559538,FRED KMGW 538W HULETI,NULL,NULL,KMGW 538W,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,unknown,null,NULL,2026-04-23 10:42:18.5831+00,2026-04-23 10:42:18.5831+00,NULL,NULL,null,NULL,NULL,NULL,null
|
||||
865135061562722,John Mbugua - KDW 573B,X3,automobile,KDW 573B,KDW 573B,Probox,automobile,NULL,NULL,NULL,NULL,John Mbugua,NULL,758052508,89254021414206816832,639021410681683,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2026-01-30 06:53:57+00,2036-01-30 23:59:59+00,1,1,nairobi,4488.19,2026-04-23 10:25:38.887433+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
865135061562847,Levine Wasike - KDV 439W,X3,automobile,KDV 439W,KDV 439W,Probox,automobile,NULL,NULL,NULL,NULL,Levine Wasike,NULL,758047032,89254021414206816840,639021410681684,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2025-12-13 11:14:14+00,2035-12-13 23:59:59+00,1,1,nairobi,7880.92,2026-04-23 10:35:50.779597+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,osp,NULL,NULL,NULL,nairobi
|
||||
865135061563282,X3-63282,X3,automobile,NULL,NULL,NULL,automobile,NULL,NULL,NULL,NULL,NULL,NULL,NULL,8925610001837573427F,641101970467668,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2026-02-14 07:20:10+00,2036-02-14 23:59:59+00,1,1,null,4758.32,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,null,NULL,NULL,NULL,null
|
||||
865135061563415,Barack Orwa - KDW 781E,X3,automobile,KDW 781E,KDW 781E,Vazel,automobile,NULL,NULL,NULL,NULL,Barack Orwa,NULL,758052541,89254021414206816931,639021410681693,Fireside_MSA,Fireside Group MSA,9d0927d235e44fe7abf254902fc68921,Default group,2026-01-13 12:37:42+00,2036-01-13 23:59:59+00,1,1,nairobi,4165.95,2026-04-23 11:22:00.676215+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,personal,NULL,NULL,NULL,nairobi
|
||||
865135061563423,Joel Ntumba - UMA 826AB,X3,automobile,UMA 826AB,UMA 826AB,Motorbike,mtc,NULL,NULL,NULL,NULL,Joel Ntumba,NULL,119051036,89254021414206652690,639021410665269,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2026-01-28 13:55:39+00,2036-01-28 23:59:59+00,1,1,uganda,1174.05,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,mtn,NULL,NULL,NULL,uganda
|
||||
865135061563597,Dominic Wambua - KDV 683Z,X3,automobile,KDV 683Z,KDV 683Z,Probox,automobile,NULL,NULL,NULL,NULL,Dominic Wambua,NULL,758052405,89254021414206816733,639021410681673,Fireside@HQ,Fireside Telematics,6ef0b0fc2d964b358b70dc2cfcbc5b7e,Default group,2026-01-30 06:55:35+00,2036-01-30 23:59:59+00,1,1,nairobi,6790.53,2026-04-23 10:25:40.125927+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,roll out,NULL,NULL,NULL,nairobi
|
||||
865135061563639,Benjamin Ananda - KDV 438W,X3,automobile,KDV 438W,KDV 438W,Probox,automobile,NULL,NULL,NULL,NULL,Benjamin Ananda,NULL,758047065,89254021414206816683,639021410681668,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-12-13 16:02:37+00,2035-12-13 23:59:59+00,1,1,nairobi,14446.33,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,planning,NULL,NULL,NULL,nairobi
|
||||
865135061564280,Rodin Kiberu - UMA 011EK,X3,automobile,UMA 011EK,UMA 011EK,Motorbike,mtc,NULL,NULL,NULL,NULL,Rodin Kiberu,NULL,118081642,89254021414206817244,639021410681724,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2026-01-28 13:13:57+00,2036-01-28 23:59:59+00,1,1,uganda,841.39,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,mtn,NULL,NULL,NULL,uganda
|
||||
865135061564470,Silvanus Kipkorir - KDV 064S,X3,automobile,KDV 064S,KDV 064S,Probox,automobile,NULL,NULL,NULL,NULL,Silvanus Kipkorir,NULL,113669866,89254021414206378718,639021410637871,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-11-21 16:49:17+00,2035-11-21 23:59:59+00,1,1,nairobi,23869.16,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,airtel,NULL,NULL,NULL,nairobi
|
||||
865135061568968,X3-68968,X3,automobile,NULL,NULL,NULL,automobile,NULL,NULL,NULL,NULL,NULL,NULL,NULL,89254021414206816915,639021410681691,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2026-03-11 06:19:14+00,2036-03-11 23:59:59+00,1,1,null,16.23,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,null,NULL,NULL,NULL,null
|
||||
865135061569123,Albert Mutwiri - KDV 437W,X3,automobile,KDV 437W,KDV 437W,Probox,automobile,NULL,NULL,NULL,NULL,Albert Mutwiri,NULL,758047101,89254021414206816881,639021410681688,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-12-13 14:26:17+00,2035-12-13 23:59:59+00,1,1,nairobi,13032.6,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,nairobi
|
||||
865135061569131,UMA 418EK,X3,automobile,UMA 418EK,UMA 418EK,NULL,automobile,NULL,NULL,NULL,NULL,UG,NULL,256792997053,8925610001837573385F,641101970467664,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2026-02-26 08:15:44+00,2036-02-26 23:59:59+00,1,1,uganda,2333.45,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,mtn,NULL,NULL,NULL,uganda
|
||||
865135061569479,UMA 382EK,X3,automobile,UMA 382EK,UMA 382EK,NULL,automobile,NULL,NULL,NULL,NULL,UG,NULL,256792997079,8925610001837573419F,641101970467667,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2026-02-26 08:21:10+00,2036-02-26 23:59:59+00,1,1,uganda,1954.86,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,mtn,NULL,NULL,NULL,uganda
|
||||
865135061578553,X3-78553,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,unknown,null,NULL,2026-04-23 15:30:19.981271+00,2026-04-23 15:30:19.981271+00,NULL,NULL,null,NULL,NULL,NULL,null
|
||||
865135061581904,Robert Kipruto - KDV 072L,X3,automobile,KDV 072L,KDV 072L,Probox,automobile,NULL,NULL,NULL,NULL,Robert Kipruto,NULL,114149576,89254021264261503993,639021266150399,fireside,Fireside Group HQ,2f1acaef6c884214b4598719180fe68d,Default group,2025-11-21 15:30:29+00,2035-11-21 23:59:59+00,1,1,nairobi,15252.84,2026-04-23 10:23:56.546784+00,2026-04-24 07:43:45.210628+00,2026-04-24 07:43:45.210628+00,NULL,fds,NULL,NULL,NULL,nairobi
|
||||
|
75
260427_audit_output.txt
Normal file
75
260427_audit_output.txt
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
============================================================================
|
||||
Device reconciliation — CSV vs tracksolid.devices
|
||||
============================================================================
|
||||
CSV file : 20260427_FSG_Vehicles_mitieng.csv
|
||||
CSV row count : 162
|
||||
DB row count : 172
|
||||
Delta (DB-CSV) : +10
|
||||
|
||||
─ Per-account breakdown ─────────────────────────────────────────────────
|
||||
account CSV DB delta
|
||||
(blank) 0 10 +10
|
||||
Fireside@HQ 52 52 +0
|
||||
Fireside_MSA 41 41 +0
|
||||
NULL 6 0 -6
|
||||
fireside 63 69 +6
|
||||
|
||||
─ IMEIs in DB but NOT in CSV (10) ─────────────────────────────
|
||||
imei account city last_synced_at device_name
|
||||
359857081891921 JK Subaru KCS 903Y
|
||||
359857082898297 KDK 829A GP
|
||||
862798052707995 fireside 2026-04-27 19:30:53.484218+00:00 JC400P-07995
|
||||
862798052715071 KDU 878T_CAM
|
||||
862798052785751 fireside 2026-04-27 19:30:53.484218+00:00 JC400P-85751
|
||||
862798052786270 fireside 2026-04-27 19:30:53.484218+00:00 JC400P-86270
|
||||
865135061040349 KDU 878T_Track
|
||||
865135061559405 X3-59405
|
||||
865135061569172 fireside 2026-04-27 19:30:53.484218+00:00 X3-69172
|
||||
865135061569529 fireside 2026-04-27 19:30:53.484218+00:00 X3-69529
|
||||
|
||||
─ IMEIs in CSV but NOT in DB (0) ─────────────────────────────
|
||||
(none — every CSV row has a corresponding device row)
|
||||
|
||||
─ Devices in both, but DB metadata still NULL (162) ──────────────
|
||||
Likely cause: import_drivers_csv.py has not been re-run with --apply
|
||||
against the new CSV, or rows had 'Identification' placeholders.
|
||||
|
||||
imei blank_fields
|
||||
353549090553685 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
353549090561720 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address, driver_name, vehicle_number
|
||||
353549090566281 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address, driver_name, vehicle_number
|
||||
353549090566885 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address, driver_name, vehicle_number
|
||||
353549090567685 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
353549090567701 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address, driver_name, vehicle_number
|
||||
359857081885410 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857081886467 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, depot_address
|
||||
359857081886871 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address, driver_name, vehicle_number
|
||||
359857081886905 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857081887069 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857081887192 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857081891566 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857081891590 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857081891632 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857081891798 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857081892101 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857081892309 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857081892440 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address, driver_name, vehicle_number
|
||||
359857081892762 assigned_city, cost_centre, assigned_route, vehicle_category, fuel_100km, depot_address, driver_name, vehicle_number
|
||||
359857082037185 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857082038977 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857082040981 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857082042052 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857082042854 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857082042953 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address, driver_name, vehicle_number
|
||||
359857082044280 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857082046145 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857082896911 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
359857082897091 assigned_city, cost_centre, assigned_route, vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
... and 132 more
|
||||
|
||||
─ Suggested next step ───────────────────────────────────────────────────
|
||||
Inspect the IMEIs above. Decide one of:
|
||||
(a) Prune — delete from tracksolid.devices if they are stale test/decommissioned units.
|
||||
(b) Leave-as-NULL — keep them as auto-synced API rows; their metadata stays NULL until added to a future CSV.
|
||||
(c) Addendum — add them to the CSV (or a sidecar CSV) and re-run import_drivers_csv.py --apply.
|
||||
Document the choice in 260427_device_reconciliation.md.
|
||||
91
260427_device_reconciliation.md
Normal file
91
260427_device_reconciliation.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Device Reconciliation — 162 vs 182 (2026-04-27)
|
||||
|
||||
Phase 0.2 of the Business Analytics redesign. Resolves the gap between
|
||||
`20260427_FSG_Vehicles_mitieng.csv` (162 rows) and `tracksolid.devices`
|
||||
(~182 rows at last check).
|
||||
|
||||
## How to populate this report
|
||||
|
||||
1. Pull this branch onto the Coolify host (or rebuild containers so the
|
||||
ingest container has `audit_device_reconciliation.py`).
|
||||
|
||||
2. Run inside the ingest container so it has `DATABASE_URL`:
|
||||
```bash
|
||||
ING=$(docker ps --filter name=ingest_movement --format "{{.Names}}" | head -1)
|
||||
docker cp 20260427_FSG_Vehicles_mitieng.csv "$ING":/app/
|
||||
docker exec "$ING" python audit_device_reconciliation.py \
|
||||
--csv 20260427_FSG_Vehicles_mitieng.csv \
|
||||
--out /tmp/260427_audit_output.txt
|
||||
docker cp "$ING":/tmp/260427_audit_output.txt ./
|
||||
```
|
||||
|
||||
3. Paste the audit output into the **Audit output** section below.
|
||||
|
||||
4. Mark the chosen disposition for each IMEI in the **Disposition** section.
|
||||
|
||||
## Audit output
|
||||
|
||||
First run: 2026-04-27 23:35 UTC against `tracksolid_db` on stage. Full output in
|
||||
`260427_audit_output.txt`. Headline numbers:
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| CSV rows | 162 |
|
||||
| `tracksolid.devices` rows | 172 |
|
||||
| Delta (DB − CSV) | +10 |
|
||||
| In CSV but not in DB | 0 |
|
||||
| In DB but not in CSV | 10 |
|
||||
| Devices both sides, DB metadata still NULL on ≥1 field | 162 (resolved by importer run) |
|
||||
|
||||
After running `import_drivers_csv.py --only-null --apply` (2026-04-27): 154
|
||||
devices updated, 8 already complete, 0 inserted. Coverage now: `assigned_city`
|
||||
152/172, `cost_centre` 150/172, `vehicle_brand` 2/172, `fuel_100km` 3/172.
|
||||
`assigned_route` / `vehicle_category` / `depot_address` remain 0/172 (CSV
|
||||
provided no values for these — Phase 1 follow-up).
|
||||
|
||||
The 10 in-DB-not-in-CSV IMEIs are listed in `260427_audit_output.txt`. They
|
||||
sit in `(blank)` or `fireside` accounts and surface in Grafana as
|
||||
`assigned_city = 'unassigned'` thanks to the existing COALESCE in
|
||||
`07_analytics_views.sql`.
|
||||
|
||||
## Disposition
|
||||
|
||||
For each IMEI in the "in DB but not in CSV" list, choose one and record why:
|
||||
|
||||
| IMEI | Account | Last seen | Disposition | Notes |
|
||||
|---|---|---|---|---|
|
||||
| | | | | |
|
||||
|
||||
**Disposition options:**
|
||||
|
||||
- **prune** — Delete from `tracksolid.devices`. Use when the unit is a stale
|
||||
test/decommissioned device that should never have synced. Capture the SQL
|
||||
before running:
|
||||
```sql
|
||||
DELETE FROM tracksolid.devices WHERE imei = '<imei>';
|
||||
```
|
||||
*Caveat:* foreign keys from `position_history`, `trips`, `alarms` must be
|
||||
considered first — these will block the delete if there's any history.
|
||||
Usually safer to leave-as-NULL.
|
||||
|
||||
- **leave-as-NULL** — Keep the row; metadata fields stay NULL. The device
|
||||
was auto-synced from a Tracksolid account that the CSV doesn't cover
|
||||
(likely `Fireside@HQ` rows that were left out of this Mitieng export).
|
||||
Grafana views already use `COALESCE(d.assigned_city, d.city, 'unassigned')`
|
||||
so these surface as "unassigned" but don't break dashboards.
|
||||
|
||||
- **addendum** — Add to a follow-up CSV and re-run the importer with
|
||||
`--apply`. Use when the device is legitimate fleet metadata was just
|
||||
missing from the export.
|
||||
|
||||
## Decision
|
||||
|
||||
<!-- Fill in once dispositions are chosen. -->
|
||||
|
||||
- Total devices reviewed: ___
|
||||
- Pruned: ___
|
||||
- Left-as-NULL: ___
|
||||
- Added via addendum: ___
|
||||
|
||||
After action, re-run `audit_device_reconciliation.py` and confirm the
|
||||
delta is what you expect.
|
||||
14
55_ts_coolify_gemini_prod.code-workspace
Normal file
14
55_ts_coolify_gemini_prod.code-workspace
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../60_karuracc-test-app"
|
||||
},
|
||||
{
|
||||
"path": "../62_ts_postman"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
31
CLAUDE.md
31
CLAUDE.md
|
|
@ -58,6 +58,7 @@ See `docs/CONNECTIONS.md` for the full shape. Summary:
|
|||
- **DB name:** `tracksolid_db` · **DB user:** `postgres` (internal) · `tracksolid_owner` (app) · `grafana_ro` (read-only)
|
||||
- **DB schemas:** `tracksolid` (live, single source of truth) · `dwh_gold` (aggregates) · `ops` (workshop / tickets / odometer) · `infrastructure`. The legacy `tracksolid_2` schema no longer exists — migrations 02–06 applied 2026-04-18.
|
||||
- **DB access:** `DATABASE_URL` points to `timescale_db:5432` (internal Docker network — not reachable locally). Use `docker exec` pattern above. See `docs/CONNECTIONS.md` for full reference.
|
||||
- **DWH target DB:** `tracksolid_dwh` at `31.97.44.246:5888` (separate PostGIS instance, public IP). Users: `dwh_owner` (bronze writes + `dwh_control`), `grafana_ro` (reads bronze/silver/gold/`dwh_control`). Always connect with `sslmode=require`. Fed by the n8n `dwh_extract` + `dwh_load_bronze` workflows — see `docs/DWH_PIPELINE.md`.
|
||||
- **Container naming:** Coolify appends a random suffix. Always resolve with:
|
||||
```bash
|
||||
docker ps --filter name=<service_name> --format "{{.Names}}" | head -1
|
||||
|
|
@ -80,9 +81,16 @@ run_migrations.py # Applies SQL migrations in order at container start
|
|||
docker-compose.yaml # Services: timescale_db, ingest_movement, ingest_events,
|
||||
# webhook_receiver, grafana
|
||||
grafana/ # Grafana provisioning (baked into image)
|
||||
n8n-workflows/ # n8n workflow exports
|
||||
n8n-workflows/ # n8n workflow exports (incl. dwh_extract, dwh_load_bronze)
|
||||
docs/ # Reference docs (connections, API, KPIs, project context)
|
||||
docs/DWH_PIPELINE.md # DWH pipeline operations runbook (setup, troubleshooting)
|
||||
docs/superpowers/ # Pitch specs and implementation plans (not deployed code)
|
||||
dwh/ # DWH migrations for tracksolid_dwh@31.97.44.246:5888
|
||||
# 260423_dwh_ddl_v1.sql — bronze/silver/gold schemas + roles
|
||||
# 261001_dwh_control.sql — watermarks + run log
|
||||
# 261002_bronze_constraints_audit.sql — ON CONFLICT key assertion
|
||||
# 261003_dwh_roles.sql — role contract assertion
|
||||
# 261004_dwh_observability_views.sql — freshness/failure views
|
||||
02_tracksolid_full_schema_rev.sql # Full schema bootstrap
|
||||
03..06_*.sql # Incremental migrations (06 adds assigned_city, dispatch_log, ops.*)
|
||||
07_analytics_views.sql # Analytics views migration (applied 2026-04-21)
|
||||
|
|
@ -140,6 +148,25 @@ tracksolid.v_sla_inflight -- §4.5 SLA panels (gated on ops.tickets)
|
|||
|
||||
All views carry a `COMMENT ON VIEW` referencing their spec — `\d+ tracksolid.v_*` shows the provenance.
|
||||
|
||||
**DWH bronze layer (separate DB `tracksolid_dwh`)** — populated by the n8n `dwh_extract` + `dwh_load_bronze` workflows. Operational details in `docs/DWH_PIPELINE.md`.
|
||||
|
||||
```sql
|
||||
-- bronze schema mirrors tracksolid.* (16 tables, DDL in dwh/260423_dwh_ddl_v1.sql)
|
||||
bronze.devices, bronze.live_positions -- snapshot tables (TRUNCATE + reload)
|
||||
bronze.position_history, bronze.trips,
|
||||
bronze.alarms, bronze.parking_events,
|
||||
bronze.device_events, bronze.ingestion_log -- incremental (watermark + ON CONFLICT DO NOTHING)
|
||||
-- Schema drift: bronze.trips.distance_km vs source tracksolid.trips.distance_m
|
||||
-- Extract SQL divides by 1000. Cross-ref FIX-M16.
|
||||
|
||||
-- dwh_control schema tracks pipeline state + observability
|
||||
dwh_control.extract_watermarks -- one row per incremental table
|
||||
dwh_control.extract_runs -- per-run audit log (status lifecycle)
|
||||
dwh_control.v_table_freshness -- Grafana: load lag per table
|
||||
dwh_control.v_recent_failures -- Grafana: failures in last 24h
|
||||
dwh_control.v_watermark_lag -- Grafana: extract vs. load lag per table
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. API Critical Facts
|
||||
|
|
@ -224,3 +251,5 @@ Latest full snapshot: `260412_baseline_report.md`
|
|||
| MEDIUM | Co-develop client KPI framework (see `docs/KPI_FRAMEWORK.md`) |
|
||||
| LOW | Populate geofences — depot boundaries, city zones |
|
||||
| LOW | Schedule nightly ETL: `SELECT dwh_gold.refresh_daily_metrics(CURRENT_DATE - 1)` (cron or n8n) |
|
||||
| HIGH | Deploy DWH bronze pipeline: apply `dwh/26100{1,2,3,4}.sql` to `tracksolid_dwh`, import + wire the two n8n workflows, verify first run via `dwh_control.v_table_freshness`. Runbook: `docs/DWH_PIPELINE.md` |
|
||||
| MEDIUM | Rotate `dwh_owner` / `grafana_ro` passwords on `tracksolid_dwh` — plaintext in `dwh/260423_dwh_ddl_v1.sql` is a pre-existing flaw to clean up separately |
|
||||
|
|
|
|||
385
DWH_Execution_Manual.md
Normal file
385
DWH_Execution_Manual.md
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
# DWH Execution Manual
|
||||
|
||||
> **Purpose:** A reusable playbook for building an `extract → blob → load` data warehouse bronze layer using n8n (or any equivalent orchestrator) + object storage + PostgreSQL/PostGIS. Generalised from the Fireside Tracksolid DWH pipeline (2026-04-24). Apply this pattern to future data projects to skip re-deriving the same decisions.
|
||||
>
|
||||
> **Reference implementation:** `dwh/26100{1..4}.sql` + `docs/DWH_PIPELINE.md` + the two `n8n-workflows/dwh_*.json` files. Treat those as the copy-paste template for the next project.
|
||||
|
||||
---
|
||||
|
||||
## 1. When to Use This Pattern
|
||||
|
||||
**Use it when all of these are true:**
|
||||
- You need an analytical replica of a production OLTP DB without letting analytical load hit the OLTP.
|
||||
- Source and target are separate PostgreSQL instances (possibly separate networks).
|
||||
- Data volumes are moderate: millions of rows per day, not billions.
|
||||
- You already have an orchestrator (n8n, Airflow, Prefect) and object storage (rustfs, S3, MinIO) in the stack.
|
||||
- Latency tolerance is hours, not minutes.
|
||||
|
||||
**Don't use it when:**
|
||||
- Sub-minute latency is required → use logical replication or CDC (Debezium, pg_logical, AWS DMS).
|
||||
- Volumes exceed ~100 GB/day → step up to Spark/Flink + columnar store (Iceberg, Delta).
|
||||
- Source and target are the same DB → just use materialized views or scheduled SQL.
|
||||
- You need exactly-once streaming semantics → this pattern is at-least-once + idempotent load.
|
||||
|
||||
---
|
||||
|
||||
## 2. The Core Pattern
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Source DB │──(a)──▶ Orchestr. │──(b)──▶ Object Store │──(c)──▶ Target DB │
|
||||
│ (OLTP) │ │ (extract) │ │ (CSVs) │ │ (bronze) │
|
||||
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
|
||||
│ ▲
|
||||
└──────────────(d)─────────────────────────────┘
|
||||
(load workflow, per CSV)
|
||||
|
||||
(a) Watermarked SELECT, closed upper bound
|
||||
(b) Atomic CSV upload with timestamped path
|
||||
(c) CSV stays until load confirms success
|
||||
(d) Load = BEGIN → INSERT ON CONFLICT → UPDATE watermark → UPDATE run log → COMMIT → move CSV
|
||||
```
|
||||
|
||||
**Why three stages and not two:**
|
||||
- **Audit trail** — every extracted CSV is a point-in-time snapshot you can replay.
|
||||
- **Decoupling** — target DB downtime doesn't lose data; the CSV waits.
|
||||
- **Observability** — blob listings are a second source of truth independent of the DB.
|
||||
|
||||
---
|
||||
|
||||
## 3. Pre-flight Checklist
|
||||
|
||||
Before writing any SQL or workflow JSON, confirm in writing:
|
||||
|
||||
- [ ] Source DB reachable from orchestrator (internal network preferred, VPN/public IP with `sslmode=require` otherwise).
|
||||
- [ ] Target DB reachable; you hold a superuser credential for one-time DDL.
|
||||
- [ ] Object storage bucket exists; credentials are configured in the orchestrator.
|
||||
- [ ] For each source table to extract, you have identified:
|
||||
- A **DB-insertion timestamp column** (not device/user-reported time), or "it's a snapshot table".
|
||||
- A **natural unique key** that already has a `PRIMARY KEY` or `UNIQUE` constraint on source (for the `ON CONFLICT` target on bronze).
|
||||
- Any **unit/column drift** between source and target (e.g., `distance_m` vs. `distance_km`).
|
||||
- [ ] Acceptable end-to-end latency (to calibrate cron cadence).
|
||||
- [ ] Security baseline: who writes bronze, who reads it, SSL requirement, password rotation cadence.
|
||||
|
||||
If any row is unchecked, pause and resolve it. Skipping this step is the #1 cause of pipelines that "worked in test but lose data in prod."
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase-by-Phase Execution
|
||||
|
||||
Execute in order. Phases are independent of each other within their phase, but phases have strict dependencies.
|
||||
|
||||
### Phase A — Target DB preparation
|
||||
|
||||
Apply three types of migrations, in numeric order:
|
||||
|
||||
1. **Bronze DDL** — one table per source table. Use `IF NOT EXISTS`; make it idempotent.
|
||||
2. **Control schema** — `dwh_control.extract_watermarks` + `dwh_control.extract_runs`.
|
||||
3. **Assertion migrations** — verify roles exist, verify every `ON CONFLICT` target is backed by a PK/UNIQUE (fail loudly if not).
|
||||
|
||||
Template files: `dwh/260423_dwh_ddl_v1.sql`, `dwh/261001_dwh_control.sql`, `dwh/261002_bronze_constraints_audit.sql`, `dwh/261003_dwh_roles.sql`.
|
||||
|
||||
**Role model:**
|
||||
- `<proj>_owner` — owns schemas, writes bronze + control tables.
|
||||
- `<proj>_ro` (or `grafana_ro`) — reads everything, writes nothing.
|
||||
- Never use `postgres` or another superuser from the orchestrator.
|
||||
|
||||
**Watermark seed:** set `last_extracted_at` to a date before any real data (`'2000-01-01T00:00:00Z'` is safe) so the first run back-fills all history in a single CSV per table.
|
||||
|
||||
### Phase B — Object storage
|
||||
|
||||
Create two prefixes per table:
|
||||
|
||||
```
|
||||
s3://<bucket>/<project>/exports/{table}/ # active CSVs, in-flight
|
||||
s3://<bucket>/<project>/processed/{table}/ # loaded CSVs, never deleted (audit)
|
||||
```
|
||||
|
||||
Naming convention: `{YYYYMMDD_HHMM}_{TZ}.csv` (e.g., `20260424_1400_EAT.csv`). Timezone in the filename because "08:00" means nothing a year from now without it.
|
||||
|
||||
Retention: match whatever backup retention is already in the stack (e.g., 30 days). `processed/` should outlive `exports/`.
|
||||
|
||||
### Phase C — Orchestrator credentials
|
||||
|
||||
Three credentials:
|
||||
|
||||
| Credential | Role | Purpose |
|
||||
|---|---|---|
|
||||
| `<proj>_source` | Read-only role on source DB | Extract queries |
|
||||
| `<proj>_dwh_target` | `<proj>_owner` on target DB | Bronze writes + control updates |
|
||||
| `<proj>_s3` | IAM user with `s3:PutObject`, `s3:GetObject`, `s3:ListBucket`, `s3:DeleteObject` on the prefix | CSV upload/download/move |
|
||||
|
||||
**Always** `sslmode=require` on any public-IP DB connection. Test each credential with the orchestrator's "Test connection" button before proceeding.
|
||||
|
||||
### Phase D — Load workflow (build this BEFORE the extract workflow)
|
||||
|
||||
Building load first lets you iterate with hand-crafted CSVs in blob storage before wiring up extract. Much faster feedback loop.
|
||||
|
||||
Load workflow input (parameters):
|
||||
```json
|
||||
{
|
||||
"table": "position_history",
|
||||
"csv_path": "s3://bucket/project/exports/position_history/20260424_1400_EAT.csv",
|
||||
"run_id": 12345,
|
||||
"run_started_at": "2026-04-24T11:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
Load workflow steps:
|
||||
1. Download CSV from blob storage.
|
||||
2. Parse CSV into rows.
|
||||
3. **Open transaction.**
|
||||
4. `INSERT INTO bronze.<table> (...) VALUES (...) ON CONFLICT (<natural_key>) DO NOTHING;`
|
||||
5. `UPDATE dwh_control.extract_watermarks SET last_extracted_at = :run_started_at, last_loaded_at = NOW(), rows_loaded_last_run = <count> WHERE table_name = :table;`
|
||||
6. `UPDATE dwh_control.extract_runs SET status = 'loaded', run_finished_at = NOW(), rows_loaded = <count> WHERE run_id = :run_id;`
|
||||
7. **Commit.**
|
||||
8. Move CSV from `exports/` to `processed/` (copy-then-delete; never delete before copy confirms).
|
||||
|
||||
**Non-negotiable invariants:**
|
||||
- Steps 3–7 are one transaction. If any fails, all rollback.
|
||||
- Step 8 only runs after commit. If step 8 fails, the next run will re-load the CSV (idempotent via ON CONFLICT) — not a data loss event.
|
||||
|
||||
### Phase E — Extract workflow
|
||||
|
||||
Extract workflow steps, per table:
|
||||
|
||||
1. Read current watermark: `SELECT last_extracted_at FROM dwh_control.extract_watermarks WHERE table_name = :table;`
|
||||
2. Capture `run_started_at = NOW()` (in the target DB's clock, not the orchestrator's — reduces clock-skew bugs).
|
||||
3. `INSERT INTO dwh_control.extract_runs (table_name, run_started_at, status) VALUES (:table, :run_started_at, 'extracting') RETURNING run_id;`
|
||||
4. Query source with closed upper bound:
|
||||
```sql
|
||||
SELECT <cols>
|
||||
FROM <source_schema>.<table>
|
||||
WHERE <watermark_col> > :last_extracted_at
|
||||
AND <watermark_col> <= :run_started_at
|
||||
ORDER BY <watermark_col>;
|
||||
```
|
||||
5. Render rows as CSV. For geometry columns: `CASE WHEN geom IS NULL THEN NULL ELSE ST_AsEWKT(geom) END`.
|
||||
6. Upload CSV to `s3://bucket/project/exports/{table}/{YYYYMMDD_HHMM}_{TZ}.csv`.
|
||||
7. `UPDATE dwh_control.extract_runs SET status = 'uploaded', rows_extracted = <count>, csv_path = :path WHERE run_id = :run_id;`
|
||||
8. Call load workflow with `{table, csv_path, run_id, run_started_at}`.
|
||||
|
||||
### Phase F — Schedule + observability
|
||||
|
||||
**Cron cadence:** start with 6–8 runs/day during active hours. Fold the overnight gap where traffic is low. Example: `0 5,8,11,14,17,20,23 * * *` TZ `Africa/Nairobi`.
|
||||
|
||||
**Three observability views** (readable by the RO role):
|
||||
|
||||
- `v_table_freshness` — per-table lag from last successful load. Drives the freshness alert.
|
||||
- `v_recent_failures` — failed runs in last 24h. Zero rows = healthy.
|
||||
- `v_watermark_lag` — extract vs. load lag per table. Distinguishes "nothing to extract" from "stuck".
|
||||
|
||||
Template file: `dwh/261004_dwh_observability_views.sql`.
|
||||
|
||||
**Grafana panels** (add at minimum):
|
||||
1. Freshness panel — red if any row in `v_table_freshness` has `lag > 4h`.
|
||||
2. Failures panel — red if `v_recent_failures` has any row.
|
||||
3. Row counts panel — daily bar chart from `extract_runs`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Design Principles (Do Not Skip)
|
||||
|
||||
### 5.1 Watermark on DB insertion time, not source-reported time
|
||||
|
||||
The watermark column must be "when the target DB got the row", not "when the device/user said it happened". Device clocks skew, webhooks arrive late, and batch imports backdate records. A source-reported watermark will silently drop rows that arrive out of order. Use `recorded_at`, `created_at`, `updated_at` (with `DEFAULT NOW()`), or `ingested_at` — never `gps_time` / `event_time` / `timestamp`.
|
||||
|
||||
### 5.2 Closed upper bound
|
||||
|
||||
Extract uses `> last_extracted_at AND <= run_started_at`. The closed upper bound prevents "row committed at `NOW()` during the extract query" from appearing in two adjacent runs. Without it, some rows are double-extracted (wasteful) or missed (data loss).
|
||||
|
||||
### 5.3 Idempotent load via natural unique keys
|
||||
|
||||
Every incremental bronze table needs a PRIMARY KEY or UNIQUE that matches the source's natural unique key. `ON CONFLICT DO NOTHING` makes re-running a CSV harmless. **Do not invent surrogate keys on bronze** — they defeat the ON CONFLICT guarantee. If the source has no natural key, fix the source or accept the table as a snapshot.
|
||||
|
||||
### 5.4 Transactional load boundary
|
||||
|
||||
Insert + watermark update + run-log update are one transaction. Splitting them creates "ghost" states where watermark advanced but rows didn't load, causing silent holes.
|
||||
|
||||
### 5.5 CSV audit trail — never delete
|
||||
|
||||
Moved-to-`processed/` CSVs are cheap ($0.023/GB/month on S3-class storage). They pay for themselves the first time you need to replay a window or debug a row-count mismatch.
|
||||
|
||||
### 5.6 PostGIS round-trip via EWKT
|
||||
|
||||
`ST_AsEWKT(geom)` on extract, `ST_GeomFromEWKT(ewkt)` on load. Preserves SRID inline. Do NOT store `ST_AsText` + separate SRID column — it doubles the chance of mismatch. Guard NULLs: `CASE WHEN geom IS NULL THEN NULL ELSE ST_AsEWKT(geom) END`.
|
||||
|
||||
### 5.7 Fail loud, fail early
|
||||
|
||||
Audit migrations (roles, constraints) should `RAISE EXCEPTION` with a bullet list of what's missing. Silent success is worse than noisy failure — a missing PK surfaces three months later as "why are there duplicate trips?".
|
||||
|
||||
---
|
||||
|
||||
## 6. Snapshot vs. Incremental Decision Matrix
|
||||
|
||||
| Signal | Snapshot (TRUNCATE + reload) | Incremental (watermark + append) |
|
||||
|---|---|---|
|
||||
| Row count | < ~10k | > ~10k |
|
||||
| Meaning of "current state" | Matters | Doesn't matter; history matters |
|
||||
| Deletes in source | Common | Rare |
|
||||
| Update frequency per row | High | Low (append-mostly) |
|
||||
| Natural unique key | May not exist | Must exist |
|
||||
| Example | `devices`, `live_positions`, `geofences` | `position_history`, `trips`, `alarms`, event logs |
|
||||
|
||||
When in doubt: **snapshot is simpler**. Only escalate to incremental when the snapshot CSV would exceed a few MB per run.
|
||||
|
||||
---
|
||||
|
||||
## 7. Observability Contract
|
||||
|
||||
Every pipeline adds these three views to its control schema — no exceptions:
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE VIEW <control>.v_table_freshness AS
|
||||
SELECT table_name,
|
||||
MAX(run_finished_at) AS last_loaded_at,
|
||||
NOW() - MAX(run_finished_at) AS lag,
|
||||
COUNT(*) FILTER (WHERE run_started_at > NOW() - INTERVAL '24 hours') AS loads_last_24h
|
||||
FROM <control>.extract_runs
|
||||
WHERE status = 'loaded'
|
||||
GROUP BY table_name;
|
||||
|
||||
CREATE OR REPLACE VIEW <control>.v_recent_failures AS
|
||||
SELECT run_id, table_name, run_started_at, run_finished_at, csv_path, error_message
|
||||
FROM <control>.extract_runs
|
||||
WHERE status = 'failed' AND run_started_at > NOW() - INTERVAL '24 hours'
|
||||
ORDER BY run_started_at DESC;
|
||||
|
||||
CREATE OR REPLACE VIEW <control>.v_watermark_lag AS
|
||||
SELECT table_name, last_extracted_at, last_loaded_at, rows_loaded_last_run,
|
||||
NOW() - last_loaded_at AS load_lag,
|
||||
NOW() - last_extracted_at AS extract_lag
|
||||
FROM <control>.extract_watermarks;
|
||||
```
|
||||
|
||||
Wire a Grafana alert on each view. Test the alert by manually failing a run before go-live.
|
||||
|
||||
---
|
||||
|
||||
## 8. Schema Drift Handling
|
||||
|
||||
Schema drift between source and bronze is inevitable. Two rules:
|
||||
|
||||
1. **Detect at design time.** Diff source DDL against bronze DDL before writing any extract SQL. Unit changes (metres vs. km), renamed columns, and added nullable columns are the usual suspects.
|
||||
2. **Fix in the extract query, not the load.** Put all transformations in the SELECT so the CSV on disk already matches the bronze column names and units. The load workflow should be dumb — CSV column N goes to bronze column N.
|
||||
|
||||
Document every drift in the runbook (§5 of the operations runbook). Future developers WILL hit them.
|
||||
|
||||
---
|
||||
|
||||
## 9. Verification Gates
|
||||
|
||||
### Pre-deploy (before first cron tick)
|
||||
|
||||
- [ ] Every migration applied successfully.
|
||||
- [ ] Control tables seeded (one watermark row per incremental table).
|
||||
- [ ] Every credential's "Test connection" passes.
|
||||
- [ ] Blob storage prefixes exist.
|
||||
- [ ] Manual workflow trigger succeeds end-to-end for one table.
|
||||
|
||||
### First run (manual trigger of extract workflow)
|
||||
|
||||
- [ ] Every processed table has a row in `extract_runs` with `status='loaded'`.
|
||||
- [ ] Row-count parity with source (± in-flight writes): `SELECT COUNT(*) FROM <source>` vs. `SELECT COUNT(*) FROM bronze.<table>`.
|
||||
- [ ] Geometry columns round-trip cleanly: `SELECT ST_AsText(geom) FROM bronze.<table> LIMIT 5` returns valid POINTs.
|
||||
- [ ] All CSVs moved from `exports/` to `processed/`.
|
||||
|
||||
### Steady-state (after 24h / first full schedule cycle)
|
||||
|
||||
- [ ] `v_table_freshness` shows lag < cadence × 2 for every table.
|
||||
- [ ] `v_recent_failures` is empty.
|
||||
- [ ] Row counts in bronze growing at expected rate.
|
||||
|
||||
Only declare "done" after all three gates pass.
|
||||
|
||||
---
|
||||
|
||||
## 10. Scheduling Calibration
|
||||
|
||||
Tradeoffs:
|
||||
|
||||
| Cadence | Pros | Cons |
|
||||
|---|---|---|
|
||||
| Every 15 min | Low lag, small CSVs | High orchestrator churn, noisy alerts |
|
||||
| Every 3 h (recommended) | Predictable, fits ops windows, tolerable lag | Overnight backlog carries to morning |
|
||||
| Nightly (once/day) | Cheap, simple | Unacceptable for real-time panels |
|
||||
|
||||
Rule of thumb: cadence = 25–50% of your latency tolerance. 4h latency budget → 1-2h cadence.
|
||||
|
||||
Fold cadence around traffic patterns. Don't run 24× at 1-hour intervals if the source generates zero rows between midnight and 05:00.
|
||||
|
||||
---
|
||||
|
||||
## 11. Common Failure Modes & Recovery
|
||||
|
||||
| Failure | Symptom | Fix |
|
||||
|---|---|---|
|
||||
| CSV stuck in `exports/` | `v_recent_failures` has a row; CSV never moved | Next scheduled run retries automatically (idempotent). If persistent, open orchestrator logs by `run_id`. |
|
||||
| Table marked `loading` for >1 cadence | n8n executor crashed mid-transaction | DB rolled back. Next run retries. If stuck >2 cadences, manually re-trigger the extract. |
|
||||
| Row counts diverge > 1% | CSV parse error silently dropped rows | `rows_extracted != rows_loaded` in `extract_runs` — inspect the CSV for malformed rows. |
|
||||
| Geometry loads as NULL | EWKT serialisation broke | Check for missing `CASE WHEN geom IS NULL` guard in extract SQL. |
|
||||
| Distance/units 1000× wrong | Schema drift not caught | Check extract SQL for the unit conversion (see §8). |
|
||||
|
||||
**Back-fill a window:**
|
||||
```sql
|
||||
UPDATE <control>.extract_watermarks
|
||||
SET last_extracted_at = NOW() - INTERVAL '24 hours'
|
||||
WHERE table_name = '<table>';
|
||||
```
|
||||
Next run re-extracts the gap. `ON CONFLICT DO NOTHING` filters duplicates.
|
||||
|
||||
**Full reseed (nuclear):**
|
||||
```sql
|
||||
UPDATE <control>.extract_watermarks
|
||||
SET last_extracted_at = '2000-01-01T00:00:00Z'
|
||||
WHERE table_name = '<table>';
|
||||
```
|
||||
Next run back-fills all history in one very large CSV. Expected; it moves to `processed/` on success.
|
||||
|
||||
---
|
||||
|
||||
## 12. Security Baseline
|
||||
|
||||
- Two roles minimum: owner (writes) and RO (reads). Never use superuser from the orchestrator.
|
||||
- `sslmode=require` on every public-IP DB connection.
|
||||
- Passwords never in committed SQL — use placeholder tokens (`CHANGE_ME_BEFORE_APPLY`) and swap in-session during apply. Document rotation in the runbook.
|
||||
- Blob storage credentials scoped to the project's prefix, not the whole bucket.
|
||||
- Rotate all credentials before go-live (don't reuse the ones that were flying around in design conversations).
|
||||
|
||||
---
|
||||
|
||||
## 13. Reusability Checklist (Applying to a New Project)
|
||||
|
||||
When starting a new data project, copy the Tracksolid DWH layout and edit these points:
|
||||
|
||||
- [ ] Rename schemas: `<proj>_control` instead of `dwh_control` if multiple DWHs share a DB.
|
||||
- [ ] Adjust `<proj>_owner` / `<proj>_ro` role names.
|
||||
- [ ] Update bucket prefix: `s3://<bucket>/<project>/exports|processed/`.
|
||||
- [ ] Re-do the snapshot/incremental decision for every source table (§6).
|
||||
- [ ] Identify watermark columns and natural unique keys for every incremental table (§5.1, §5.3).
|
||||
- [ ] Map schema drift before writing extract SQL (§8).
|
||||
- [ ] Calibrate cadence to the new project's latency budget (§10).
|
||||
- [ ] Ship the three observability views (§7) — even if nobody will look at them in week one.
|
||||
- [ ] Write the runbook from the template: follow `docs/DWH_PIPELINE.md` section-for-section.
|
||||
- [ ] Run the verification gates (§9) before declaring done.
|
||||
|
||||
---
|
||||
|
||||
## 14. Reference Implementation (Tracksolid DWH)
|
||||
|
||||
These files are the copy-paste template:
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `dwh/260423_dwh_ddl_v1.sql` | Bronze DDL + roles + schemas |
|
||||
| `dwh/261001_dwh_control.sql` | Control schema (watermarks + run log) |
|
||||
| `dwh/261002_bronze_constraints_audit.sql` | ON CONFLICT key assertion |
|
||||
| `dwh/261003_dwh_roles.sql` | Role contract assertion |
|
||||
| `dwh/261004_dwh_observability_views.sql` | Freshness/failure/watermark views |
|
||||
| `docs/DWH_PIPELINE.md` | Operations runbook (troubleshooting, manual re-run, rotation) |
|
||||
| `docs/superpowers/specs/2026-04-24-n8n-dwh-bronze-pipeline-design.md` | Design spec (why each decision) |
|
||||
| `docs/superpowers/plans/2026-04-24-n8n-dwh-bronze-pipeline.md` | Task-by-task implementation plan |
|
||||
| `n8n-workflows/dwh_extract.json` | Extract workflow (reference) |
|
||||
| `n8n-workflows/dwh_load_bronze.json` | Load workflow (reference) |
|
||||
|
||||
**For the next project, fork this manual first, then adapt.** Do not re-design from scratch — the seven design principles in §5 are the parts people keep getting wrong.
|
||||
205
audit_device_reconciliation.py
Normal file
205
audit_device_reconciliation.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
"""
|
||||
audit_device_reconciliation.py — 162-vs-182 device delta audit
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Phase 0.2 of the Business Analytics redesign.
|
||||
|
||||
Compares `20260427_FSG_Vehicles_mitieng.csv` (162 rows) to `tracksolid.devices`
|
||||
(182 rows at last check) and reports:
|
||||
1. Per-account row counts on each side.
|
||||
2. IMEIs in DB but not in CSV (the unexplained delta — typically auto-synced
|
||||
API rows with no business metadata).
|
||||
3. IMEIs in CSV but not in DB (should be empty after a successful import).
|
||||
4. IMEIs present on both sides where DB metadata is still NULL on key fields.
|
||||
|
||||
Usage:
|
||||
# Read-only audit, prints to stdout.
|
||||
python audit_device_reconciliation.py
|
||||
|
||||
# Same, but write output to a file (useful for the reconciliation report).
|
||||
python audit_device_reconciliation.py --out 260427_audit_output.txt
|
||||
|
||||
# Use a different CSV path
|
||||
python audit_device_reconciliation.py --csv path/to/file.csv
|
||||
|
||||
This script makes no writes — safe to run on prod.
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
from ts_shared_rev import get_conn, get_logger
|
||||
|
||||
log = get_logger("device_audit")
|
||||
|
||||
DEFAULT_CSV_PATH = Path(__file__).parent / "20260427_FSG_Vehicles_mitieng.csv"
|
||||
|
||||
# Fields whose NULL-ness on devices that DO appear in CSV would indicate
|
||||
# a stale import.
|
||||
META_FIELDS = ("assigned_city", "cost_centre", "assigned_route",
|
||||
"vehicle_category", "vehicle_brand", "fuel_100km",
|
||||
"depot_address", "driver_name", "vehicle_number")
|
||||
|
||||
|
||||
def load_csv_index(csv_path: Path) -> dict[str, dict]:
|
||||
rows: dict[str, dict] = {}
|
||||
with open(csv_path, encoding="utf-8-sig", newline="") as f:
|
||||
for row in csv.DictReader(f):
|
||||
imei = (row.get("imei") or "").strip()
|
||||
if imei:
|
||||
rows[imei] = row
|
||||
return rows
|
||||
|
||||
|
||||
def load_db_index() -> dict[str, dict]:
|
||||
cols = (
|
||||
"imei", "account", "assigned_city", "city", "cost_centre",
|
||||
"assigned_route", "vehicle_category", "vehicle_brand", "fuel_100km",
|
||||
"depot_address", "driver_name", "vehicle_number", "device_name",
|
||||
"last_synced_at", "created_at",
|
||||
)
|
||||
devices: dict[str, dict] = {}
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"SELECT {', '.join(cols)} FROM tracksolid.devices")
|
||||
names = [d[0] for d in cur.description]
|
||||
for row in cur.fetchall():
|
||||
rec = dict(zip(names, row))
|
||||
devices[rec["imei"]] = rec
|
||||
return devices
|
||||
|
||||
|
||||
def _csv_account(row: dict) -> str:
|
||||
return (row.get("account") or "").strip() or "(blank)"
|
||||
|
||||
|
||||
def _db_account(row: dict) -> str:
|
||||
return (row.get("account") or "").strip() or "(blank)"
|
||||
|
||||
|
||||
def _is_blank(v) -> bool:
|
||||
if v is None:
|
||||
return True
|
||||
s = str(v).strip()
|
||||
return s == "" or s.upper() == "NULL"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Reconcile vehicle CSV against tracksolid.devices")
|
||||
parser.add_argument("--csv", default=str(DEFAULT_CSV_PATH))
|
||||
parser.add_argument("--out", default=None, help="Write report to this file in addition to stdout")
|
||||
args = parser.parse_args()
|
||||
|
||||
csv_path = Path(args.csv)
|
||||
if not csv_path.exists():
|
||||
log.error("CSV not found: %s", csv_path)
|
||||
return 1
|
||||
|
||||
csv_idx = load_csv_index(csv_path)
|
||||
db_idx = load_db_index()
|
||||
|
||||
csv_imeis = set(csv_idx)
|
||||
db_imeis = set(db_idx)
|
||||
|
||||
only_db = sorted(db_imeis - csv_imeis)
|
||||
only_csv = sorted(csv_imeis - db_imeis)
|
||||
both = csv_imeis & db_imeis
|
||||
|
||||
csv_accounts = Counter(_csv_account(r) for r in csv_idx.values())
|
||||
db_accounts = Counter(_db_account(r) for r in db_idx.values())
|
||||
|
||||
out_lines: list[str] = []
|
||||
def w(line: str = "") -> None:
|
||||
out_lines.append(line)
|
||||
print(line)
|
||||
|
||||
w("=" * 76)
|
||||
w(" Device reconciliation — CSV vs tracksolid.devices")
|
||||
w("=" * 76)
|
||||
w(f" CSV file : {csv_path.name}")
|
||||
w(f" CSV row count : {len(csv_idx)}")
|
||||
w(f" DB row count : {len(db_idx)}")
|
||||
w(f" Delta (DB-CSV) : {len(only_db):+d}")
|
||||
w("")
|
||||
|
||||
w("─ Per-account breakdown ─────────────────────────────────────────────────")
|
||||
all_accounts = sorted(set(csv_accounts) | set(db_accounts))
|
||||
w(f" {'account':<30} {'CSV':>6} {'DB':>6} {'delta':>7}")
|
||||
for acct in all_accounts:
|
||||
c, d = csv_accounts.get(acct, 0), db_accounts.get(acct, 0)
|
||||
w(f" {acct:<30} {c:>6} {d:>6} {d-c:>+7}")
|
||||
w("")
|
||||
|
||||
w(f"─ IMEIs in DB but NOT in CSV ({len(only_db)}) ─────────────────────────────")
|
||||
if not only_db:
|
||||
w(" (none — DB is a strict subset of CSV)")
|
||||
else:
|
||||
w(f" {'imei':<18} {'account':<22} {'city':<10} {'last_synced_at':<28} {'device_name'}")
|
||||
for imei in only_db:
|
||||
r = db_idx[imei]
|
||||
w(f" {imei:<18} {(r.get('account') or ''):<22} "
|
||||
f"{(r.get('assigned_city') or r.get('city') or ''):<10} "
|
||||
f"{str(r.get('last_synced_at') or ''):<28} "
|
||||
f"{r.get('device_name') or ''}")
|
||||
w("")
|
||||
|
||||
w(f"─ IMEIs in CSV but NOT in DB ({len(only_csv)}) ─────────────────────────────")
|
||||
if not only_csv:
|
||||
w(" (none — every CSV row has a corresponding device row)")
|
||||
else:
|
||||
w(f" {'imei':<18} {'account':<22} {'assigned_city':<14} {'cost_centre':<14} {'driver_name'}")
|
||||
for imei in only_csv:
|
||||
r = csv_idx[imei]
|
||||
w(f" {imei:<18} {(r.get('account') or ''):<22} "
|
||||
f"{(r.get('assigned_city') or ''):<14} "
|
||||
f"{(r.get('cost_centre') or ''):<14} "
|
||||
f"{r.get('driver_name') or ''}")
|
||||
w("")
|
||||
|
||||
# Stale-metadata audit: in both, but DB is still NULL on key fields.
|
||||
stale: list[tuple[str, list[str]]] = []
|
||||
for imei in sorted(both):
|
||||
d = db_idx[imei]
|
||||
blanks = [f for f in META_FIELDS if _is_blank(d.get(f))]
|
||||
if blanks:
|
||||
stale.append((imei, blanks))
|
||||
|
||||
w(f"─ Devices in both, but DB metadata still NULL ({len(stale)}) ──────────────")
|
||||
if not stale:
|
||||
w(" (none — import looks complete on intersecting devices)")
|
||||
else:
|
||||
w(" Likely cause: import_drivers_csv.py has not been re-run with --apply")
|
||||
w(" against the new CSV, or rows had 'Identification' placeholders.")
|
||||
w("")
|
||||
w(f" {'imei':<18} blank_fields")
|
||||
for imei, blanks in stale[:30]: # cap output
|
||||
w(f" {imei:<18} {', '.join(blanks)}")
|
||||
if len(stale) > 30:
|
||||
w(f" ... and {len(stale) - 30} more")
|
||||
w("")
|
||||
|
||||
w("─ Suggested next step ───────────────────────────────────────────────────")
|
||||
if only_db:
|
||||
w(" Inspect the IMEIs above. Decide one of:")
|
||||
w(" (a) Prune — delete from tracksolid.devices if they are stale "
|
||||
"test/decommissioned units.")
|
||||
w(" (b) Leave-as-NULL — keep them as auto-synced API rows; their "
|
||||
"metadata stays NULL until added to a future CSV.")
|
||||
w(" (c) Addendum — add them to the CSV (or a sidecar CSV) and re-run "
|
||||
"import_drivers_csv.py --apply.")
|
||||
w(" Document the choice in 260427_device_reconciliation.md.")
|
||||
else:
|
||||
w(" CSV and DB are reconciled. No further action.")
|
||||
|
||||
if args.out:
|
||||
Path(args.out).write_text("\n".join(out_lines), encoding="utf-8")
|
||||
print(f"\nReport also written to {args.out}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
229
backfill_trips_enrichment.py
Normal file
229
backfill_trips_enrichment.py
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
"""
|
||||
backfill_trips_enrichment.py — One-shot enrichment of historical tracksolid.trips rows
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Migration 09 added route_geom, start/end_address, vehicle_plate, waypoints_count.
|
||||
poll_trips() fills these for new trips going forward; this script backfills
|
||||
existing rows where the new columns are NULL by reconstructing data from
|
||||
position_history (the GPS trail is already there) and Nominatim.
|
||||
|
||||
Behaviour:
|
||||
• Selects rows where route_geom IS NULL OR start_geom IS NULL
|
||||
(covers the original 8 historical poll-ingested trips and any future
|
||||
rows that landed before position_history caught up).
|
||||
• Per row: runs the same 4-subquery enrichment poll_trips uses, then
|
||||
reverse-geocodes start/end via Nominatim.
|
||||
• Writes only via COALESCE — never overwrites webhook-supplied data.
|
||||
• Logs each run to tracksolid.ingestion_log with endpoint='backfill_trips_enrichment'.
|
||||
|
||||
Usage:
|
||||
# Dry-run — shows counts only, writes nothing
|
||||
python backfill_trips_enrichment.py
|
||||
|
||||
# Apply changes
|
||||
python backfill_trips_enrichment.py --apply
|
||||
|
||||
# Scope to a single device
|
||||
python backfill_trips_enrichment.py --imei 862798052707896 --apply
|
||||
|
||||
# Limit to trips since a date (UTC)
|
||||
python backfill_trips_enrichment.py --since 2026-04-01 --apply
|
||||
|
||||
# Skip Nominatim reverse-geocoding (geometry/plate/idle only — runs in
|
||||
# minutes instead of hours when backfilling thousands of rows). Addresses
|
||||
# remain NULL for these rows and will be filled by future poll_trips
|
||||
# cycles only for new trips, not retroactively.
|
||||
python backfill_trips_enrichment.py --skip-geocode --apply
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import time
|
||||
|
||||
from ts_shared_rev import (
|
||||
get_conn,
|
||||
get_logger,
|
||||
log_ingestion,
|
||||
reverse_geocode,
|
||||
)
|
||||
|
||||
log = get_logger("backfill_trips")
|
||||
|
||||
_ENRICH_QUERY = """
|
||||
SELECT
|
||||
(SELECT geom FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time >= %s
|
||||
ORDER BY gps_time ASC LIMIT 1) AS start_geom,
|
||||
(SELECT ST_Y(geom) FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time >= %s
|
||||
ORDER BY gps_time ASC LIMIT 1) AS start_lat,
|
||||
(SELECT ST_X(geom) FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time >= %s
|
||||
ORDER BY gps_time ASC LIMIT 1) AS start_lng,
|
||||
(SELECT geom FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time <= %s
|
||||
ORDER BY gps_time DESC LIMIT 1) AS end_geom,
|
||||
(SELECT ST_Y(geom) FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time <= %s
|
||||
ORDER BY gps_time DESC LIMIT 1) AS end_lat,
|
||||
(SELECT ST_X(geom) FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time <= %s
|
||||
ORDER BY gps_time DESC LIMIT 1) AS end_lng,
|
||||
(SELECT ST_MakeLine(geom ORDER BY gps_time)
|
||||
FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time BETWEEN %s AND %s
|
||||
AND geom IS NOT NULL) AS route_geom,
|
||||
(SELECT COUNT(*) FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time BETWEEN %s AND %s) AS waypoints_count
|
||||
"""
|
||||
|
||||
|
||||
def _select_targets(cur, imei: str | None, since: str | None) -> list[tuple]:
|
||||
"""Return rows that need enrichment, as (id, imei, start_time, end_time)."""
|
||||
sql = """
|
||||
SELECT id, imei, start_time, end_time, vehicle_plate
|
||||
FROM tracksolid.trips
|
||||
WHERE (route_geom IS NULL OR start_geom IS NULL)
|
||||
AND end_time IS NOT NULL
|
||||
"""
|
||||
params: list = []
|
||||
if imei:
|
||||
sql += " AND imei = %s"
|
||||
params.append(imei)
|
||||
if since:
|
||||
sql += " AND start_time >= %s"
|
||||
params.append(since)
|
||||
sql += " ORDER BY start_time"
|
||||
cur.execute(sql, params)
|
||||
return cur.fetchall()
|
||||
|
||||
|
||||
def _load_plates_cache(cur) -> dict[str, str]:
|
||||
cur.execute("""
|
||||
SELECT imei, vehicle_number
|
||||
FROM tracksolid.devices
|
||||
WHERE vehicle_number IS NOT NULL
|
||||
""")
|
||||
return {imei: plate for imei, plate in cur.fetchall()}
|
||||
|
||||
|
||||
def run(apply: bool, filter_imei: str | None, since: str | None,
|
||||
skip_geocode: bool = False) -> None:
|
||||
t0 = time.time()
|
||||
enriched = degenerate = no_fixes = failed = 0
|
||||
if skip_geocode:
|
||||
log.info("Reverse-geocoding disabled (--skip-geocode). Addresses will stay NULL.")
|
||||
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
plates = _load_plates_cache(cur)
|
||||
targets = _select_targets(cur, filter_imei, since)
|
||||
|
||||
log.info(
|
||||
"Found %d trip(s) needing enrichment%s%s.",
|
||||
len(targets),
|
||||
f" for imei={filter_imei}" if filter_imei else "",
|
||||
f" since={since}" if since else "",
|
||||
)
|
||||
|
||||
for trip_id, imei, start_time, end_time, existing_plate in targets:
|
||||
try:
|
||||
cur.execute(_ENRICH_QUERY, (
|
||||
imei, start_time,
|
||||
imei, start_time,
|
||||
imei, start_time,
|
||||
imei, end_time,
|
||||
imei, end_time,
|
||||
imei, end_time,
|
||||
imei, start_time, end_time,
|
||||
imei, start_time, end_time,
|
||||
))
|
||||
(start_geom, start_lat, start_lng,
|
||||
end_geom, end_lat, end_lng,
|
||||
route_geom, waypoints_count) = cur.fetchone()
|
||||
|
||||
if waypoints_count == 0:
|
||||
no_fixes += 1
|
||||
log.info(
|
||||
" trip id=%s imei=%s start=%s — no GPS fixes in window, skipping",
|
||||
trip_id, imei, start_time,
|
||||
)
|
||||
continue
|
||||
|
||||
if waypoints_count < 2:
|
||||
# Not enough fixes for a polyline. Still capture the
|
||||
# single endpoint geom and address.
|
||||
degenerate += 1
|
||||
log.info(
|
||||
" trip id=%s imei=%s — only %d fix(es), no route_geom",
|
||||
trip_id, imei, waypoints_count,
|
||||
)
|
||||
|
||||
if skip_geocode:
|
||||
start_address = end_address = None
|
||||
else:
|
||||
start_address = reverse_geocode(start_lat, start_lng)
|
||||
end_address = reverse_geocode(end_lat, end_lng)
|
||||
vehicle_plate = existing_plate or plates.get(imei)
|
||||
|
||||
log.info(
|
||||
" trip id=%s imei=%s waypoints=%d start=%s end=%s",
|
||||
trip_id, imei, waypoints_count, start_address, end_address,
|
||||
)
|
||||
|
||||
if apply:
|
||||
cur.execute("""
|
||||
UPDATE tracksolid.trips SET
|
||||
start_geom = COALESCE(start_geom, %s),
|
||||
end_geom = COALESCE(end_geom, %s),
|
||||
route_geom = COALESCE(route_geom, %s),
|
||||
waypoints_count = COALESCE(waypoints_count, %s),
|
||||
start_address = COALESCE(start_address, %s),
|
||||
end_address = COALESCE(end_address, %s),
|
||||
vehicle_plate = COALESCE(vehicle_plate, %s)
|
||||
WHERE id = %s
|
||||
""", (
|
||||
start_geom, end_geom, route_geom, waypoints_count,
|
||||
start_address, end_address, vehicle_plate,
|
||||
trip_id,
|
||||
))
|
||||
enriched += 1
|
||||
except Exception:
|
||||
failed += 1
|
||||
log.warning(
|
||||
"Failed to enrich trip id=%s imei=%s",
|
||||
trip_id, imei, exc_info=True,
|
||||
)
|
||||
|
||||
if apply:
|
||||
log_ingestion(
|
||||
cur, "backfill_trips_enrichment",
|
||||
imei_count=len(targets),
|
||||
upserted=0, inserted=enriched,
|
||||
duration_ms=int((time.time() - t0) * 1000),
|
||||
success=(failed == 0),
|
||||
)
|
||||
|
||||
mode = "APPLIED" if apply else "DRY-RUN"
|
||||
print(f"\n{'='*60}")
|
||||
print(f" {mode} COMPLETE")
|
||||
print(f"{'='*60}")
|
||||
print(f" Trips enriched : {enriched}")
|
||||
print(f" Degenerate (<2 fixes) : {degenerate}")
|
||||
print(f" Skipped (no fixes) : {no_fixes}")
|
||||
print(f" Failed : {failed}")
|
||||
if not apply:
|
||||
print("\n Run with --apply to commit changes.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Backfill route_geom / start_geom / end_geom / addresses on tracksolid.trips."
|
||||
)
|
||||
parser.add_argument("--apply", action="store_true", help="Write changes to DB (default: dry-run)")
|
||||
parser.add_argument("--imei", default=None, help="Limit to a single IMEI")
|
||||
parser.add_argument("--since", default=None, help="Only trips with start_time >= YYYY-MM-DD (UTC)")
|
||||
parser.add_argument("--skip-geocode", action="store_true", help="Skip Nominatim reverse-geocoding (fast path for large backfills)")
|
||||
args = parser.parse_args()
|
||||
|
||||
run(apply=args.apply, filter_imei=args.imei, since=args.since,
|
||||
skip_geocode=args.skip_geocode)
|
||||
0
backup/manualtrigger_backuprest.md
Normal file
0
backup/manualtrigger_backuprest.md
Normal file
0
connecting_python_tracksolid.md
Normal file
0
connecting_python_tracksolid.md
Normal file
252
docs/DWH_PIPELINE.md
Normal file
252
docs/DWH_PIPELINE.md
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
# DWH Pipeline — Operations Runbook
|
||||
|
||||
**Pipeline:** n8n extract + load into `tracksolid_dwh` bronze schema
|
||||
**Design spec:** `docs/superpowers/specs/2026-04-24-n8n-dwh-bronze-pipeline-design.md`
|
||||
**Implementation plan:** `docs/superpowers/plans/2026-04-24-n8n-dwh-bronze-pipeline.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. What This Pipeline Does
|
||||
|
||||
Every ~3 hours during active hours (7 runs/day, 05:00–23:00 EAT), n8n extracts 8 tables from the production `tracksolid_db` (Coolify internal network), writes each as a timestamped CSV to rustfs, then loads each CSV into the `bronze` schema on `tracksolid_dwh` (31.97.44.246:5888). Rustfs CSVs are moved to `dwh/processed/` after a successful load — never deleted.
|
||||
|
||||
Two n8n workflows:
|
||||
- **`dwh_extract`** — cron-triggered, iterates tables in sequence, writes CSVs, calls `dwh_load_bronze` per table.
|
||||
- **`dwh_load_bronze`** — triggered per-table by `dwh_extract`, loads one CSV inside a single transaction (insert → update watermark → update run log → move CSV).
|
||||
|
||||
---
|
||||
|
||||
## 2. Key Locations
|
||||
|
||||
| What | Where |
|
||||
|---|---|
|
||||
| Source DB | `tracksolid_db` on Coolify (internal Docker network, `timescale_db:5432`) |
|
||||
| Target DB | `tracksolid_dwh` at `31.97.44.246:5888` |
|
||||
| Rustfs bucket | `fleet-db` (same bucket used by pg_dump backups) |
|
||||
| Active CSVs | `s3://fleet-db/dwh/exports/{table}/{YYYYMMDD_HHMM}_EAT.csv` |
|
||||
| Processed CSVs | `s3://fleet-db/dwh/processed/{table}/{YYYYMMDD_HHMM}_EAT.csv` |
|
||||
| Control schema | `dwh_control` in `tracksolid_dwh` |
|
||||
| Migrations | `dwh/26*.sql` applied in numeric order |
|
||||
|
||||
---
|
||||
|
||||
## 3. First-Time Setup
|
||||
|
||||
Apply migrations to `tracksolid_dwh` in numeric order, as a superuser (not `dwh_owner`):
|
||||
|
||||
```bash
|
||||
PSQL="psql 'postgres://postgres:***@31.97.44.246:5888/tracksolid_dwh?sslmode=require'"
|
||||
|
||||
$PSQL -f dwh/260423_dwh_ddl_v1.sql # Bronze DDL, roles, schemas
|
||||
$PSQL -f dwh/261001_dwh_control.sql # Watermarks + run log
|
||||
$PSQL -f dwh/261002_bronze_constraints_audit.sql # Assertion: ON CONFLICT keys exist
|
||||
$PSQL -f dwh/261003_dwh_roles.sql # Assertion: roles + grants present
|
||||
$PSQL -f dwh/261004_dwh_observability_views.sql # Freshness/failure views
|
||||
```
|
||||
|
||||
Each migration is idempotent. Audit files (261002, 261003) raise an exception with a bullet list of what is missing if the contract is broken — re-apply the relevant predecessor file and try again.
|
||||
|
||||
### Rustfs prefixes
|
||||
|
||||
```bash
|
||||
aws --endpoint ${RUSTFS_ENDPOINT} s3api put-object \
|
||||
--bucket fleet-db --key dwh/exports/
|
||||
aws --endpoint ${RUSTFS_ENDPOINT} s3api put-object \
|
||||
--bucket fleet-db --key dwh/processed/
|
||||
```
|
||||
|
||||
### n8n credentials
|
||||
|
||||
Three credentials, all configured in the n8n UI before importing workflows:
|
||||
|
||||
| Credential | Target | User | Notes |
|
||||
|---|---|---|---|
|
||||
| `tracksolid_source` | Coolify internal → `tracksolid_db` | `grafana_ro` | Read-only; no `sslmode` needed on internal network |
|
||||
| `tracksolid_dwh_target` | `31.97.44.246:5888` → `tracksolid_dwh` | `dwh_owner` | **Must set `sslmode=require`** — public IP |
|
||||
| `rustfs_s3` | `${RUSTFS_ENDPOINT}` | `${RUSTFS_ACCESS_KEY}` | Same creds as pg_dump backup sidecar |
|
||||
|
||||
Test each credential via the n8n "Test connection" button before enabling the cron schedule.
|
||||
|
||||
---
|
||||
|
||||
## 4. Schedule
|
||||
|
||||
n8n Schedule node, Africa/Nairobi TZ: `0 5,8,11,14,17,20,23 * * *`
|
||||
|
||||
- 7 runs/day at 05:00, 08:00, 11:00, 14:00, 17:00, 20:00, 23:00 EAT
|
||||
- Overnight gap (23:00 → 05:00 = 6h) by design — device traffic minimal
|
||||
- First-of-day run carries the overnight backlog (watermark picks up where 23:00 left off)
|
||||
|
||||
---
|
||||
|
||||
## 5. What Each Table Does on Every Run
|
||||
|
||||
### Snapshot tables (TRUNCATE + full reload)
|
||||
|
||||
`bronze.devices`, `bronze.live_positions` — small state tables, "current state" semantics. Full replace every run.
|
||||
|
||||
### Incremental tables (watermark + append-with-dedup)
|
||||
|
||||
| Bronze table | Source watermark column | ON CONFLICT target |
|
||||
|---|---|---|
|
||||
| `position_history` | `recorded_at` (DB insertion time) | `(imei, gps_time)` |
|
||||
| `trips` | `updated_at` | `id` |
|
||||
| `alarms` | `updated_at` | `id` |
|
||||
| `parking_events` | `updated_at` | `id` |
|
||||
| `device_events` | `created_at` | `id` |
|
||||
| `ingestion_log` | `run_at` | `id` |
|
||||
|
||||
Watermark bounds are closed upper: `WHERE <col> > last_extracted_at AND <col> <= :run_started_at`.
|
||||
|
||||
### Schema drift to handle in extract SQL
|
||||
|
||||
- **`trips.distance_m` → `bronze.trips.distance_km`**: source stores metres, bronze expects km. Extract SQL: `SELECT ..., distance_m/1000.0 AS distance_km, ...`. Cross-reference: FIX-M16 in `CLAUDE.md`.
|
||||
|
||||
### PostGIS geometry round-trip
|
||||
|
||||
All six geometry columns (`live_positions`, `position_history`, `trips.start_geom`, `trips.end_geom`, `parking_events`, `alarms`) use EWKT serialisation:
|
||||
|
||||
```sql
|
||||
-- Extract
|
||||
SELECT ..., CASE WHEN geom IS NULL THEN NULL ELSE ST_AsEWKT(geom) END AS geom_ewkt FROM ...;
|
||||
|
||||
-- Load
|
||||
INSERT INTO bronze... (..., geom) VALUES (..., ST_GeomFromEWKT(:geom_ewkt));
|
||||
```
|
||||
|
||||
SRID 4326 is preserved inline; no separate SRID step required.
|
||||
|
||||
---
|
||||
|
||||
## 6. Verifying a Healthy Run
|
||||
|
||||
### Immediate sanity checks (after any scheduled run)
|
||||
|
||||
```sql
|
||||
-- Any failures in the last hour?
|
||||
SELECT * FROM dwh_control.v_recent_failures WHERE run_started_at > NOW() - INTERVAL '1 hour';
|
||||
|
||||
-- All tables loaded in last 4h?
|
||||
SELECT * FROM dwh_control.v_table_freshness WHERE lag > INTERVAL '4 hours';
|
||||
|
||||
-- Watermarks advancing?
|
||||
SELECT * FROM dwh_control.v_watermark_lag ORDER BY extract_lag DESC;
|
||||
```
|
||||
|
||||
### Row-count parity (spot check weekly)
|
||||
|
||||
```sql
|
||||
-- Source
|
||||
SELECT COUNT(*) FROM tracksolid.position_history;
|
||||
-- Target
|
||||
SELECT COUNT(*) FROM bronze.position_history;
|
||||
```
|
||||
|
||||
Numbers should match ± rows inserted between the two queries. Persistent gap > 1% → investigate CSV parse errors or a dropped batch.
|
||||
|
||||
### Geometry round-trip
|
||||
|
||||
```sql
|
||||
SELECT ST_AsText(geom) FROM bronze.position_history WHERE geom IS NOT NULL LIMIT 5;
|
||||
-- Should return valid POINT(lng lat), not NULL or garbage.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Troubleshooting
|
||||
|
||||
### "A table is stale (`v_table_freshness` shows lag > 4h)"
|
||||
|
||||
1. Check `v_recent_failures` for that table. If a row exists, read `error_message`.
|
||||
2. If `status='loading'` in `extract_runs` for that table, a load is in progress — wait for the next cron tick. If it stays `loading` across two ticks, the n8n executor crashed mid-transaction; the DB rolled back, and the next run will retry naturally.
|
||||
3. If no failure row and no in-progress row, the extract workflow never fired — check n8n execution logs for the cron trigger.
|
||||
|
||||
### "A CSV is stuck in `dwh/exports/`"
|
||||
|
||||
The load failed after upload. The next scheduled run will re-process it (the watermark did not advance, so the extract SQL returns the same window). Safe to leave. If multiple days of CSVs pile up, the load workflow has a persistent bug — open n8n execution logs for the specific `run_id` in `extract_runs`.
|
||||
|
||||
### "Row counts diverge more than ~1%"
|
||||
|
||||
Usually one of:
|
||||
- A retry window overlapped the PK range and some rows lost the race with a concurrent source-side write. Re-trigger the extract for that window manually (see §8).
|
||||
- CSV parse error silently dropped a row. Check `extract_runs.rows_extracted` vs. `rows_loaded` — if they differ, the loader found malformed CSV.
|
||||
|
||||
### "Geometry loaded as NULL"
|
||||
|
||||
EWKT serialisation broke on the extract side. Verify the source query has the `CASE WHEN geom IS NULL` guard — without it, `ST_AsEWKT(NULL)` returns `NULL` correctly but an empty geometry returns `'GEOMETRYCOLLECTION EMPTY'` which `ST_GeomFromEWKT` rejects.
|
||||
|
||||
### "`bronze.trips.distance_km` values are 1000× too large"
|
||||
|
||||
The extract query is missing `/1000.0` on the source `distance_m` column. See §5 "Schema drift".
|
||||
|
||||
---
|
||||
|
||||
## 8. Manual Re-Run
|
||||
|
||||
### Re-run a single table for the current window
|
||||
|
||||
1. Open n8n, go to `dwh_extract` workflow.
|
||||
2. Locate the branch for that table.
|
||||
3. Click **Execute Workflow** → selects that table only.
|
||||
4. Confirm a new row appears in `dwh_control.extract_runs` with `status='loaded'`.
|
||||
|
||||
### Back-fill a historical window
|
||||
|
||||
The extract workflow respects the watermark; to re-extract a window, rewind the watermark:
|
||||
|
||||
```sql
|
||||
-- Rewind position_history to 24h ago
|
||||
UPDATE dwh_control.extract_watermarks
|
||||
SET last_extracted_at = NOW() - INTERVAL '24 hours'
|
||||
WHERE table_name = 'position_history';
|
||||
```
|
||||
|
||||
Next scheduled run will re-extract the gap. Loads are idempotent (`ON CONFLICT DO NOTHING` on the PK), so duplicate rows are filtered at the bronze boundary.
|
||||
|
||||
### Full reseed (nuclear option)
|
||||
|
||||
```sql
|
||||
-- Restart position_history from the beginning
|
||||
UPDATE dwh_control.extract_watermarks
|
||||
SET last_extracted_at = '2026-01-01T00:00:00Z'
|
||||
WHERE table_name = 'position_history';
|
||||
```
|
||||
|
||||
The first post-reseed run will produce one very large CSV (~all history). The rustfs `exports/` prefix will briefly hold a multi-GB object. Expected; it moves to `processed/` on success.
|
||||
|
||||
---
|
||||
|
||||
## 9. Credential Rotation
|
||||
|
||||
`260423_dwh_ddl_v1.sql` commits role passwords in plaintext — a pre-existing flaw to be cleaned up separately.
|
||||
|
||||
To rotate:
|
||||
|
||||
```sql
|
||||
-- As superuser on tracksolid_dwh:
|
||||
ALTER ROLE dwh_owner PASSWORD '<new secret>';
|
||||
ALTER ROLE grafana_ro PASSWORD '<new secret>';
|
||||
```
|
||||
|
||||
Then update the matching n8n credential and Grafana datasource. Never commit the new password — store in `.env` if needed for scripts, or keep exclusively inside n8n/Grafana credential stores.
|
||||
|
||||
---
|
||||
|
||||
## 10. Known Quirks
|
||||
|
||||
| Quirk | Source | Handling |
|
||||
|---|---|---|
|
||||
| `trips.distance_m` → `bronze.trips.distance_km` | Schema drift between source and bronze | Divide in extract SQL (§5) |
|
||||
| Hypertable row counts read 0 in `pg_stat_user_tables` | TimescaleDB quirk | Always `SELECT COUNT(*)` directly |
|
||||
| `parking_events` can be empty for days | Endpoint returns empty; not a failure | Zero rows loaded is a valid run outcome |
|
||||
| First run of each day larger | Overnight backlog | Expected; plan watermark design |
|
||||
| `last_extracted_at` default `2026-01-01` | Seed value from 261001 | First run on a new table back-fills all history |
|
||||
|
||||
---
|
||||
|
||||
## 11. Out of Scope (follow-up)
|
||||
|
||||
- Silver/gold transformations — `silver` and `gold` schemas exist but contain no views.
|
||||
- Grafana dashboard panels — these views are the data source; panels TBD.
|
||||
- OBD / fault codes / fuel / temperature / LBS / heartbeats — webhooks not yet registered; add a watermark row + extract branch when they start reporting.
|
||||
- Bronze schema evolution tooling — additive changes via numbered migrations is fine for one pipeline; revisit if scope grows.
|
||||
1732
docs/superpowers/plans/2026-04-24-n8n-dwh-bronze-pipeline.md
Normal file
1732
docs/superpowers/plans/2026-04-24-n8n-dwh-bronze-pipeline.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,320 @@
|
|||
# n8n DWH Bronze Layer Pipeline — Design & Plan
|
||||
|
||||
**Date:** 2026-04-24
|
||||
**Status:** Awaiting approval
|
||||
**Repo:** `/Users/davidkiania/Downloads/55_ts_coolify_gemini_prod`
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Fireside's Tracksolid fleet pipeline currently ingests telemetry into a single production DB (`tracksolid_db`, TimescaleDB/PostGIS on Coolify at `stage.rahamafresh.com`). There is no downstream data warehouse, so every analytical query hits the live operational DB — risking contention as Grafana panels and ad-hoc analysis scale. A full medallion-architecture bronze DDL exists on disk (`dwh/260423_dwh_ddl_v1.sql`) but has never been populated.
|
||||
|
||||
The user wants to build the **first layer of that DWH** using n8n (already running on the same Coolify instance, already connected to both source and target DBs). The design has two n8n workflows:
|
||||
|
||||
1. **Workflow 1 — Extract**: pull tables from the source `tracksolid_db` (Coolify-hosted TimescaleDB, reached via the same internal Docker network n8n is on), write CSVs to rustfs blob storage.
|
||||
2. **Workflow 2 — Load**: pick up those CSVs and upsert into the bronze schema inside `tracksolid_dwh` (PostGIS) on the separate server `31.97.44.246:5888`.
|
||||
|
||||
**Confirmed connection targets:**
|
||||
- **Source:** `tracksolid_db` on the Coolify stack — n8n connects via internal Docker network (trial confirmed working).
|
||||
- **Target:** `tracksolid_dwh` at `31.97.44.246:5888` — a separate PostGIS instance. Schemas `bronze`, `silver`, `gold`, plus `dwh_control` all live in this one database.
|
||||
|
||||
The intermediate rustfs CSV layer (a) gives a durable audit trail of every extract, (b) decouples source-DB availability from target-DB availability (a remote-DB outage doesn't lose data — the CSV waits in `exports/`), and (c) matches how rustfs is already used in the stack (pg_dump backups).
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ n8n (Coolify instance) │
|
||||
│ │
|
||||
│ Workflow 1: dwh_extract │
|
||||
│ Schedule: cron 0 5,8,11,14,17,20,23 * * * │
|
||||
│ (Africa/Nairobi, 7 runs/day) │
|
||||
│ Steps per table: │
|
||||
│ 1. Read watermark from target control table │
|
||||
│ 2. Query source with watermark bounds │
|
||||
│ 3. Render rows as CSV │
|
||||
│ 4. Upload CSV to rustfs │
|
||||
│ 5. Insert row into dwh_control.extract_runs │
|
||||
│ (status='uploaded') │
|
||||
│ 6. Execute Workflow 2 for this CSV │
|
||||
│ │
|
||||
│ Workflow 2: dwh_load_bronze │
|
||||
│ Trigger: Execute Workflow (from Workflow 1) │
|
||||
│ Input: { table, csv_path, run_id, │
|
||||
│ run_started_at } │
|
||||
│ Steps: │
|
||||
│ 1. Download CSV from rustfs │
|
||||
│ 2. Parse CSV │
|
||||
│ 3. BEGIN │
|
||||
│ INSERT ... ON CONFLICT DO NOTHING │
|
||||
│ UPDATE extract_watermarks │
|
||||
│ UPDATE extract_runs SET status='loaded' │
|
||||
│ COMMIT │
|
||||
│ 4. Move CSV: dwh/exports/ → dwh/processed/ │
|
||||
└──────────────────────────────────────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
tracksolid_db rustfs (fleet-db) tracksolid_dwh (PostGIS)
|
||||
(Coolify internal) /dwh/exports/ 31.97.44.246:5888
|
||||
/dwh/processed/ dwh_control.extract_watermarks
|
||||
dwh_control.extract_runs
|
||||
bronze.devices
|
||||
bronze.position_history
|
||||
bronze.trips
|
||||
bronze.alarms
|
||||
bronze.parking_events
|
||||
bronze.device_events
|
||||
bronze.live_positions
|
||||
bronze.ingestion_log
|
||||
```
|
||||
|
||||
**Rustfs path convention:**
|
||||
- Active export: `s3://fleet-db/dwh/exports/{table}/{YYYYMMDD_HHMM}_EAT.csv`
|
||||
- After successful load: moved to `s3://fleet-db/dwh/processed/{table}/{YYYYMMDD_HHMM}_EAT.csv`
|
||||
- Never deleted — this is the audit trail.
|
||||
|
||||
---
|
||||
|
||||
## Table-by-Table Extraction Strategy
|
||||
|
||||
### Snapshot tables (TRUNCATE + full reload every run)
|
||||
|
||||
Small state-based tables where "current state" matters, not history.
|
||||
|
||||
| Source table | Rows | Bronze target |
|
||||
|---|---|---|
|
||||
| `tracksolid.devices` | 63 | `bronze.devices` |
|
||||
| `tracksolid.live_positions` | 19 | `bronze.live_positions` |
|
||||
|
||||
**Load pattern:**
|
||||
```sql
|
||||
BEGIN;
|
||||
TRUNCATE bronze.devices;
|
||||
INSERT INTO bronze.devices (...) VALUES (...);
|
||||
UPDATE dwh_control.extract_watermarks SET last_loaded_at = NOW() WHERE table_name='devices';
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### Incremental tables (watermark + append-with-dedup)
|
||||
|
||||
Append-only event/history tables. Watermark is the **DB insertion timestamp**, not the device-reported timestamp, so out-of-order device clocks / delayed pushes can't cause silent data loss.
|
||||
|
||||
| Source table | Watermark column | Natural unique key (exists in source) | Bronze conflict target |
|
||||
|---|---|---|---|
|
||||
| `tracksolid.position_history` | `recorded_at` | `(imei, gps_time)` | `(imei, gps_time)` |
|
||||
| `tracksolid.trips` | `updated_at` | `(imei, start_time)` | `id` |
|
||||
| `tracksolid.alarms` | `updated_at` | `(imei, alarm_type, alarm_time)` | `id` |
|
||||
| `tracksolid.parking_events` | `updated_at` | `(imei, start_time, event_type)` | `id` |
|
||||
| `tracksolid.device_events` | `created_at` | `(imei, event_type, event_time)` | `id` |
|
||||
| `tracksolid.ingestion_log` | `run_at` | PK `id` | `id` |
|
||||
|
||||
**Extract pattern (closed upper bound to avoid boundary drift):**
|
||||
```sql
|
||||
SELECT <cols>, ST_AsEWKT(geom) AS geom_ewkt
|
||||
FROM tracksolid.position_history
|
||||
WHERE recorded_at > :last_extracted_at
|
||||
AND recorded_at <= :run_started_at
|
||||
ORDER BY recorded_at;
|
||||
```
|
||||
|
||||
**Load pattern (idempotent):**
|
||||
```sql
|
||||
BEGIN;
|
||||
INSERT INTO bronze.position_history (imei, gps_time, geom, lat, lng, ...)
|
||||
SELECT imei, gps_time, ST_GeomFromEWKT(geom_ewkt), lat, lng, ...
|
||||
FROM csv_stage
|
||||
ON CONFLICT (imei, gps_time) DO NOTHING;
|
||||
|
||||
UPDATE dwh_control.extract_watermarks
|
||||
SET last_extracted_at = :run_started_at,
|
||||
last_loaded_at = NOW(),
|
||||
rows_loaded_last_run = <count>
|
||||
WHERE table_name = 'position_history';
|
||||
|
||||
UPDATE dwh_control.extract_runs
|
||||
SET status = 'loaded', run_finished_at = NOW(), rows_loaded = <count>
|
||||
WHERE run_id = :run_id;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### First-run behaviour
|
||||
|
||||
`extract_watermarks` seeded with `last_extracted_at = '2026-01-01T00:00:00Z'` so the first run back-fills all historical data in a single CSV per table.
|
||||
|
||||
### Skipped for now (no data, webhooks pending)
|
||||
|
||||
`obd_readings`, `fault_codes`, `fuel_readings`, `temperature_readings`, `lbs_readings`, `heartbeats` — add later by copying the incremental pattern and seeding a watermark row.
|
||||
|
||||
---
|
||||
|
||||
## PostGIS Geometry Handling
|
||||
|
||||
Six source tables have `geometry(Point, 4326)` columns: `live_positions`, `position_history`, `trips` (start+end), `parking_events`, `alarms`.
|
||||
|
||||
- **Extract:** `ST_AsEWKT(geom) AS geom_ewkt` — preserves SRID inline (`SRID=4326;POINT(...)`)
|
||||
- **Load:** `ST_GeomFromEWKT(csv.geom_ewkt)` — no separate SRID step, no loss on round-trip
|
||||
- **NULL safety:** `CASE WHEN geom IS NULL THEN NULL ELSE ST_AsEWKT(geom) END`
|
||||
|
||||
---
|
||||
|
||||
## Control Tables (to add to `tracksolid_dwh`)
|
||||
|
||||
New migration file: `dwh/261001_dwh_control.sql` — applied once to `tracksolid_dwh@31.97.44.246:5888`.
|
||||
|
||||
```sql
|
||||
CREATE SCHEMA IF NOT EXISTS dwh_control;
|
||||
|
||||
CREATE TABLE dwh_control.extract_watermarks (
|
||||
table_name TEXT PRIMARY KEY,
|
||||
last_extracted_at TIMESTAMPTZ NOT NULL DEFAULT '2026-01-01T00:00:00Z',
|
||||
last_loaded_at TIMESTAMPTZ,
|
||||
rows_loaded_last_run INT,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE dwh_control.extract_runs (
|
||||
run_id BIGSERIAL PRIMARY KEY,
|
||||
table_name TEXT NOT NULL,
|
||||
run_started_at TIMESTAMPTZ NOT NULL,
|
||||
run_finished_at TIMESTAMPTZ,
|
||||
rows_extracted INT,
|
||||
rows_loaded INT,
|
||||
csv_path TEXT,
|
||||
status TEXT CHECK (status IN ('extracting','uploaded','loading','loaded','failed')),
|
||||
error_message TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_extract_runs_table_time ON dwh_control.extract_runs (table_name, run_started_at DESC);
|
||||
CREATE INDEX idx_extract_runs_status_time ON dwh_control.extract_runs (status, run_finished_at DESC);
|
||||
|
||||
-- Seed one row per incremental table
|
||||
INSERT INTO dwh_control.extract_watermarks (table_name) VALUES
|
||||
('position_history'), ('trips'), ('alarms'),
|
||||
('parking_events'), ('device_events'), ('ingestion_log');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scheduling
|
||||
|
||||
- **Cron:** `0 5,8,11,14,17,20,23 * * *` with TZ `Africa/Nairobi` (set in n8n schedule node).
|
||||
- **7 runs/day:** 05:00, 08:00, 11:00, 14:00, 17:00, 20:00, 23:00 EAT.
|
||||
- **Fits the 6–8/day requirement** with even 3-hour gaps in daytime and a silent overnight window (23:00 → 05:00 = 6h) which is fine because device traffic is minimal after hours.
|
||||
- First run of each day (05:00) will carry the overnight backlog — this is the expected behaviour of the watermark design.
|
||||
|
||||
---
|
||||
|
||||
## Error Handling & Observability
|
||||
|
||||
### Per-table isolation
|
||||
Workflow 1 iterates tables in sequence; a failure on one table does not block others. Every table's result (success or failure) is logged to `dwh_control.extract_runs`.
|
||||
|
||||
### Retryable failures
|
||||
If Workflow 2 fails mid-load: transaction rolls back → watermark stays → CSV stays in `exports/` → next scheduled run re-processes it (natural retry).
|
||||
|
||||
### Alerting (Grafana panels on `tracksolid_dwh`, read via `dwh_ro` role — see below)
|
||||
- **Freshness:** `SELECT table_name, NOW() - MAX(run_finished_at) AS lag FROM dwh_control.extract_runs WHERE status='loaded' GROUP BY 1 HAVING NOW() - MAX(run_finished_at) > INTERVAL '4 hours';`
|
||||
- **Failures in last hour:** `SELECT * FROM dwh_control.extract_runs WHERE status='failed' AND run_started_at > NOW() - INTERVAL '1 hour';`
|
||||
- **Row count sanity:** `rows_extracted != rows_loaded` flags CSV parse or load issues.
|
||||
|
||||
### n8n-level error workflow
|
||||
Attach an "Error Workflow" in both n8n workflows that posts to a webhook (existing pattern in `n8n-workflows/`) for immediate notification.
|
||||
|
||||
---
|
||||
|
||||
## Security & Credentials
|
||||
|
||||
Both DB credentials already exist in n8n (connections trialled and working). The required credential shapes are:
|
||||
|
||||
| n8n credential | Host / Port / DB | Recommended user | Usage |
|
||||
|---|---|---|---|
|
||||
| `tracksolid_source` | Coolify internal `timescale_db:5432` → DB `tracksolid_db` | `grafana_ro` (read-only) | Source extract queries |
|
||||
| `tracksolid_dwh_target` | `31.97.44.246:5888` → DB `tracksolid_dwh` | `dwh_owner` (scoped) | Bronze writes + control-table updates |
|
||||
| `rustfs_s3` | `${RUSTFS_ENDPOINT}` | `${RUSTFS_ACCESS_KEY}` | CSV upload/download/move |
|
||||
|
||||
### Credential-hardening recommendations (current state vs target state)
|
||||
|
||||
The trial connection string uses `postgres` (superuser) over a public IP. Two hardening steps to take before production:
|
||||
|
||||
1. **Create a scoped `dwh_owner` role** on `tracksolid_dwh` — owns only `bronze` + `dwh_control` schemas, cannot touch other DBs or cluster roles. n8n's `tracksolid_dwh_target` credential switches to this user.
|
||||
2. **Create a `dwh_ro` role** for Grafana panels — read-only across `bronze` + `dwh_control`. This is what the freshness/failure dashboards in §Error Handling use.
|
||||
3. **Enforce `sslmode=require`** on the `tracksolid_dwh_target` connection string (public-IP hop, cleartext otherwise).
|
||||
4. **Rotate the `postgres` password** that was shared in chat history — one-off cleanup, not a plan blocker.
|
||||
|
||||
All four are one-migration-file tasks and fit naturally into the `dwh/261001_dwh_control.sql` setup step.
|
||||
|
||||
---
|
||||
|
||||
## Files to Create / Modify
|
||||
|
||||
| Path | Action | Purpose |
|
||||
|---|---|---|
|
||||
| `dwh/261001_dwh_control.sql` | **new** | Control-schema migration (watermarks + run log) |
|
||||
| `dwh/260423_dwh_ddl_v1.sql` | **review** | Confirm bronze tables have matching unique constraints; patch if missing |
|
||||
| `n8n-workflows/dwh_extract.json` | **new** | Workflow 1 export |
|
||||
| `n8n-workflows/dwh_load_bronze.json` | **new** | Workflow 2 export |
|
||||
| `docs/DWH_PIPELINE.md` | **new** | Operations runbook (see verification section) |
|
||||
| `CLAUDE.md` §3, §4, §5, §10 | **update** | Add `tracksolid_dwh@31.97.44.246:5888` to §3 Connection Params; add bronze schema + n8n DWH workflows to codebase map; remove DWH item from Open Items |
|
||||
|
||||
**Existing utilities to reuse (do NOT reinvent):**
|
||||
- Rustfs env vars already wired in `docker-compose.yaml` (`RUSTFS_ENDPOINT`, `RUSTFS_ACCESS_KEY`, `RUSTFS_SECRET_KEY`, `RUSTFS_BUCKET`) — Workflow nodes read from the same `.env`.
|
||||
- Backup rustfs client logic in `backup/backup_db.sh` is the reference pattern for S3 auth shape.
|
||||
- Existing n8n workflow pattern in `n8n-workflows/jimi_pushgps.json` et al. for webhook trigger + HTTP-forward shape.
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
### Pre-deployment checks (before first cron trigger)
|
||||
1. **Bronze DDL applied:** `psql -h 31.97.44.246 -p 5888 -U dwh_owner -d tracksolid_dwh -c "\dt bronze.*"` lists 16 tables.
|
||||
2. **Control schema applied:** same connection, `\dt dwh_control.*` lists `extract_watermarks`, `extract_runs`.
|
||||
3. **Watermarks seeded:** `SELECT * FROM dwh_control.extract_watermarks;` returns 6 rows, all with `last_extracted_at = 2026-01-01`.
|
||||
4. **Roles created:** `\du` lists `dwh_owner` and `dwh_ro`; `postgres` superuser no longer used for n8n.
|
||||
5. **n8n credentials:** Test each credential individually in n8n UI — all three connect successfully (source via internal network, target via `31.97.44.246:5888` with `sslmode=require`).
|
||||
6. **Rustfs path exists:** `aws --endpoint ${RUSTFS_ENDPOINT} s3 ls s3://fleet-db/dwh/` — if missing, create `exports/` and `processed/` prefixes.
|
||||
|
||||
### First-run verification (manually trigger Workflow 1)
|
||||
1. `SELECT * FROM dwh_control.extract_runs ORDER BY run_id DESC LIMIT 20;` — 8 rows (one per table processed), all `status='loaded'`.
|
||||
2. `SELECT table_name, rows_loaded_last_run FROM dwh_control.extract_watermarks;` — non-zero for all incremental tables that have source data.
|
||||
3. Row-count parity:
|
||||
```sql
|
||||
-- on source (tracksolid_db, Coolify internal)
|
||||
SELECT COUNT(*) FROM tracksolid.position_history;
|
||||
-- on target (tracksolid_dwh @ 31.97.44.246:5888)
|
||||
SELECT COUNT(*) FROM bronze.position_history;
|
||||
```
|
||||
Numbers should match ± rows inserted in the narrow window between the two queries.
|
||||
4. **Geometry round-trip check:**
|
||||
```sql
|
||||
SELECT ST_AsText(geom) FROM bronze.position_history LIMIT 5;
|
||||
-- should return valid POINT(lng lat) values, not NULL or garbage
|
||||
```
|
||||
5. **Rustfs audit:** `aws s3 ls s3://fleet-db/dwh/processed/` — 8 CSV files present (one per table), originals no longer in `exports/`.
|
||||
|
||||
### Steady-state verification (after 24h / 7 runs)
|
||||
1. `SELECT table_name, NOW() - MAX(run_finished_at) FROM dwh_control.extract_runs WHERE status='loaded' GROUP BY 1;` — max lag < 3h 15min for every table.
|
||||
2. `SELECT COUNT(*) FROM dwh_control.extract_runs WHERE status='failed';` — zero.
|
||||
3. Grafana dashboard (to be added in a follow-up plan) shows freshness and row counts per table.
|
||||
|
||||
---
|
||||
|
||||
## Out of Scope (follow-up work)
|
||||
|
||||
- Silver/gold layer transformations (the DWH DDL defines schemas but no queries yet).
|
||||
- Bronze schema evolution tooling (manual migrations are acceptable for one pipeline).
|
||||
- Backfill of tables where webhooks aren't yet registered (OBD, fuel, temperature, LBS).
|
||||
- Grafana dashboard panels for the DWH — worth its own spec once we have a week of data to design around.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions (none blocking)
|
||||
|
||||
All design decisions resolved in the brainstorming session. Confirmed:
|
||||
- Source: `tracksolid_db` on Coolify, reached via internal Docker network.
|
||||
- Target: `tracksolid_dwh` at `31.97.44.246:5888` (public IP), schemas `bronze`/`silver`/`gold` + `dwh_control`.
|
||||
- Trial connections already working in n8n.
|
||||
|
||||
If any endpoint/credential changes during implementation, those are n8n-credential updates only — no design change.
|
||||
0
documents.txt
Normal file
0
documents.txt
Normal file
367
dwh/260423_dwh_ddl_v1.sql
Normal file
367
dwh/260423_dwh_ddl_v1.sql
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
-- =============================================================
|
||||
-- TRACKSOLID DWH SETUP & PERMISSIONS
|
||||
-- Target Database: tracksolid_dwh
|
||||
-- =============================================================
|
||||
|
||||
-- 1. EXTENSIONS
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
CREATE EXTENSION IF NOT EXISTS btree_gist;
|
||||
CREATE EXTENSION IF NOT EXISTS postgis; -- REQUIRED for geometry(Point,4326) columns
|
||||
|
||||
-- 2. ROLE CREATION (Idempotent)
|
||||
-- SECURITY: Passwords below are placeholders. Before applying this file:
|
||||
-- 1. Generate two strong secrets (e.g. `openssl rand -hex 24`)
|
||||
-- 2. Replace both CHANGE_ME_BEFORE_APPLY tokens in-session (do NOT commit real values)
|
||||
-- 3. Store the generated secrets in the n8n / Grafana credential stores only
|
||||
-- Rotation: `ALTER ROLE <role> PASSWORD '<new secret>'` as a superuser.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'dwh_owner') THEN
|
||||
CREATE ROLE dwh_owner WITH LOGIN PASSWORD 'CHANGE_ME_BEFORE_APPLY';
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'grafana_ro') THEN
|
||||
CREATE ROLE grafana_ro WITH LOGIN PASSWORD 'CHANGE_ME_BEFORE_APPLY';
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- Grant database connection
|
||||
GRANT CONNECT ON DATABASE tracksolid_dwh TO dwh_owner;
|
||||
GRANT CONNECT ON DATABASE tracksolid_dwh TO grafana_ro;
|
||||
|
||||
-- 3. SCHEMAS
|
||||
CREATE SCHEMA IF NOT EXISTS bronze AUTHORIZATION dwh_owner;
|
||||
CREATE SCHEMA IF NOT EXISTS silver AUTHORIZATION dwh_owner;
|
||||
CREATE SCHEMA IF NOT EXISTS gold AUTHORIZATION dwh_owner;
|
||||
|
||||
ALTER DATABASE tracksolid_dwh SET search_path TO bronze, silver, gold, public;
|
||||
|
||||
-- 4. PERMISSIONS & DEFAULT PRIVILEGES (Critical for Security & Automation)
|
||||
-- Schema access
|
||||
GRANT USAGE, CREATE ON SCHEMA bronze TO dwh_owner;
|
||||
GRANT USAGE, CREATE ON SCHEMA silver TO dwh_owner;
|
||||
GRANT USAGE, CREATE ON SCHEMA gold TO dwh_owner;
|
||||
GRANT USAGE ON SCHEMA bronze TO grafana_ro;
|
||||
GRANT USAGE ON SCHEMA silver TO grafana_ro;
|
||||
GRANT USAGE ON SCHEMA gold TO grafana_ro;
|
||||
GRANT USAGE ON SCHEMA public TO dwh_owner, grafana_ro;
|
||||
|
||||
-- Existing table access for Grafana
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA bronze TO grafana_ro;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA silver TO grafana_ro;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA gold TO grafana_ro;
|
||||
|
||||
-- FUTURE table access: Any table created by dwh_owner will automatically be readable by grafana_ro
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE dwh_owner IN SCHEMA bronze GRANT SELECT ON TABLES TO grafana_ro;
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE dwh_owner IN SCHEMA silver GRANT SELECT ON TABLES TO grafana_ro;
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE dwh_owner IN SCHEMA gold GRANT SELECT ON TABLES TO grafana_ro;
|
||||
|
||||
-- 5. BRONZE SCHEMA TABLES
|
||||
-- Run as dwh_owner to ensure correct ownership & default privileges apply
|
||||
SET ROLE dwh_owner;
|
||||
SET search_path TO bronze, public;
|
||||
|
||||
-- 5.1 DEVICES (Slowly Changing Dimension - Type 2 handled in Silver)
|
||||
CREATE TABLE IF NOT EXISTS bronze.devices (
|
||||
imei TEXT PRIMARY KEY,
|
||||
device_name TEXT,
|
||||
mc_type TEXT,
|
||||
mc_type_use_scope TEXT,
|
||||
vehicle_name TEXT,
|
||||
vehicle_number TEXT,
|
||||
vehicle_models TEXT,
|
||||
vehicle_icon TEXT,
|
||||
vin TEXT,
|
||||
engine_number TEXT,
|
||||
vehicle_brand TEXT,
|
||||
fuel_100km NUMERIC(6,2),
|
||||
driver_name TEXT,
|
||||
driver_phone TEXT,
|
||||
sim TEXT,
|
||||
iccid TEXT,
|
||||
imsi TEXT,
|
||||
account TEXT,
|
||||
customer_name TEXT,
|
||||
device_group_id TEXT,
|
||||
device_group TEXT,
|
||||
activation_time TIMESTAMPTZ,
|
||||
expiration TIMESTAMPTZ,
|
||||
enabled_flag SMALLINT DEFAULT 1 NOT NULL,
|
||||
status TEXT DEFAULT 'active'::text,
|
||||
city TEXT,
|
||||
current_mileage_km NUMERIC(12,2),
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
last_synced_at TIMESTAMPTZ,
|
||||
vehicle_category TEXT,
|
||||
cost_centre TEXT,
|
||||
assigned_route TEXT,
|
||||
depot_geom geometry(Point,4326),
|
||||
depot_address TEXT,
|
||||
assigned_city TEXT,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.2 POSITION HISTORY (High-volume fact table)
|
||||
CREATE TABLE IF NOT EXISTS bronze.position_history (
|
||||
imei TEXT NOT NULL,
|
||||
gps_time TIMESTAMPTZ NOT NULL,
|
||||
geom geometry(Point,4326),
|
||||
lat DOUBLE PRECISION,
|
||||
lng DOUBLE PRECISION,
|
||||
speed NUMERIC(7,2),
|
||||
direction NUMERIC(6,2),
|
||||
acc_status TEXT,
|
||||
satellite SMALLINT,
|
||||
current_mileage NUMERIC(12,2),
|
||||
recorded_at TIMESTAMPTZ DEFAULT now(),
|
||||
altitude NUMERIC(8,2),
|
||||
post_type SMALLINT,
|
||||
source TEXT DEFAULT 'poll'::text,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
PRIMARY KEY (imei, gps_time)
|
||||
);
|
||||
|
||||
-- 5.3 TRIPS (Aggregated fact table)
|
||||
CREATE TABLE IF NOT EXISTS bronze.trips (
|
||||
id BIGINT NOT NULL,
|
||||
imei TEXT NOT NULL,
|
||||
start_time TIMESTAMPTZ NOT NULL,
|
||||
end_time TIMESTAMPTZ,
|
||||
start_geom geometry(Point,4326),
|
||||
end_geom geometry(Point,4326),
|
||||
distance_km NUMERIC(12,2),
|
||||
avg_speed_kmh NUMERIC(7,2),
|
||||
max_speed_kmh NUMERIC(7,2),
|
||||
updated_at TIMESTAMPTZ DEFAULT now(),
|
||||
fuel_consumed_l NUMERIC(8,2),
|
||||
idle_time_s INTEGER,
|
||||
driving_time_s INTEGER,
|
||||
trip_seq INTEGER,
|
||||
source TEXT DEFAULT 'poll'::text,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
-- 5.4 ALARMS (Event log fact table)
|
||||
CREATE TABLE IF NOT EXISTS bronze.alarms (
|
||||
id BIGINT PRIMARY KEY,
|
||||
imei TEXT,
|
||||
alarm_type TEXT,
|
||||
alarm_time TIMESTAMPTZ,
|
||||
geom geometry(Point,4326),
|
||||
lat DOUBLE PRECISION,
|
||||
lng DOUBLE PRECISION,
|
||||
speed NUMERIC(7,2),
|
||||
acc_status TEXT,
|
||||
updated_at TIMESTAMPTZ DEFAULT now(),
|
||||
alarm_name TEXT,
|
||||
source TEXT DEFAULT 'poll'::text,
|
||||
severity TEXT,
|
||||
geofence_id TEXT,
|
||||
geofence_name TEXT,
|
||||
acknowledged_at TIMESTAMPTZ,
|
||||
acknowledged_by TEXT,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.5 DEVICE EVENTS (Connection lifecycle)
|
||||
CREATE TABLE IF NOT EXISTS bronze.device_events (
|
||||
id BIGINT PRIMARY KEY,
|
||||
imei TEXT NOT NULL,
|
||||
event_type TEXT NOT NULL,
|
||||
event_time TIMESTAMPTZ NOT NULL,
|
||||
timezone TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.6 DISPATCH LOG (Operational/SLA tracking)
|
||||
CREATE TABLE IF NOT EXISTS bronze.dispatch_log (
|
||||
dispatch_id BIGINT PRIMARY KEY,
|
||||
ticket_id TEXT NOT NULL,
|
||||
imei TEXT NOT NULL,
|
||||
driver_name TEXT,
|
||||
job_lat DOUBLE PRECISION NOT NULL,
|
||||
job_lng DOUBLE PRECISION NOT NULL,
|
||||
job_geom geometry(Point,4326),
|
||||
assigned_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
first_movement_at TIMESTAMPTZ,
|
||||
on_site_at TIMESTAMPTZ,
|
||||
resolved_at TIMESTAMPTZ,
|
||||
cancelled_at TIMESTAMPTZ,
|
||||
distance_km NUMERIC(8,2),
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.7 FAULT CODES (OBD/DTC diagnostics)
|
||||
CREATE TABLE IF NOT EXISTS bronze.fault_codes (
|
||||
id BIGINT PRIMARY KEY,
|
||||
imei TEXT NOT NULL,
|
||||
reported_at TIMESTAMPTZ NOT NULL,
|
||||
fault_code TEXT NOT NULL,
|
||||
status_flags INTEGER,
|
||||
lat DOUBLE PRECISION,
|
||||
lng DOUBLE PRECISION,
|
||||
geom geometry(Point,4326),
|
||||
event_time TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.8 FUEL READINGS
|
||||
CREATE TABLE IF NOT EXISTS bronze.fuel_readings (
|
||||
imei TEXT NOT NULL,
|
||||
reading_time TIMESTAMPTZ NOT NULL,
|
||||
sensor_path TEXT,
|
||||
value NUMERIC(10,3),
|
||||
unit TEXT,
|
||||
lat DOUBLE PRECISION,
|
||||
lng DOUBLE PRECISION,
|
||||
geom geometry(Point,4326),
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
PRIMARY KEY (imei, reading_time)
|
||||
);
|
||||
|
||||
-- 5.9 GEOFENCES (Dimension/Reference)
|
||||
CREATE TABLE IF NOT EXISTS bronze.geofences (
|
||||
id BIGINT PRIMARY KEY,
|
||||
fence_id TEXT,
|
||||
fence_name TEXT NOT NULL,
|
||||
fence_type TEXT,
|
||||
geom geometry(Geometry,4326),
|
||||
radius_m NUMERIC(10,2),
|
||||
description TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.10 HEARTBEATS (Device health/ping)
|
||||
CREATE TABLE IF NOT EXISTS bronze.heartbeats (
|
||||
imei TEXT NOT NULL,
|
||||
gate_time TIMESTAMPTZ NOT NULL,
|
||||
power_level SMALLINT,
|
||||
gsm_signal SMALLINT,
|
||||
acc_status SMALLINT,
|
||||
power_status SMALLINT,
|
||||
fortify SMALLINT,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
PRIMARY KEY (imei, gate_time)
|
||||
);
|
||||
|
||||
-- 5.11 INGESTION LOG (Metadata for tracking loads)
|
||||
CREATE TABLE IF NOT EXISTS bronze.ingestion_log (
|
||||
id BIGINT PRIMARY KEY,
|
||||
run_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
endpoint TEXT NOT NULL,
|
||||
imei_count INTEGER DEFAULT 0 NOT NULL,
|
||||
rows_upserted INTEGER DEFAULT 0 NOT NULL,
|
||||
rows_inserted INTEGER DEFAULT 0 NOT NULL,
|
||||
duration_ms INTEGER DEFAULT 0 NOT NULL,
|
||||
success BOOLEAN DEFAULT true NOT NULL,
|
||||
error_code TEXT,
|
||||
error_message TEXT,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.12 LBS READINGS (Fallback positioning)
|
||||
CREATE TABLE IF NOT EXISTS bronze.lbs_readings (
|
||||
id BIGINT PRIMARY KEY,
|
||||
imei TEXT NOT NULL,
|
||||
gate_time TIMESTAMPTZ NOT NULL,
|
||||
post_type TEXT,
|
||||
lbs_data JSONB,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.13 LIVE POSITIONS (Current state snapshot)
|
||||
CREATE TABLE IF NOT EXISTS bronze.live_positions (
|
||||
imei TEXT PRIMARY KEY,
|
||||
geom geometry(Point,4326),
|
||||
lat DOUBLE PRECISION,
|
||||
lng DOUBLE PRECISION,
|
||||
pos_type TEXT,
|
||||
confidence SMALLINT,
|
||||
gps_time TIMESTAMPTZ,
|
||||
hb_time TIMESTAMPTZ,
|
||||
speed NUMERIC(7,2),
|
||||
direction NUMERIC(6,2),
|
||||
acc_status TEXT,
|
||||
gps_signal SMALLINT,
|
||||
gps_num SMALLINT,
|
||||
elec_quantity NUMERIC(5,2),
|
||||
power_value NUMERIC(5,2),
|
||||
battery_power_val NUMERIC(5,2),
|
||||
tracker_oil TEXT,
|
||||
temperature NUMERIC(8,2),
|
||||
current_mileage NUMERIC(12,2),
|
||||
device_status TEXT,
|
||||
expire_flag TEXT,
|
||||
activation_flag TEXT,
|
||||
loc_desc TEXT,
|
||||
recorded_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.14 OBD READINGS (Vehicle diagnostics)
|
||||
CREATE TABLE IF NOT EXISTS bronze.obd_readings (
|
||||
id BIGINT PRIMARY KEY,
|
||||
imei TEXT,
|
||||
reading_time TIMESTAMPTZ,
|
||||
engine_rpm INTEGER,
|
||||
fuel_level_pct NUMERIC(5,2),
|
||||
updated_at TIMESTAMPTZ DEFAULT now(),
|
||||
car_type SMALLINT,
|
||||
acc_state SMALLINT,
|
||||
status_flags INTEGER,
|
||||
lat DOUBLE PRECISION,
|
||||
lng DOUBLE PRECISION,
|
||||
geom geometry(Point,4326),
|
||||
obd_data JSONB,
|
||||
coolant_temp_c NUMERIC(6,2),
|
||||
battery_voltage NUMERIC(5,2),
|
||||
intake_pressure NUMERIC(6,2),
|
||||
throttle_pct NUMERIC(5,2),
|
||||
vehicle_speed NUMERIC(7,2),
|
||||
engine_load_pct NUMERIC(5,2),
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.15 PARKING EVENTS
|
||||
CREATE TABLE IF NOT EXISTS bronze.parking_events (
|
||||
id BIGINT PRIMARY KEY,
|
||||
imei TEXT NOT NULL,
|
||||
event_type TEXT,
|
||||
start_time TIMESTAMPTZ NOT NULL,
|
||||
end_time TIMESTAMPTZ,
|
||||
duration_seconds INTEGER,
|
||||
geom geometry(Point,4326),
|
||||
address TEXT,
|
||||
updated_at TIMESTAMPTZ DEFAULT now(),
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 5.16 TEMPERATURE READINGS (Cold chain sensors)
|
||||
CREATE TABLE IF NOT EXISTS bronze.temperature_readings (
|
||||
imei TEXT NOT NULL,
|
||||
reading_time TIMESTAMPTZ NOT NULL,
|
||||
temperature NUMERIC(6,2),
|
||||
humidity_pct NUMERIC(5,2),
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
ingested_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
PRIMARY KEY (imei, reading_time)
|
||||
);
|
||||
|
||||
-- Reset role back to superuser
|
||||
RESET ROLE;
|
||||
RESET search_path;
|
||||
|
||||
-- 6. VERIFICATION GRANTS (Ensure Grafana can query immediately)
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA bronze TO grafana_ro;
|
||||
GRANT USAGE ON SCHEMA bronze TO grafana_ro;
|
||||
163
dwh/260424_all_vehicles.csv
Normal file
163
dwh/260424_all_vehicles.csv
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
"imei","device_name","mc_type","mc_type_use_scope","vehicle_name","vehicle_number","vehicle_models","vehicle_icon","vin","engine_number","vehicle_brand","fuel_100km","driver_name","driver_phone","sim","iccid","imsi","account","customer_name","device_group_id","device_group","activation_time","expiration","enabled_flag","status","city","current_mileage_km","created_at","updated_at","last_synced_at","vehicle_category","cost_centre","assigned_route","depot_geom","depot_address","assigned_city"
|
||||
"353549090553685","Daniel Omondi - KMFF 099Z","AT4","personal","KMFF 099Z","KMFF 099Z","Motorbike","mtc",NULL,NULL,NULL,NULL,"Robert","0112794067","759336150","89254021334258404099","639021335840409","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-09-23 05:50:30+00","2040-09-23 23:59:59+00",1,"1",NULL,"2354.70","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"353549090561720","Wireless_Git","AT4","personal",NULL,NULL,NULL,"bus",NULL,NULL,NULL,NULL,NULL,NULL,"0701211913","89254021374215155053","639021371515505","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2025-06-09 09:12:50+00","2035-06-09 23:59:59+00",1,"1",NULL,"5992.43","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"353549090566281","KDR 592N","AT4","personal",NULL,NULL,NULL,"bus",NULL,NULL,NULL,NULL,NULL,NULL,"0797680464","89254021334258159693","639021335815969","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2024-11-08 04:01:30+00","2034-11-08 23:59:59+00",1,"1",NULL,"7771.90","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"353549090566885","Wireless GPS","AT4","personal",NULL,NULL,NULL,"bus",NULL,NULL,NULL,NULL,NULL,NULL,"0768445963","89254021334212352574","639021331235257","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2024-10-15 13:16:57+00","2034-10-15 23:59:59+00",1,"1",NULL,"17036.41","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"353549090567685","Daniel Kipkirui - KMFF 162Z","AT4","personal","KMFF 162Z","KMFF 162Z","Motorbike","mtc",NULL,NULL,NULL,NULL,"edwine","0112795498","742532058","89254021264260388966","639021266038896","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-09-23 05:09:39+00","2040-09-23 23:59:59+00",1,"1",NULL,"462.33","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"353549090567701","Wireless","AT4","personal",NULL,NULL,NULL,"bus",NULL,NULL,NULL,NULL,NULL,NULL,"0790176094","89254021394215205906","639021391520590","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2024-11-08 04:04:44+00","2034-11-08 23:59:59+00",1,"1",NULL,"16896.20","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081885410","Allan Owana - KDK 780K","GT06E","aotomobile","KDK 780K","KDK 780K","Probox","automobile",NULL,NULL,NULL,NULL,"Allan Owana",NULL,"703616117","89254021234222499854","639021232249985","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2019-06-19 09:32:22+00","2039-06-19 23:59:59+00",1,"1",NULL,"128853.11","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081886467","Gideon Kiprono - KCQ 215F","GT06E","aotomobile","KCQ 215F","OHS","Probox","automobile",NULL,NULL,NULL,"0.00","Gideon Kiprono",NULL,"746763076","89254021084186499865","639021088649986","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2019-06-30 09:30:00+00","2039-06-30 23:59:59+00",1,"1",NULL,"141057.46","2026-04-23 10:56:37.983314+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081886871","Kamonde KBA 467S","GT06E","aotomobile",NULL,NULL,NULL,"bus",NULL,NULL,NULL,NULL,NULL,NULL,"0746763083","89254021084186499873","639021088649987","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2019-06-30 09:09:14+00","2039-06-30 23:59:59+00",1,"1",NULL,"74183.36","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081886905","Kennedy Chege - KCQ 618K","GT06E","aotomobile","KCQ 618K","KCQ 618K","Probox","automobile",NULL,NULL,NULL,NULL,"Kennedy Chege",NULL,"746763132","89254021084186499923","639021088649992","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2019-06-30 07:08:35+00","2039-06-30 23:59:59+00",1,"1",NULL,"215608.19","2026-04-23 10:35:37.678371+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081887069","Wright Oseko - KCG 668W","GT06E","aotomobile","KCG 668W","KCG 668W","Probox","automobile",NULL,NULL,NULL,NULL,"Wright Oseko",NULL,"746763106","89254021084186499915","639021088649991","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2019-06-30 06:17:43+00","2039-06-30 23:59:59+00",1,"1",NULL,"239001.19","2026-04-23 11:00:08.769463+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081887192","Ndegwa Dancun - KCG 669W","GT06E","aotomobile","KCG 669W","KCG 669W","Probox","automobile",NULL,NULL,NULL,NULL,"Ndegwa Dancun",NULL,"746760191","89254021084186499501","639021088649950","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2019-06-15 10:26:15+00","2039-06-15 23:59:59+00",1,"1",NULL,"199191.85","2026-04-23 10:34:29.074112+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081891566","Simon Kamau - KCE 090R","GT06E","aotomobile","KCE 090R","KCE 090R","Probox","automobile",NULL,NULL,NULL,NULL,"Simon Kamau",NULL,"746760404","89254021084186499527","639021088649952","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2019-06-16 07:06:15+00","2039-06-16 23:59:59+00",1,"1",NULL,"215592.36","2026-04-23 10:30:55.739184+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081891590","Garage - KCE 699F","GT06E","aotomobile","KCE 699F","KCE 699F","Probox","automobile",NULL,NULL,NULL,NULL,"Garage",NULL,"746760215","89254021084186499519","639021088649951","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2019-06-16 11:11:24+00","2039-06-16 23:59:59+00",1,"1",NULL,"207814.05","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081891632","John Ondego - KCA 542Q","GT06E","aotomobile","KCA 542Q","KCA 542Q","Probox","automobile",NULL,NULL,NULL,NULL,"John Ondego",NULL,"746760038","89254021084186499485","639021088649948","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2019-06-15 09:17:53+00","2039-06-15 23:59:59+00",1,"1",NULL,"178914.47","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081891798","Garage - KCH 167M","GT06E","aotomobile","KCH 167M","KCH 167M","Probox","automobile",NULL,NULL,NULL,NULL,"Garage",NULL,"746760102","89254021084186499493","639021088649949","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2019-06-16 10:18:57+00","2039-06-16 23:59:59+00",1,"1",NULL,"168840.95","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081892101","Cornelius Kimutai - KCU 938R","GT06E","aotomobile","KCU 938R","KCU 938R","Van","automobile",NULL,NULL,NULL,NULL,"Cornelius Kimutai",NULL,"746759919","89254021084186499451","639021088649945","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2019-06-12 08:13:48+00","2039-06-12 23:59:59+00",1,"1",NULL,"149558.50","2026-04-23 10:29:21.507861+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081892309","Nicholas Erastus - KCQ 581M","GT06E","aotomobile","KCQ 581M","KCQ 581M","Probox","automobile",NULL,NULL,NULL,NULL,"Nicholas Erastus",NULL,"700023776","89254021084178504672","639021087850467","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2019-06-09 09:39:40+00","2039-06-09 23:59:59+00",1,"1",NULL,"209105.89","2026-04-23 10:40:40.169684+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081892440","KAZ 489Z","GT06E","aotomobile",NULL,NULL,NULL,"bus",NULL,NULL,NULL,NULL,NULL,NULL,"0700023806","89254021084178504698","639021087850469","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2019-06-09 10:04:39+00","2039-06-09 23:59:59+00",1,"1",NULL,"38197.20","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857081892762","Nicholas","GT06E","aotomobile",NULL,NULL,"Station Wagon","bus",NULL,NULL,"Toyota",NULL,NULL,NULL,"0746760503","89254021274233125361","639021273312536","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2019-06-16 08:31:46+00","2039-06-16 23:59:59+00",1,"1",NULL,"51048.97","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082037185","Amani Kazungu - KCY 084X","GT06E","aotomobile","KCY 084X","KCY 084X","Probox","automobile",NULL,NULL,NULL,NULL,"Amani Kazungu",NULL,"757338522","89254021154287000597","639021158700059","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-07-13 09:42:28+00","2040-07-13 23:59:59+00",1,"1",NULL,"172298.81","2026-04-23 10:51:08.665273+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082038977","Wilfred Kinyanjui - KCU 729C","GT06E","aotomobile","KCU 729C","KCU 729C","Crane","truck",NULL,NULL,NULL,NULL,"Wilfred Kinyanjui",NULL,"110094469","89254021164215938057","639021161593805","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-04-05 09:26:00+00","2040-04-05 23:59:59+00",1,"1",NULL,"172487.09","2026-04-23 10:24:33.914628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082040981","Amani Sulubu - KCY 090X","GT06E","aotomobile","KCY 090X","KCY 090X","Probox","automobile",NULL,NULL,NULL,NULL,"Amani Sulubu",NULL,"793375853","89254021064168004164","639021066800416","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-07-13 07:25:16+00","2040-07-13 23:59:59+00",1,"1",NULL,"166028.15","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082042052","Gabriel Musumba - KCE 690F","GT06E","aotomobile","KCE 690F","KCE 690F","Probox","automobile",NULL,NULL,NULL,NULL,"Gabriel Musumba",NULL,"110094466","89254021164215938024","639021161593802","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2020-04-03 17:30:13+00","2040-04-03 23:59:59+00",1,"1",NULL,"192693.23","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082042854","Elias Baya - KCZ 476E","GT06E","aotomobile","KCZ 476E","KCZ 476E","Probox","automobile",NULL,NULL,NULL,NULL,"Elias Baya",NULL,"110941187","89254021164224352993","639021162435299","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-08-09 05:06:42+00","2040-08-09 23:59:59+00",1,"1",NULL,"217595.68","2026-04-23 10:33:56.216621+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082042953","KCU 865Q Vanguard",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,"unknown",NULL,NULL,"2026-04-23 13:24:33.293453+00","2026-04-23 13:24:33.293453+00",NULL,NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082044280","Lawrence Kijogi - KCY 080X","GT06E","aotomobile","KCY 080X","KCY 080X","Probox","automobile",NULL,NULL,NULL,NULL,"Lawrence Kijogi",NULL,"708155933","89254029851005131222","639029850513122","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-07-13 11:05:02+00","2040-07-13 11:05:02+00",1,"1",NULL,"169740.37","2026-04-23 14:52:58.983571+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082046145","Joseph Kabandi - KCY 076X","GT06E","aotomobile","KCY 076X","KCY 076X","Probox","automobile",NULL,NULL,NULL,NULL,"Joseph Kabandi",NULL,"110850007","89254021164223447158","639021162344715","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-07-13 08:31:26+00","2040-07-13 23:59:59+00",1,"1",NULL,"122254.48","2026-04-23 10:47:40.895504+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082896911","Hamisi Pande - KDD 689Y","GT06E","aotomobile","KDD 689Y","KDD 689Y","Probox","automobile",NULL,NULL,NULL,NULL,"Hamisi Pande",NULL,"112714612","89254021214211314660","639021211131466","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-09-17 11:50:53+00","2041-09-17 23:59:59+00",1,"1",NULL,"163435.74","2026-04-23 10:26:09.922447+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082897091","Peter Mbugua - KDK 728K","GT06E","aotomobile","KDK 728K","KDK 728K","Probox","automobile",NULL,NULL,NULL,NULL,"Peter Mbugua",NULL,"790262984","89254021234222500396","639021232250039","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-12-14 11:31:57+00","2042-12-14 23:59:59+00",1,"1",NULL,"131109.26","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082897257","Cassius Wakiyo - KDB 323M","GT06E","aotomobile","KDB 323M","KDB 323M","Probox","automobile",NULL,NULL,NULL,NULL,"Cassius Wakiyo",NULL,"746428882","89254021234222500818","639021232250081","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-08-29 15:07:26+00","2041-08-29 15:07:26+00",1,"1",NULL,"121688.92","2026-04-23 10:28:26.388654+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082897737","John Makori - KDB 585E","GT06E","aotomobile","KDB 585E","KDB 585E","Probox","automobile",NULL,NULL,NULL,NULL,"John Makori",NULL,"114596734","89254021214211145262","639021211114526","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-08-29 14:29:28+00","2041-08-29 14:29:28+00",1,"1",NULL,"156765.03","2026-04-23 10:38:57.445964+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082897794","Mutuku Joseph - KDC 739F","GT06E","aotomobile","KDC 739F","KDC 739F","Probox","automobile",NULL,NULL,NULL,NULL,"Mutuku Joseph","115019037","115019037","89254021224222632356","639021222263235","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-04-10 14:55:32+00","2041-04-10 14:55:32+00",1,"1",NULL,"205169.79","2026-04-23 10:30:22.530563+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082898008","Samuel Ng'ang'a - KDE 264M","GT06E","aotomobile","KDE 264M","KDE 264M","Probox","automobile",NULL,NULL,NULL,NULL,"Samuel Ng'ang'a",NULL,"711731539","89254021264260342245","639021266034224","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-10-28 09:43:11+00","2041-10-28 23:59:59+00",1,"1",NULL,"126584.24","2026-04-23 11:35:59.816581+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082898016","Job Ngare - KDM 309S","GT06E","aotomobile","KDM 309S","KDM 309S","Probox","automobile",NULL,NULL,NULL,NULL,"Job Ngare",NULL,"706895756","89254021324273007563","639021327300756","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-08-15 13:45:26+00","2033-08-15 23:59:59+00",1,"1",NULL,"107726.56","2026-04-23 11:20:25.939244+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082898073","Mutuku Antony - KDK 732K","GT06E","aotomobile","KDK 732K","KDK 732K","Probox","automobile",NULL,NULL,NULL,NULL,"Mutuku Antony",NULL,"793026954","89254021234222387539","639021232238753","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-12-20 06:33:12+00","2042-12-20 23:59:59+00",1,"1",NULL,"82096.79","2026-04-23 14:52:07.094447+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082898487","Dan Watila - KDE 638J","GT06E","aotomobile","KDE 638J","KDE 638J","Probox","automobile",NULL,NULL,NULL,NULL,"Dan Watila",NULL,"116242996","89254021334258404214","639021335840421","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-10-21 15:50:06+00","2041-10-21 23:59:59+00",1,"1",NULL,"123872.36","2026-04-23 10:31:45.186653+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082900168","KDD 913G_Ruth Mazda",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,"unknown",NULL,NULL,"2026-04-23 15:09:48.575568+00","2026-04-23 15:09:48.575568+00",NULL,NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082900341","Simon Munda - KCZ 154S","GT06E","aotomobile","KCZ 154S","KCZ 154S","Probox","automobile",NULL,NULL,NULL,NULL,"Simon Munda",NULL,"757236135","89254021154296723312","639021159672331","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-09-23 17:12:51+00","2040-09-23 23:59:59+00",1,"1",NULL,"186504.10","2026-04-23 10:45:21.454595+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082900358","Geoffrey Too - KDM 308S","GT06E","aotomobile","KDM 308S","KDM 308S","Probox","automobile",NULL,NULL,NULL,NULL,"Geoffrey Too",NULL,"796527601","89254021264260126572","639021266012657","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-10-21 15:25:28+00","2041-10-21 23:59:59+00",1,"1",NULL,"142216.91","2026-04-23 12:35:06.661934+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082900697","George Ochieng' - KDD 684Y","GT06E","aotomobile","KDD 684Y","KDD 684Y","Probox","automobile",NULL,NULL,NULL,NULL,"George Ochieng'",NULL,"114879518","89254021214211314678","639021211131467","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-09-17 10:53:11+00","2041-09-17 23:59:59+00",1,"1",NULL,"152820.07","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082902461","Sadique Wakayula - KDC 490Q","GT06E","aotomobile","KDC 490Q","KDC 490Q","Crane","truck",NULL,NULL,NULL,NULL,"Sadique Wakayula",NULL,"757556468","89254021154296722488","639021159672248","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-05-22 11:27:30+00","2041-05-22 11:27:30+00",1,"1",NULL,"183009.52","2026-04-23 11:16:03.730519+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082902503","Felix Andole - KDC 207R","GT06E","aotomobile","KDC 207R","KDC 207R","Probox","automobile",NULL,NULL,NULL,NULL,"Felix Andole",NULL,"794820817","89254021224270993254","639021227099325","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-05-15 11:38:24+00","2041-05-15 11:38:24+00",1,"1",NULL,"208724.46","2026-04-23 15:32:46.935568+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082907973","Felix Muema - KCZ 223P","GT06E","aotomobile","KCZ 223P","KCZ 223P","Probox","automobile",NULL,NULL,NULL,NULL,"Felix Muema",NULL,"757843826","89254021154287138371","639021158713837","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-08-22 14:01:25+00","2040-08-22 23:59:59+00",1,"1",NULL,"222126.36","2026-04-23 10:26:48.220151+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082908500","Santoes Omondi - KCZ 181P","GT06E","aotomobile","KCZ 181P","KCZ 181P","Pick-Up","automobile",NULL,NULL,NULL,NULL,"Santoes Omondi",NULL,"701211974","89254021374215155087","639021371515508","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-08-23 08:58:55+00","2040-08-23 23:59:59+00",1,"1",NULL,"221339.62","2026-04-23 10:48:09.537346+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082910589","Patric Bett - KDA 609E","GT06E","aotomobile","KDA 609E","KDA 609E","Probox","automobile",NULL,NULL,NULL,NULL,"Patric Bett",NULL,"797622637","89254021154296722496","639021159672249","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2020-10-26 15:46:41+00","2040-10-26 23:59:59+00",1,"1",NULL,"194618.69","2026-04-23 10:34:25.350862+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082910886","Makanda Andrew - KCZ 155P","GT06E","aotomobile","KCZ 155P","KCZ 155P","Pick-Up","automobile",NULL,NULL,NULL,NULL,"Makanda Andrew",NULL,"745067338","89254021154287138397","639021158713839","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-08-23 11:52:35+00","2040-08-23 23:59:59+00",1,"1",NULL,"231065.89","2026-04-23 11:36:31.150282+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082911983","Brian Ngetich - KDA 717B","GT06E","aotomobile","KDA 717B","KDA 717B","Probox","automobile",NULL,NULL,NULL,NULL,"Brian Ngetich","795188807","795188807","89254021214211145288","639021211114528","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-08-29 07:21:43+00","2041-08-29 07:21:43+00",1,"1",NULL,"145404.96","2026-04-23 10:36:11.774166+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082912239","Dickson Jaoko - KDK 815R","GT06E","aotomobile","KDK 815R","KDK 815R","Probox","automobile",NULL,NULL,"Probox","0.00","Sammy",NULL,"706392117","89254021234296021287","639021239602128","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2023-06-21 07:14:51+00","2033-06-21 23:59:59+00",1,"1",NULL,"77008.75","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082912486","Moses Wambua - KCZ 751V","GT06E","aotomobile","KCZ 751V","KCZ 751V","Probox","automobile",NULL,NULL,NULL,NULL,"Moses Wambua","0792756503","792756503","89254021154296723437","639021159672343","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-09-23 10:14:28+00","2040-09-23 23:59:59+00",1,"1",NULL,"139762.20","2026-04-23 10:41:00.207177+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082916826","Denis Kazungu - KDM 794R","GT06E","aotomobile","KDM 794R","KDM 794R","Probox","automobile",NULL,NULL,NULL,NULL,"Denis Kazungu",NULL,"705700971","89254021324273006854","639021327300685","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2023-08-21 06:38:00+00","2033-08-21 23:59:59+00",1,"1",NULL,"79639.71","2026-04-23 20:18:46.496567+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082918012","Charles Nyambane - KCB 711C","GT06E","aotomobile","KCB 711C","KCB 711C","Probox","automobile",NULL,NULL,NULL,NULL,"Charles Nyambane",NULL,"793704231","89254021154287138363","639021158713836","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2020-09-21 10:48:35+00","2040-09-21 23:59:59+00",1,"1",NULL,"159597.27","2026-04-23 10:25:52.843474+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082918038","Mbuvi Kioko - KCC 199P","GT06E","aotomobile","KCC 199P","KCC 199P","Pick-Up","automobile",NULL,NULL,NULL,NULL,"Mbuvi Kioko",NULL,"797318126","89254021154287138389","639021158713838","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-08-22 15:26:27+00","2040-08-22 23:59:59+00",1,"1",NULL,"222106.80","2026-04-23 12:09:05.609075+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082918186","KDD 977T Fielder",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,"unknown",NULL,NULL,"2026-04-23 10:36:25.832836+00","2026-04-23 10:36:25.832836+00",NULL,NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"359857082925330","Noel Merengeni - KCY 838X","GT06E","aotomobile","KCY 838X","KCY 838X","Probox","automobile",NULL,NULL,NULL,NULL,"Noel Merengeni",NULL,"794873610","89254021154296723429","639021159672342","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2020-10-26 16:36:37+00","2040-10-26 23:59:59+00",1,"1",NULL,"194429.24","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050288212","Nicholas Erastus - KCQ 581M","JC400P","aotomobile","KCQ 581M","KCQ 581M","Probox","automobile",NULL,NULL,NULL,NULL,"Nicholas Erastus",NULL,"746979531",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-11-02 09:07:41+00","2041-11-02 23:59:59+00",1,"1",NULL,"40898.98","2026-04-23 13:05:18.326254+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050288261","Patric Bett - KDA 609E","JC400P","aotomobile","KDA 609E","KDA 609E","Probox","automobile",NULL,NULL,NULL,NULL,"Patric Bett","112693340","790176509",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2021-10-23 11:50:11+00","2041-10-23 23:59:59+00",1,"1",NULL,"18538.42","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050288303","Elias Baya - KCZ 476E","JC400P","aotomobile","KCZ 476E","KCZ 476E","Probox","automobile",NULL,NULL,NULL,NULL,"Elias Baya",NULL,"115870439",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-11-06 08:50:28+00","2041-11-06 23:59:59+00",1,"1",NULL,"116091.42","2026-04-23 17:46:09.993791+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050288345","Santoes Omondi - KCZ 181P","JC400P","aotomobile","KCZ 181P","KCZ 181P","Pick-Up","automobile",NULL,NULL,NULL,NULL,"Santoes Omondi",NULL,"768446105",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2021-11-06 10:17:51+00","2041-11-06 23:59:59+00",1,"1",NULL,"107462.79","2026-04-23 10:29:45.563231+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050288360","Brian Ngetich - KDA 717B","JC400P","aotomobile","KDA 717B","KDA 717B","Probox","automobile",NULL,NULL,NULL,NULL,"Brian Ngetich",NULL,"717867861",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2021-11-05 08:47:08+00","2041-11-05 23:59:59+00",1,"1",NULL,"17808.56","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050521521","John Kimeria - KDS 525D","JC400P","aotomobile","KDS 525D","KDS 525D","Crane","truck",NULL,NULL,NULL,NULL,"John Kimeria",NULL,"752958416",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-11-26 07:58:13+00","2033-11-26 23:59:59+00",1,"1",NULL,"19354.92","2026-04-23 10:28:34.917147+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050521612","Denis Kazungu - KDM 794R","JC400P","aotomobile","KDM 794R","KDM 794R","Probox","automobile",NULL,NULL,NULL,NULL,"Denis Kazungu",NULL,"704113731",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-22 07:52:12+00","2042-01-22 23:59:59+00",1,"1",NULL,"4350.75","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050521752","Simon Munda - KCZ 154S","JC400P","aotomobile","KCZ 154S","KCZ 154S","Probox","automobile",NULL,NULL,NULL,NULL,"Simon Munda",NULL,"113805921",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-16 08:14:37+00","2042-01-16 23:59:59+00",1,"1",NULL,"4698.02","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050522065","Gideon Kiprono - KCQ 215F","JC400P","aotomobile","KCQ 215F","KCQ 215F","Probox","automobile",NULL,NULL,NULL,NULL,"Gideon Kiprono",NULL,"113343715",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2022-01-16 07:10:16+00","2042-01-16 23:59:59+00",1,"1",NULL,"8111.98","2026-04-23 18:23:51.445608+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050522107","Cassius Wakiyo - KDB 323M","JC400P","aotomobile","KDB 323M","KDB 323M","Probox","automobile",NULL,NULL,NULL,NULL,"Cassius Wakiyo",NULL,"114149576",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-22 08:18:15+00","2042-01-22 23:59:59+00",1,"1",NULL,"23316.09","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050522719","Mbuvi Kioko - KCZ 199P","JC400P","aotomobile","KCZ 199P","KCZ 199P","Pick-Up","automobile",NULL,NULL,NULL,NULL,"Mbuvi Kioko",NULL,"768218655",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-16 22:07:24+00","2042-01-16 23:59:59+00",1,"1",NULL,"16973.89","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050522743","Charles Nyambane - KCB 711C","JC400P","aotomobile","KCB 711C","KCB 711C","Probox","automobile",NULL,NULL,NULL,NULL,"Charles Nyambane",NULL,"768657106",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2023-12-22 21:53:57+00","2033-12-22 23:59:59+00",1,"1",NULL,"12133.75","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050522859","Garage - KCH 167M","JC400P","aotomobile","KCH 167M","KCH 167M","Probox","automobile",NULL,NULL,NULL,NULL,"Garage",NULL,"706740252",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-15 08:23:21+00","2042-01-15 23:59:59+00",1,"1",NULL,"6934.86","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050522883","Dan Watila - KDE 638J","JC400P","aotomobile","KDE 638J","KDE 638J","Probox","automobile",NULL,NULL,NULL,NULL,"Dan Watila",NULL,"112615393",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-15 07:17:21+00","2042-01-15 23:59:59+00",1,"1",NULL,"14483.01","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050522891","Lawrence Kijogi - KCY 080X","JC400P","aotomobile","KCY 080X","KCY 080X","Pick-Up","automobile",NULL,NULL,NULL,NULL,"Lawrence Kijogi",NULL,"113287191",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-16 10:51:30+00","2042-01-16 23:59:59+00",1,"1",NULL,"11585.33","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523014","Samuel Muriithy - KDR 594N","JC400P","aotomobile","KDR 594N","KDR 594N","Probox","automobile",NULL,NULL,NULL,NULL,"Samuel Muriithy",NULL,"790175423",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-12-21 15:54:32+00","2033-12-21 23:59:59+00",1,"1",NULL,"27275.43","2026-04-23 10:26:17.747928+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523063","Kelvin Wambugu - KDR 594N","JC400P","aotomobile","KDR 594N","KDR 594N","Probox","automobile",NULL,NULL,NULL,NULL,"Kelvin Wambugu",NULL,"701211876",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-12-22 19:24:51+00","2043-12-22 19:24:51+00",1,"1",NULL,"32698.94","2026-04-23 15:31:08.065856+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523139","Mike Wanaswa - KDT 724R","JC400P","aotomobile","KDT 724R","KDT 724R","Probox","automobile",NULL,NULL,NULL,NULL,"Mike Wanaswa",NULL,"790175045",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-12-22 22:28:09+00","2043-12-22 22:28:09+00",1,"1",NULL,"29559.82","2026-04-23 11:16:37.277518+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523204","Amani Kazungu - KCY 084X","JC400P","aotomobile","KCY 084X","KCY 084X","Probox","automobile",NULL,NULL,NULL,NULL,"Amani Kazungu",NULL,"707892547",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-16 06:18:20+00","2042-01-16 23:59:59+00",1,"1",NULL,"66955.70","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523295","Emmanuel Luseno - KDS 453 Y","JC400P","aotomobile","KDS 453 Y","KDS 453 Y","Pick-Up","automobile",NULL,NULL,NULL,NULL,"Emmanuel Luseno",NULL,"700242474",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-12-22 14:39:50+00","2033-12-22 23:59:59+00",1,"1",NULL,"37098.35","2026-04-23 11:29:48.369147+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523337","Victor Kimutai - KDS 919Y","JC400P","aotomobile","KDS 919Y","KDS 919Y","Probox","automobile",NULL,NULL,NULL,NULL,"Victor Kimutai",NULL,"700242527",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-12-22 18:00:49+00","2043-12-22 18:00:49+00",1,"1",NULL,"50756.64","2026-04-23 10:27:13.522675+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523386","George Ochieng' - KDD 684Y","JC400P","aotomobile","KDD 684Y","KDD 684Y","Probox","automobile",NULL,NULL,NULL,NULL,"George Ochieng'",NULL,"785586834",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-22 06:36:08+00","2042-01-22 23:59:59+00",1,"1",NULL,"33979.83","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523527","Allan Owana - KDK 780K","JC400P","aotomobile","KDK 780K","KDK 780K","Probox","automobile",NULL,NULL,NULL,NULL,"Allan Owana",NULL,"792375024",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2022-12-03 10:43:41+00","2042-12-03 23:59:59+00",1,"1",NULL,"109564.95","2026-04-23 10:25:24.360765+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523618","Geoffrey Too - KDM 308S","JC400P","aotomobile","KDM 308S","KDM 308S","Probox","automobile",NULL,NULL,NULL,NULL,"Geoffrey Too",NULL,"701211625",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2023-08-15 15:42:32+00","2033-08-15 23:59:59+00",1,"1",NULL,"26496.50","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523626","Major Simiyu - KDS 949Y","JC400P","aotomobile","KDS 949Y","KDS 949Y","Probox","automobile",NULL,NULL,NULL,NULL,"Major Simiyu",NULL,"701211892",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-12-22 18:05:16+00","2033-12-22 23:59:59+00",1,"1",NULL,"37042.97","2026-04-23 10:51:18.245194+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523816","Job Ngare - KDM 309S","JC400P","aotomobile","KDM 309S","KDM 309S","Probox","automobile",NULL,NULL,NULL,NULL,"Job Ngare",NULL,"707936781",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2023-08-15 14:05:52+00","2033-08-15 23:59:59+00",1,"1",NULL,"54320.21","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050523949","Joseph Kabandi - KCY 076X","JC400P","aotomobile","KCY 076X","KCY 076X","Probox","automobile",NULL,NULL,NULL,NULL,"Joseph Kabandi",NULL,"113288492",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-16 05:52:54+00","2042-01-16 23:59:59+00",1,"1",NULL,"14427.50","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524012","Moses Wambua - KCZ 751V","JC400P","aotomobile","KCZ 751V","KCZ 751V","Probox","automobile",NULL,NULL,NULL,NULL,"Moses Wambua",NULL,"113313797",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-16 07:40:10+00","2042-01-16 23:59:59+00",1,"1",NULL,"26551.46","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524087","Felix Muema - KCZ 223P","JC400P","aotomobile","KCZ 223P","KCZ 223P","Pick-Up","automobile",NULL,NULL,NULL,NULL,"Felix Muema",NULL,"113973875",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-16 13:02:24+00","2042-01-16 23:59:59+00",1,"1",NULL,"11543.26","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524368","862798050524368","JC400P","aotomobile",NULL,NULL,NULL,"automobile",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-10-29 09:24:53+00","2042-10-29 23:59:59+00",1,"1",NULL,"169208.91","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524384","Hamisi Pande - KDD 689Y","JC400P","aotomobile","KDD 689Y","KDD 689Y","Probox","automobile",NULL,NULL,NULL,"0.00","Hamisi Pande",NULL,"701211744",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-22 05:49:19+00","2042-01-22 23:59:59+00",1,"1",NULL,"13685.18","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524392","Ndegwa Dancun - KCG 669W","JC400P","aotomobile","KCG 669W","KCG 669W","Probox","automobile",NULL,NULL,NULL,NULL,"Ndegwa Dancun",NULL,"113799173",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-16 09:43:10+00","2042-01-16 23:59:59+00",1,"1",NULL,"13638.25","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524426","Amani Sulubu - KCY 090X","JC400P","aotomobile","KCY 090X","KCY 090X","Probox","automobile",NULL,NULL,NULL,NULL,"Amani Sulubu",NULL,"113823350",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2022-01-16 08:56:25+00","2042-01-16 23:59:59+00",1,"1",NULL,"14243.83","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524533","Leonard Nzai - KDM 306S","JC400P","aotomobile","KDM 306S","KDM 306S","Probox","automobile",NULL,NULL,NULL,NULL,"Leonard Nzai",NULL,"703487162",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-08-21 08:22:12+00","2033-08-21 23:59:59+00",1,"1",NULL,"68942.41","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524558","Mutuku Joseph - KDC 739F","JC400P","aotomobile","KDC 739F","KDC 739F","Probox","automobile",NULL,NULL,NULL,NULL,"Mutuku Joseph",NULL,"100858817",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-22 10:38:25+00","2042-01-22 23:59:59+00",1,"1",NULL,"23711.63","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524566","Makanda Andrew - KCZ 155P","JC400P","aotomobile","KCZ 155P","KCZ 155P","Pick-Up","automobile",NULL,NULL,NULL,NULL,"Makanda Andrew",NULL,"758781444",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-22 09:47:33+00","2042-01-22 23:59:59+00",1,"1",NULL,"31663.30","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524608","Peter Mbugua - KDK 728K","JC400P","aotomobile","KDK 728K","KDK 728K","Probox","automobile",NULL,NULL,NULL,NULL,"Peter Mbugua",NULL,"706742413",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-12-03 12:11:32+00","2042-12-03 23:59:59+00",1,"1",NULL,"7219.31","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524657","Felix Andole - KDC 207R","JC400P","aotomobile","KDC 207R","KDC 207R","Probox","automobile",NULL,NULL,NULL,NULL,"Felix Andole",NULL,"758689195",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-22 07:17:47+00","2042-01-22 23:59:59+00",1,"1",NULL,"46233.99","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524681","Mutuku Antony - KDK 732K","JC400P","aotomobile","KDK 732K","KDK 732K","Probox","automobile",NULL,NULL,NULL,NULL,"Mutuku Antony",NULL,"796275746",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-12-06 13:37:49+00","2042-12-06 23:59:59+00",1,"1",NULL,"14993.36","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524707","Garage - KCE 699F","JC400P","aotomobile","KCE 699F","KCE 699F","Probox","automobile",NULL,NULL,NULL,NULL,"Garage",NULL,"110525751",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-15 07:58:49+00","2042-01-15 23:59:59+00",1,"1",NULL,"34715.97","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050524897","Cornelius Kimutai - KCU 938R","JC400P","aotomobile","KCU 938R","KCU 938R","Van","automobile",NULL,NULL,NULL,NULL,"Cornelius Kimutai",NULL,"114924404",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-22 09:03:40+00","2042-01-22 23:59:59+00",1,"1",NULL,"12668.43","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525068","Samuel Ng'ang'a - KDE 264M","JC400P","aotomobile","KDE 264M","KDE 264M","Probox","automobile",NULL,NULL,NULL,NULL,"Samuel Ng'ang'a",NULL,"768658564",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2023-12-22 13:33:42+00","2033-12-22 23:59:59+00",1,"1",NULL,"12299.13","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525225","Sadique Wakayula - KDC 490Q","JC400P","aotomobile","KDC 490Q","KDC 490Q","Crane","truck",NULL,NULL,NULL,NULL,"Sadique Wakayula",NULL,"768652386",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2023-12-22 20:52:08+00","2043-12-22 20:52:08+00",1,"1",NULL,"19138.05","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525266","Dickson Jaoko - KDK 815R","JC400P","aotomobile","KDK 815R","KDK 815R","Probox","automobile",NULL,NULL,NULL,NULL,"Dickson Jaoko",NULL,"706665867",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-06-21 07:50:00+00","2033-06-21 23:59:59+00",1,"1",NULL,"63754.71","2026-04-23 13:50:24.21992+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525423","Makori John - KDB 585E","JC400P","aotomobile","KDB 585E","KDB 585E","Probox","automobile",NULL,NULL,NULL,NULL,"Makori John",NULL,"701211724",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-15 10:59:19+00","2042-01-15 23:59:59+00",1,"1",NULL,"48804.83","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525589","Simon Kamau - KCE 090R","JC400P","aotomobile","KCE 090R","KCE 090R","Probox","automobile",NULL,NULL,NULL,NULL,"Simon Kamau",NULL,"796276387",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-19 10:10:04+00","2042-01-19 23:59:59+00",1,"1",NULL,"15874.39","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525605","John Ondego - KCA 542Q","JC400P","aotomobile","KCA 542Q","KCA 542Q","Probox","automobile",NULL,NULL,NULL,NULL,"John Ondego",NULL,"110526783",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-15 05:56:11+00","2042-01-15 23:59:59+00",1,"1",NULL,"23976.94","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525613","Kennedy Chege - KCQ 618K","JC400P","aotomobile","KCQ 618K","KCQ 618K","Probox","automobile",NULL,NULL,NULL,NULL,"Kennedy Chege",NULL,"729994247",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-16 05:21:05+00","2042-01-16 23:59:59+00",1,"1",NULL,"12804.24","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525670","Gabriel Musumba - KCE 690F","JC400P","aotomobile","KCE 690F","KCE 690F","Probox","automobile",NULL,NULL,NULL,NULL,"Gabriel Musumba",NULL,"701211996",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2022-01-15 06:40:01+00","2042-01-15 23:59:59+00",1,"1",NULL,"20110.93","2026-04-24 05:34:23.167312+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525753","Noel Merengeni - KCY 838X","JC400P","aotomobile","KCY 838X","KCY 838X","Probox","automobile",NULL,NULL,NULL,NULL,"Noel Merengeni",NULL,NULL,NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2022-01-15 05:24:00+00","2042-01-15 23:59:59+00",1,"1",NULL,"14596.59","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525837","Kennedy Ondieki - KCU 237Z","JC400P","aotomobile","KCU 237Z","KCU 237Z","Probox","automobile",NULL,NULL,NULL,NULL,"Kennedy Ondieki",NULL,"113669852",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2023-12-21 19:32:44+00","2033-12-21 23:59:59+00",1,"1",NULL,NULL,"2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050525951","Wright Oseko - KCG 668W","JC400P","aotomobile","KCG 668W","KCG 668W","Probox","automobile",NULL,NULL,NULL,NULL,"Wright Oseko",NULL,"741943212",NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2022-01-15 09:36:45+00","2042-01-15 23:59:59+00",1,"1",NULL,"13116.00","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050526165","Wilfred Kinyanjui - KCU 729C","JC400P","aotomobile","KCU 729C","KCU 729C","Crane","truck",NULL,NULL,NULL,NULL,"Wilfred Kinyanjui",NULL,"790564929",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2023-11-26 10:17:19+00","2033-11-26 23:59:59+00",1,"1",NULL,"24270.20","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050526231","Rashid Hassan - KDM 840V","JC400P","aotomobile","KDM 840V","KDM 840V","Probox","automobile",NULL,NULL,NULL,NULL,"Rashid Hassan",NULL,"790175526",NULL,NULL,"Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2023-12-22 22:36:15+00","2043-12-22 22:36:15+00",1,"1",NULL,"45418.38","2026-04-23 10:29:41.575467+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798050526256","Ian Dancun - KDT 923R","JC400P","aotomobile","KDT 923R","KDT 923R","Probox","automobile",NULL,NULL,NULL,NULL,"Ian Dancun",NULL,"794873610",NULL,NULL,"Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2023-12-22 19:37:24+00","2043-12-22 19:37:24+00",1,"1",NULL,"11093.11","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052707888","Benjamin Ananda - KDV 438W","JC400P","aotomobile","KDV 438W","KDV 438W","Probox","automobile",NULL,NULL,NULL,NULL,"Benjamin Ananda",NULL,"758047312","89254021414206816980","639021410681698","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-12-15 07:39:23+00","2035-12-15 23:59:59+00",1,"1",NULL,"8720.87","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052707896","John Mbugua - KDW 573B","JC400P","aotomobile","KDW 573B","KDW 573B","Probox","automobile",NULL,NULL,NULL,NULL,"John Mbugua",NULL,NULL,"89254021414206816725","639021410681672","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2026-01-30 14:48:17+00","2036-01-30 23:59:59+00",1,"1",NULL,"515.16","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052707946","Tom Wekesa/OSP-KCY 930Y_CAM","JC400P","aotomobile",NULL,NULL,NULL,"automobile",NULL,NULL,NULL,NULL,NULL,NULL,"0758047806","89254021414206816766","639021410681676","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2026-01-20 21:02:13+00","2036-01-20 23:59:59+00",1,"1",NULL,"10079.17","2026-04-23 10:25:24.363965+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052708035","862798052708035","JC400P","aotomobile",NULL,NULL,"Probox","automobile",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group",NULL,NULL,1,"1",NULL,NULL,"2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052708068","Dominic Wambua - KDV 683Z","JC400P","aotomobile","KDV 683Z","KDV 683Z","Probox","automobile",NULL,NULL,NULL,NULL,"Dominic Wambua",NULL,"758048043","89254021414206816964","639021410681696","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2026-01-24 09:20:09+00","2036-01-24 23:59:59+00",1,"1",NULL,"4438.55","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052708076","Albert Mutwiri - KDV 437W","JC400P","aotomobile",NULL,"KDV 437W","Probox","automobile",NULL,NULL,NULL,NULL,"Albert Mutwiri",NULL,"758047094","89254021414206816782","639021410681678","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-12-13 15:03:30+00","2035-12-13 23:59:59+00",1,"1",NULL,"5575.64","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052708167","Levine Wasike - KDV 439W","JC400P","aotomobile","KDV 439W","KDV 439W","Probox","automobile",NULL,NULL,NULL,NULL,"Levine Wasike",NULL,"758046738","89254021414206816741","639021410681674","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-12-13 19:49:29+00","2035-12-13 23:59:59+00",1,"1",NULL,"4601.08","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052708282","Godffrey Nandwa - KCN 496A","JC400P","aotomobile","KCN 496A","KCN 496A","Probox","automobile",NULL,NULL,NULL,NULL,"Godffrey Nandwa",NULL,"758047934","89254021414206816865","639021410681686","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2026-01-25 18:55:54+00","2036-01-25 23:59:59+00",1,"1",NULL,"7040.60","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052713654","Garage/ISP_KCL 502T_CAM","JC400P","aotomobile",NULL,NULL,NULL,"automobile",NULL,NULL,NULL,NULL,NULL,NULL,"0780215879","89254035061001753803","639035060175380","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-09-02 10:09:57+00","2035-09-02 23:59:59+00",1,"1",NULL,"5199.72","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052713696","862798052713696","JC400P","aotomobile",NULL,NULL,"Probox","automobile",NULL,NULL,NULL,NULL,NULL,NULL,NULL,"89254021394215205906","639021391520590","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-09-02 10:20:58+00","2035-09-02 23:59:59+00",1,"1",NULL,"6214.49","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052713761","Management_Mazda - KDU 613A","JC400P","aotomobile","KDU 613A","KDU 613A","Mazda","automobile",NULL,NULL,NULL,NULL,"Management_Mazda",NULL,"790176786","89254021394215205955","639021391520595","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-07-09 15:49:26+00","2035-07-09 23:59:59+00",1,"1",NULL,"9262.78","2026-04-23 16:40:48.879666+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052713779","Benard Kimutai - KDN 759G","JC400P","aotomobile","KDN 759G","KDN 759G","Probox","automobile",NULL,NULL,NULL,NULL,"Benard Kimutai",NULL,"752143258","89254035061001753860","639035060175386","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-08-23 11:15:59+00","2035-08-23 23:59:59+00",1,"1",NULL,"5344.24","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052713811","James Onyango - KDU 613B","JC400P","aotomobile","KDU 613B","KDU 613B","Probox","automobile",NULL,NULL,NULL,NULL,"James Onyango",NULL,"790176542","89254021394215205880","639021391520588","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-07-09 19:24:14+00","2035-07-09 23:59:59+00",1,"1",NULL,"9657.42","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052713837","Kennedy Ondieki - KCU 237Z","JC400P","aotomobile","KCU 237Z","KCU 237Z","Probox","automobile",NULL,NULL,NULL,NULL,"Kennedy Ondieki",NULL,"113669852","89254021414206327855","639021410632785","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-10-08 14:55:23+00","2035-10-08 23:59:59+00",1,"1",NULL,"9346.02","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052713985","Timothy Gitau - KDT 916R","JC400P","aotomobile","KDT 916R","KDT 916R","Probox","automobile",NULL,NULL,NULL,NULL,"Timothy Gitau",NULL,"768696668","89254021394274518892","639021397451889","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-08-02 18:21:23+00","2035-08-02 23:59:59+00",1,"1",NULL,"19998.22","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052714066","862798052714066","JC400P","aotomobile",NULL,NULL,"Probox","automobile",NULL,NULL,NULL,NULL,NULL,NULL,NULL,"89254021414206378684","639021410637868","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-11-21 17:44:44+00","2035-11-21 23:59:59+00",1,"1",NULL,"10755.28","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"862798052715220","Rofas Njagi - KDT 728R","JC400P","aotomobile","KDT 728R","KDT 728R","Probox","automobile",NULL,NULL,NULL,NULL,"Rofas Njagi",NULL,"704573658","89254021334258495873","639021335849587","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-07-16 07:09:25+00","2035-07-16 23:59:59+00",1,"1",NULL,"16385.58","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061035133","Major Simiyu - KDS 949Y","X3","aotomobile","KDS 949Y","KDS 949Y","Probox","automobile",NULL,NULL,NULL,NULL,"Major Simiyu",NULL,"768696642","89254021394274518918","639021397451891","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2025-08-02 13:14:33+00","2035-08-02 23:59:59+00",1,"1",NULL,"25089.98","2026-04-23 12:07:56.044395+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061035653","Richardson Komu - KDT 923R","X3","aotomobile","KDT 923R","KDT 923R","Probox","automobile",NULL,NULL,NULL,NULL,"Richardson Komu",NULL,"768697292","89254021394274518942","639021397451894","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-08-02 08:11:46+00","2035-08-02 23:59:59+00",1,"1",NULL,"23556.65","2026-04-23 10:24:50.340401+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061035778","John Kimeria - KDS 525D","X3","aotomobile","KDS 525D","KDS 525D","Crane","truck",NULL,NULL,NULL,NULL,"John Kimeria",NULL,"790176738","89254021394215205922","639021391520592","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-07-11 05:50:36+00","2035-07-11 23:59:59+00",1,"1",NULL,"17653.96","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061036164","Brian Njenga - KMFF 113Z","X3","aotomobile","KMFF 113Z","KMFF 113Z","Motorbike","mtc",NULL,NULL,NULL,NULL,"Brian Njenga",NULL,"768696705","89254021394274518850","639021397451885","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-07-31 10:06:46+00","2035-07-31 23:59:59+00",1,"1",NULL,"22990.33","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061037980","Emmanuel Luseno - KDS 453Y","X3","aotomobile","KDS 453Y","KDS 453Y","Pick-Up","automobile",NULL,NULL,NULL,NULL,"Emmanuel Luseno",NULL,"790176734","89254021394215205856","639021391520585","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-07-15 06:30:34+00","2035-07-15 23:59:59+00",1,"1",NULL,"42609.03","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061042261","Kelvin Wambugu - KDR 592N","X3","aotomobile","KDR 592N","KDR 592N","Probox","automobile",NULL,NULL,NULL,NULL,"Kelvin Wambugu",NULL,"797680464","89254021334258159693","639021335815969","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-07-10 10:23:44+00","2035-07-10 23:59:59+00",1,"1",NULL,"18755.66","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061043079","Mike Wanaswa - KDT 724R","X3","aotomobile","KDT 724R","KDT 724R","Probox","automobile",NULL,NULL,NULL,NULL,"Mike Wanaswa",NULL,"768696664","89254021394274518959","639021397451895","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2025-08-02 12:16:11+00","2035-08-02 23:59:59+00",1,"1",NULL,"27470.11","2026-04-23 11:16:35.682194+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061043426","Geoffrey Karanja - KMGS 239H","X3","aotomobile","KMGS 239H","KMGS 239H","Motorbike","mtc",NULL,NULL,NULL,NULL,"Geoffrey Karanja",NULL,"768696658","89254021394274518926","639021397451892","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-08-22 13:32:25+00","2035-08-22 23:59:59+00",1,"1",NULL,"21267.01","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061047435","Management_Mazda - KDU 613A","X3","aotomobile","KDU 613A","KDU 613A","Mazda","automobile",NULL,NULL,NULL,NULL,"Management_Mazda",NULL,"790175971","89254021394215205971","639021391520597","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-07-09 08:02:26+00","2035-07-09 23:59:59+00",1,"1",NULL,"9761.38","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061048276","Victor Kimutai - KDS 919Y","X3","aotomobile","KDS 919Y","KDS 919Y","Probox","automobile",NULL,NULL,NULL,NULL,"Victor Kimutai",NULL,"768696755","89254021394274518900","639021397451890","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2025-08-02 07:38:01+00","2035-08-02 23:59:59+00",1,"1",NULL,"23296.79","2026-04-23 10:54:41.63532+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061048300","KMGR 409U HENRY JAZZ",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,"unknown",NULL,NULL,"2026-04-24 04:30:20.231102+00","2026-04-24 04:30:20.231102+00",NULL,NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061048466","Samuel Muriithy - KDR 594N","X3","aotomobile","KDR 594N","KDR 594N","Probox","automobile",NULL,NULL,NULL,NULL,"Samuel Muriithy",NULL,"797680395","89254021334258159628","639021335815962","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-07-24 09:37:31+00","2035-07-24 23:59:59+00",1,"1",NULL,"27634.10","2026-04-23 11:43:39.178819+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061048615","Office-KMDG 902Z","X3","aotomobile",NULL,NULL,NULL,"automobile",NULL,NULL,NULL,NULL,NULL,NULL,"0768697276","89254021394274518876","639021397451887","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-07-31 09:59:43+00","2035-07-31 23:59:59+00",1,"1",NULL,"5721.21","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061048953","Timothy Gitau - KDT 916R","X3","aotomobile","KDT 916R","KDT 916R","Probox","automobile",NULL,NULL,NULL,NULL,"Timothy Gitau",NULL,"768697056","89254021394274518967","639021397451896","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2025-08-02 08:48:05+00","2035-08-02 23:59:59+00",1,"1",NULL,"28536.23","2026-04-23 10:53:31.102315+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061049001","Parked - KMGK 596V","X3","aotomobile","KMGK 596V","KMGK 596V","Motorbike","mtc",NULL,NULL,NULL,NULL,"Parked",NULL,"768697064","89254021394274518884","639021397451888","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-07-31 08:40:18+00","2035-07-31 23:59:59+00",1,"1",NULL,"20612.89","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061053714","Samuel Kihara - KMEL 225X","X3","aotomobile","KMEL 225X","KMEL 225X","Motorbike","mtc",NULL,NULL,NULL,NULL,"Samuel Kihara",NULL,"768696832","89254021394274518934","639021397451893","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-08-02 13:51:47+00","2035-08-02 23:59:59+00",1,"1",NULL,"26897.18","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061053748","Rashid Hassan - KDM 840V","X3","aotomobile","KDM 840V","KDM 840V","Probox","automobile",NULL,NULL,NULL,NULL,"Rashid Hassan",NULL,"768445963","89254021334212352574","639021331235257","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-07-10 13:54:11+00","2035-07-10 23:59:59+00",1,"1",NULL,"26612.42","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061054548","James Onyango - KDU 613B","X3","aotomobile","KDU 613B","KDU 613B","Probox","automobile",NULL,NULL,NULL,NULL,"James Onyango",NULL,"790175997","89254021394215205948","639021391520594","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-07-09 07:11:15+00","2035-07-09 23:59:59+00",1,"1",NULL,"13446.05","2026-04-23 10:26:24.667167+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061054555","Rofas Njagi - KDT 728R","X3","aotomobile","KDT 728R","KDT 728R","Probox","automobile",NULL,NULL,NULL,NULL,"Rofas Njagi",NULL,"790176726","89254021394215205823","639021391520582","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-07-16 06:44:33+00","2035-07-16 23:59:59+00",1,"1",NULL,"27250.80","2026-04-23 10:25:21.085437+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061559538","FRED KMGW 538W HULETI",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,"unknown",NULL,NULL,"2026-04-23 10:42:18.5831+00","2026-04-23 10:42:18.5831+00",NULL,NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061562722","John Mbugua - KDW 573B","X3","aotomobile","KDW 573B","KDW 573B","Probox","automobile",NULL,NULL,NULL,NULL,"John Mbugua",NULL,"758052508","89254021414206816832","639021410681683","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2026-01-30 06:53:57+00","2036-01-30 23:59:59+00",1,"1",NULL,"4488.19","2026-04-23 10:25:38.887433+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061562847","Levine Wasike - KDV 439W","X3","aotomobile","KDV 439W","KDV 439W","Probox","automobile",NULL,NULL,NULL,NULL,"Levine Wasike",NULL,"758047032","89254021414206816840","639021410681684","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2025-12-13 11:14:14+00","2035-12-13 23:59:59+00",1,"1",NULL,"7880.92","2026-04-23 10:35:50.779597+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061563282","X3-63282","X3","aotomobile",NULL,NULL,NULL,"automobile",NULL,NULL,NULL,NULL,NULL,NULL,NULL,"8925610001837573427F","641101970467668","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2026-02-14 07:20:10+00","2036-02-14 23:59:59+00",1,"1",NULL,"4758.32","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061563415","Barack Orwa - KDW 781E","X3","aotomobile","KDW 781E","KDW 781E","Vazel","automobile",NULL,NULL,NULL,NULL,"Barack Orwa",NULL,"758052541","89254021414206816931","639021410681693","Fireside_MSA","Fireside Group MSA","9d0927d235e44fe7abf254902fc68921","Default group","2026-01-13 12:37:42+00","2036-01-13 23:59:59+00",1,"1",NULL,"4165.95","2026-04-23 11:22:00.676215+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061563423","Joel Ntumba - UMA 826AB","X3","aotomobile","UMA 826AB","UMA 826AB","Motorbike","mtc",NULL,NULL,NULL,NULL,"Joel Ntumba",NULL,"119051036","89254021414206652690","639021410665269","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2026-01-28 13:55:39+00","2036-01-28 23:59:59+00",1,"1",NULL,"1174.05","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061563597","Dominic Wambua - KDV 683Z","X3","aotomobile","KDV 683Z","KDV 683Z","Probox","automobile",NULL,NULL,NULL,NULL,"Dominic Wambua",NULL,"758052405","89254021414206816733","639021410681673","Fireside@HQ","Fireside Telematics","6ef0b0fc2d964b358b70dc2cfcbc5b7e","Default group","2026-01-30 06:55:35+00","2036-01-30 23:59:59+00",1,"1",NULL,"6790.53","2026-04-23 10:25:40.125927+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061563639","Benjamin Ananda - KDV 438W","X3","aotomobile","KDV 438W","KDV 438W","Probox","automobile",NULL,NULL,NULL,NULL,"Benjamin Ananda",NULL,"758047065","89254021414206816683","639021410681668","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-12-13 16:02:37+00","2035-12-13 23:59:59+00",1,"1",NULL,"14446.33","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061564280","Rodin Kiberu - UMA 011EK","X3","aotomobile","UMA 011EK","UMA 011EK","Motorbike","mtc",NULL,NULL,NULL,NULL,"Rodin Kiberu",NULL,"118081642","89254021414206817244","639021410681724","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2026-01-28 13:13:57+00","2036-01-28 23:59:59+00",1,"1",NULL,"841.39","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061564470","Silvanus Kipkorir - KDV 064S","X3","aotomobile","KDV 064S","KDV 064S","Probox","automobile",NULL,NULL,NULL,NULL,"Silvanus Kipkorir",NULL,"113669866","89254021414206378718","639021410637871","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-11-21 16:49:17+00","2035-11-21 23:59:59+00",1,"1",NULL,"23869.16","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061568968","X3-68968","X3","aotomobile",NULL,NULL,NULL,"automobile",NULL,NULL,NULL,NULL,NULL,NULL,NULL,"89254021414206816915","639021410681691","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2026-03-11 06:19:14+00","2036-03-11 23:59:59+00",1,"1",NULL,"16.23","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061569123","Albert Mutwiri - KDV 437W","X3","aotomobile","KDV 437W","KDV 437W","Probox","automobile",NULL,NULL,NULL,NULL,"Albert Mutwiri",NULL,"758047101","89254021414206816881","639021410681688","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-12-13 14:26:17+00","2035-12-13 23:59:59+00",1,"1",NULL,"13032.60","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061569131","UMA 418EK","X3","aotomobile","UMA 418EK","UMA 418EK",NULL,"automobile",NULL,NULL,NULL,NULL,"UG",NULL,"256792997053","8925610001837573385F","641101970467664","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2026-02-26 08:15:44+00","2036-02-26 23:59:59+00",1,"1",NULL,"2333.45","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061569479","UMA 382EK","X3","aotomobile","UMA 382EK","UMA 382EK",NULL,"automobile",NULL,NULL,NULL,NULL,"UG",NULL,"256792997079","8925610001837573419F","641101970467667","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2026-02-26 08:21:10+00","2036-02-26 23:59:59+00",1,"1",NULL,"1954.86","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061578553","X3-78553",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,"unknown",NULL,NULL,"2026-04-23 15:30:19.981271+00","2026-04-23 15:30:19.981271+00",NULL,NULL,NULL,NULL,NULL,NULL,NULL
|
||||
"865135061581904","Robert Kipruto - KDV 072L","X3","aotomobile","KDV 072L","KDV 072L","Probox","automobile",NULL,NULL,NULL,NULL,"Robert Kipruto",NULL,"114149576","89254021264261503993","639021266150399","fireside","Fireside Group HQ","2f1acaef6c884214b4598719180fe68d","Default group","2025-11-21 15:30:29+00","2035-11-21 23:59:59+00",1,"1",NULL,"15252.84","2026-04-23 10:23:56.546784+00","2026-04-24 07:43:45.210628+00","2026-04-24 07:43:45.210628+00",NULL,NULL,NULL,NULL,NULL,NULL
|
||||
|
69
dwh/261001_dwh_control.sql
Normal file
69
dwh/261001_dwh_control.sql
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
-- =============================================================
|
||||
-- TRACKSOLID DWH CONTROL SCHEMA
|
||||
-- Target Database: tracksolid_dwh
|
||||
-- Purpose: Watermarks + per-run audit log for the n8n DWH pipeline
|
||||
-- Applies after: 260423_dwh_ddl_v1.sql (requires dwh_owner role + grafana_ro role)
|
||||
-- =============================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. CONTROL SCHEMA
|
||||
-- Owned by dwh_owner to match bronze/silver/gold ownership convention from 260423.
|
||||
CREATE SCHEMA IF NOT EXISTS dwh_control AUTHORIZATION dwh_owner;
|
||||
|
||||
GRANT USAGE ON SCHEMA dwh_control TO grafana_ro;
|
||||
|
||||
-- 2. PERMISSIONS (dwh_owner writes, grafana_ro reads)
|
||||
-- Existing default privileges from 260423 only cover bronze/silver/gold; extend to dwh_control.
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE dwh_owner IN SCHEMA dwh_control
|
||||
GRANT SELECT ON TABLES TO grafana_ro;
|
||||
|
||||
-- 3. EXTRACT WATERMARKS
|
||||
-- One row per incremental table. Updated by Workflow 2 after a successful load commit.
|
||||
-- last_extracted_at is the UPPER bound used in the most recent successful extract,
|
||||
-- so the next run uses `WHERE <ts_col> > last_extracted_at AND <ts_col> <= :run_started_at`.
|
||||
SET ROLE dwh_owner;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dwh_control.extract_watermarks (
|
||||
table_name TEXT PRIMARY KEY,
|
||||
last_extracted_at TIMESTAMPTZ NOT NULL DEFAULT '2026-01-01T00:00:00Z',
|
||||
last_loaded_at TIMESTAMPTZ,
|
||||
rows_loaded_last_run INT,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 4. EXTRACT RUN AUDIT LOG
|
||||
-- One row per table per cron tick. Lifecycle: extracting → uploaded → loading → loaded (or failed).
|
||||
-- Failures retain error_message; the CSV stays in dwh/exports/ for the next run to pick up.
|
||||
CREATE TABLE IF NOT EXISTS dwh_control.extract_runs (
|
||||
run_id BIGSERIAL PRIMARY KEY,
|
||||
table_name TEXT NOT NULL,
|
||||
run_started_at TIMESTAMPTZ NOT NULL,
|
||||
run_finished_at TIMESTAMPTZ,
|
||||
rows_extracted INT,
|
||||
rows_loaded INT,
|
||||
csv_path TEXT,
|
||||
status TEXT CHECK (status IN ('extracting','uploaded','loading','loaded','failed')),
|
||||
error_message TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_extract_runs_table_time
|
||||
ON dwh_control.extract_runs (table_name, run_started_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_extract_runs_status_time
|
||||
ON dwh_control.extract_runs (status, run_finished_at DESC);
|
||||
|
||||
-- 5. SEED WATERMARKS
|
||||
-- One row per incremental table. Snapshot tables (devices, live_positions) do not need
|
||||
-- watermarks and are intentionally omitted.
|
||||
INSERT INTO dwh_control.extract_watermarks (table_name) VALUES
|
||||
('position_history'),
|
||||
('trips'),
|
||||
('alarms'),
|
||||
('parking_events'),
|
||||
('device_events'),
|
||||
('ingestion_log')
|
||||
ON CONFLICT (table_name) DO NOTHING;
|
||||
|
||||
RESET ROLE;
|
||||
|
||||
COMMIT;
|
||||
63
dwh/261002_bronze_constraints_audit.sql
Normal file
63
dwh/261002_bronze_constraints_audit.sql
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
-- =============================================================
|
||||
-- BRONZE CONSTRAINTS AUDIT
|
||||
-- Target Database: tracksolid_dwh
|
||||
-- Purpose: Assert that every ON CONFLICT target used by Workflow 2
|
||||
-- (dwh_load_bronze) is backed by a PRIMARY KEY or UNIQUE
|
||||
-- constraint in the bronze schema. Fails loudly if a future
|
||||
-- DDL edit removes a key the ingestion pipeline depends on.
|
||||
-- Applies after: 260423_dwh_ddl_v1.sql
|
||||
-- Idempotent: pure assertion, no DDL changes.
|
||||
-- =============================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
missing TEXT := '';
|
||||
expected RECORD;
|
||||
BEGIN
|
||||
-- Each row asserts: bronze.<table> has a PK/UNIQUE matching <cols>.
|
||||
-- If the pipeline's ON CONFLICT clause ever diverges from this list,
|
||||
-- update both here and the n8n load workflow in lockstep.
|
||||
FOR expected IN
|
||||
SELECT * FROM (VALUES
|
||||
('devices', 'imei'),
|
||||
('live_positions', 'imei'),
|
||||
('position_history', 'imei,gps_time'),
|
||||
('trips', 'id'),
|
||||
('alarms', 'id'),
|
||||
('parking_events', 'id'),
|
||||
('device_events', 'id'),
|
||||
('ingestion_log', 'id')
|
||||
) AS t(table_name, cols)
|
||||
LOOP
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint c
|
||||
JOIN pg_class r ON r.oid = c.conrelid
|
||||
JOIN pg_namespace n ON n.oid = r.relnamespace
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT string_agg(a.attname, ',' ORDER BY k.ord) AS keycols
|
||||
FROM unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
|
||||
JOIN pg_attribute a
|
||||
ON a.attrelid = c.conrelid AND a.attnum = k.attnum
|
||||
) AS cols
|
||||
WHERE n.nspname = 'bronze'
|
||||
AND r.relname = expected.table_name
|
||||
AND c.contype IN ('p','u')
|
||||
AND cols.keycols = expected.cols
|
||||
) THEN
|
||||
missing := missing
|
||||
|| format(E'\n - bronze.%s missing PK/UNIQUE on (%s)',
|
||||
expected.table_name, expected.cols);
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
IF length(missing) > 0 THEN
|
||||
RAISE EXCEPTION E'Bronze constraint audit FAILED:%s', missing;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'Bronze constraint audit OK: all 8 ON CONFLICT targets backed by PK/UNIQUE.';
|
||||
END$$;
|
||||
|
||||
COMMIT;
|
||||
66
dwh/261003_dwh_roles.sql
Normal file
66
dwh/261003_dwh_roles.sql
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
-- =============================================================
|
||||
-- DWH ROLES AUDIT
|
||||
-- Target Database: tracksolid_dwh
|
||||
-- Purpose: Assert that the n8n DWH pipeline's role contract holds:
|
||||
-- - dwh_owner exists (writes bronze + dwh_control)
|
||||
-- - grafana_ro exists (reads bronze + silver + gold + dwh_control)
|
||||
-- - grafana_ro has CONNECT on the database
|
||||
-- - grafana_ro has USAGE on every schema it needs
|
||||
-- Applies after: 260423_dwh_ddl_v1.sql, 261001_dwh_control.sql
|
||||
-- Idempotent: pure assertion, no CREATE ROLE or GRANT statements.
|
||||
--
|
||||
-- Why this file exists: 260423 creates both roles and grants bronze/silver/gold;
|
||||
-- 261001 grants dwh_control. This file is a single checkpoint that verifies
|
||||
-- those prior migrations were applied in the right order, and fails loudly
|
||||
-- if anything is missing before the pipeline goes live.
|
||||
--
|
||||
-- Password rotation and sslmode=require enforcement are out-of-band:
|
||||
-- rotate via ALTER ROLE ... PASSWORD ... in a psql superuser session,
|
||||
-- enforce SSL via the n8n credential (sslmode=require) — not SQL-level.
|
||||
-- =============================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
missing TEXT := '';
|
||||
r RECORD;
|
||||
BEGIN
|
||||
-- 1. Roles exist
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'dwh_owner') THEN
|
||||
missing := missing || E'\n - role dwh_owner missing (expected from 260423)';
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro') THEN
|
||||
missing := missing || E'\n - role grafana_ro missing (expected from 260423)';
|
||||
END IF;
|
||||
|
||||
-- 2. grafana_ro CONNECT on this database
|
||||
IF NOT has_database_privilege('grafana_ro', current_database(), 'CONNECT') THEN
|
||||
missing := missing
|
||||
|| format(E'\n - grafana_ro lacks CONNECT on database %s',
|
||||
current_database());
|
||||
END IF;
|
||||
|
||||
-- 3. grafana_ro USAGE on every schema the pipeline / dashboards touch
|
||||
FOR r IN
|
||||
SELECT unnest(ARRAY['bronze','silver','gold','dwh_control']) AS schema_name
|
||||
LOOP
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = r.schema_name) THEN
|
||||
missing := missing
|
||||
|| format(E'\n - schema %s missing (expected from 260423/261001)',
|
||||
r.schema_name);
|
||||
ELSIF NOT has_schema_privilege('grafana_ro', r.schema_name, 'USAGE') THEN
|
||||
missing := missing
|
||||
|| format(E'\n - grafana_ro lacks USAGE on schema %s',
|
||||
r.schema_name);
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
IF length(missing) > 0 THEN
|
||||
RAISE EXCEPTION E'DWH roles audit FAILED:%s', missing;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'DWH roles audit OK: dwh_owner + grafana_ro present with expected grants.';
|
||||
END$$;
|
||||
|
||||
COMMIT;
|
||||
79
dwh/261004_dwh_observability_views.sql
Normal file
79
dwh/261004_dwh_observability_views.sql
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
-- =============================================================
|
||||
-- DWH OBSERVABILITY VIEWS
|
||||
-- Target Database: tracksolid_dwh
|
||||
-- Purpose: Surface pipeline health for Grafana dashboards. Three views,
|
||||
-- one concern each:
|
||||
-- v_table_freshness — how long since each table was last loaded
|
||||
-- v_recent_failures — failed runs in the last 24h
|
||||
-- v_watermark_lag — per-table watermark vs. now
|
||||
-- Applies after: 261001_dwh_control.sql
|
||||
-- Readability: owned by dwh_owner → grafana_ro inherits SELECT via the
|
||||
-- ALTER DEFAULT PRIVILEGES set in 261001. Explicit GRANT below
|
||||
-- covers the case where defaults were set AFTER this file runs.
|
||||
-- =============================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
SET ROLE dwh_owner;
|
||||
|
||||
-- 1. FRESHNESS
|
||||
-- One row per table that has ever loaded successfully. `lag` drives the
|
||||
-- freshness panel; `loads_last_24h` sanity-checks the cron cadence.
|
||||
CREATE OR REPLACE VIEW dwh_control.v_table_freshness AS
|
||||
SELECT
|
||||
table_name,
|
||||
MAX(run_finished_at) AS last_loaded_at,
|
||||
NOW() - MAX(run_finished_at) AS lag,
|
||||
COUNT(*) FILTER (WHERE run_started_at > NOW() - INTERVAL '24 hours') AS loads_last_24h
|
||||
FROM dwh_control.extract_runs
|
||||
WHERE status = 'loaded'
|
||||
GROUP BY table_name;
|
||||
|
||||
COMMENT ON VIEW dwh_control.v_table_freshness IS
|
||||
'Per-table load lag. Alert when lag > 4h during active hours (05:00–23:00 EAT).';
|
||||
|
||||
-- 2. RECENT FAILURES
|
||||
-- Failures retain error_message; the CSV stays in dwh/exports/ for the next
|
||||
-- scheduled run to retry. Panel should show run_id so operators can grep logs.
|
||||
CREATE OR REPLACE VIEW dwh_control.v_recent_failures AS
|
||||
SELECT
|
||||
run_id,
|
||||
table_name,
|
||||
run_started_at,
|
||||
run_finished_at,
|
||||
csv_path,
|
||||
error_message
|
||||
FROM dwh_control.extract_runs
|
||||
WHERE status = 'failed'
|
||||
AND run_started_at > NOW() - INTERVAL '24 hours'
|
||||
ORDER BY run_started_at DESC;
|
||||
|
||||
COMMENT ON VIEW dwh_control.v_recent_failures IS
|
||||
'Failed extract/load runs in the last 24h. Alert on any row.';
|
||||
|
||||
-- 3. WATERMARK LAG
|
||||
-- Distinguishes "pipeline ran but found nothing" (load_lag small, extract_lag
|
||||
-- growing) from "pipeline is stuck" (both lags growing). Snapshot tables are
|
||||
-- not in extract_watermarks so they do not appear here — that is intentional.
|
||||
CREATE OR REPLACE VIEW dwh_control.v_watermark_lag AS
|
||||
SELECT
|
||||
table_name,
|
||||
last_extracted_at,
|
||||
last_loaded_at,
|
||||
rows_loaded_last_run,
|
||||
NOW() - last_loaded_at AS load_lag,
|
||||
NOW() - last_extracted_at AS extract_lag
|
||||
FROM dwh_control.extract_watermarks;
|
||||
|
||||
COMMENT ON VIEW dwh_control.v_watermark_lag IS
|
||||
'Per-table watermark position vs. now. Incremental tables only (6 rows).';
|
||||
|
||||
RESET ROLE;
|
||||
|
||||
-- Explicit grants: defensive in case ALTER DEFAULT PRIVILEGES from 261001
|
||||
-- was not in effect when these views were created.
|
||||
GRANT SELECT ON dwh_control.v_table_freshness TO grafana_ro;
|
||||
GRANT SELECT ON dwh_control.v_recent_failures TO grafana_ro;
|
||||
GRANT SELECT ON dwh_control.v_watermark_lag TO grafana_ro;
|
||||
|
||||
COMMIT;
|
||||
BIN
dwh/device_events_schema
Normal file
BIN
dwh/device_events_schema
Normal file
Binary file not shown.
6842
dwh/tracksolid_full_schema.txt
Normal file
6842
dwh/tracksolid_full_schema.txt
Normal file
File diff suppressed because it is too large
Load diff
145
fireside_logistics_cleaned_v2.csv
Normal file
145
fireside_logistics_cleaned_v2.csv
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
IMEI,Device Name,Vehicle Name,Vehicle Icon,License Plate No.,Vehicle Model,Vehicle Brand,Driver Name,Telephone,SIM,Fuel/100km,VIN,Engine Number,Remarks,Group,Department,Account,Customer Name,Model,Activated Date,Sales Time,MAC,Subscription Expiration,User Expiration Date,Battery replacement date,ICCID,IMSI,ID Number,Installation Time
|
||||
865135061569479,UMA 382EK,UMA 382EK,automobile,UMA 382EK,,,UG,,+256792997079,,,,Dept: MTN,Default Group,MTN,fireside,Fireside Group HQ,X3,2026-02-26,2025-09-08,,2036-02-27,2036-02-27,,8925610001837573419F,641101970467667,,
|
||||
865135061569131,UMA 418EK,UMA 418EK,automobile,UMA 418EK,,,UG,,+256792997053,,,,Dept: MTN,Default Group,MTN,fireside,Fireside Group HQ,X3,2026-02-26,2025-09-08,,2036-02-27,2036-02-27,,8925610001837573385F,641101970467664,,
|
||||
862798052707896,John Mbugua - KDW 573B,KDW 573B,automobile,KDW 573B,Probox,,John Mbugua,,,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,JC400P,2026-01-30,2025-06-11,,2036-01-31,2036-01-31,,89254021414206816725,639021410681672,,
|
||||
865135061563423,Joel Ntumba - UMA 826AB,UMA 826AB,mtc,UMA 826AB,Motorbike,,Joel Ntumba,,0119051036,,,,Dept: MTN,Default Group,MTN,fireside,Fireside Group HQ,X3,2026-01-28,2025-09-08,,2036-01-29,2036-01-29,,89254021414206652690,639021410665269,,
|
||||
865135061564280,Rodin Kiberu - UMA 011EK,UMA 011EK,mtc,UMA 011EK,Motorbike,,Rodin Kiberu,,0118081642,,,,Dept: MTN,Default Group,MTN,fireside,Fireside Group HQ,X3,2026-01-28,2025-09-08,,2036-01-29,2036-01-29,,89254021414206817244,639021410681724,,
|
||||
862798052708068,Dominic Wambua - KDV 683Z,KDV 683Z,automobile,KDV 683Z,Probox,,Dominic Wambua,,0758048043,,,,Dept: ROLLOUT,Default Group,ROLLOUT,fireside,Fireside Group HQ,JC400P,2026-01-24,2025-06-11,,2036-01-25,2036-01-25,,89254021414206816964,639021410681696,,
|
||||
862798052708167,Levine Wasike - KDV 439W,KDV 439W,automobile,KDV 439W,Probox,,Levine Wasike,,0758046738,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group HQ,JC400P,2025-12-13,2025-06-11,,2035-12-14,2035-12-14,,89254021414206816741,639021410681674,,
|
||||
865135061563639,Benjamin Ananda - KDV 438W,KDV 438W,automobile,KDV 438W,Probox,,Benjamin Ananda,,0758047065,,,,Dept: PLANNING,Default Group,PLANNING,fireside,Fireside Group HQ,X3,2025-12-13,2025-09-08,,2035-12-14,2035-12-14,,89254021414206816683,639021410681668,,
|
||||
865135061569123,Albert Mutwiri - KDV 437W,KDV 437W,automobile,KDV 437W,Probox,,Albert Mutwiri,,0758047101,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group HQ,X3,2025-12-13,2025-09-08,,2035-12-14,2035-12-14,,89254021414206816881,639021410681688,,
|
||||
865135061564470,Silvanus Kipkorir - KDV 064S,KDV 064S,automobile,KDV 064S,Probox,,Silvanus Kipkorir,,0113669866,,,,Dept: AIRTEL,Default Group,AIRTEL,fireside,Fireside Group HQ,X3,2025-11-21,2025-09-08,,2035-11-22,2035-11-22,,89254021414206378718,639021410637871,,
|
||||
865135061581904,Robert Kipruto - KDV 072L,KDV 072L,automobile,KDV 072L,Probox,,Robert Kipruto,,0114149576,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group HQ,X3,2025-11-21,2025-09-08,,2035-11-22,2035-11-22,,89254021264261503993,639021266150399,,
|
||||
862798052713779,Benard Kimutai - KDN 759G,KDN 759G,automobile,KDN 759G,Probox,,Benard Kimutai,,0752143258,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,JC400P,2025-08-23,2025-06-11,,2035-08-24,2035-08-24,,89254035061001753860,639035060175386,,
|
||||
865135061043426,Geoffrey Karanja - KMGS 239H,KMGS 239H,mtc,KMGS 239H,Motorbike,,Geoffrey Karanja,,0768696658,,,,Dept: OSP-PATROL,Default Group,OSP-PATROL,fireside,Fireside Group HQ,X3,2025-08-22,2025-06-11,,2035-08-23,2035-08-23,,89254021394274518926,639021397451892,,
|
||||
865135061053714,Samuel Kihara - KMEL 225X,KMEL 225X,mtc,KMEL 225X,Motorbike,,Samuel Kihara,,0768696832,,,,Dept: OSP-PATROL,Default Group,OSP-PATROL,fireside,Fireside Group HQ,X3,2025-08-02,2025-06-11,,2035-08-03,2035-08-03,,89254021394274518934,639021397451893,,
|
||||
865135061036164,Brian Njenga - KMFF 113Z,KMFF 113Z,mtc,KMFF 113Z,Motorbike,,Brian Njenga,,0768696705,,,,Dept: OSP-PATROL,Default Group,OSP-PATROL,fireside,Fireside Group HQ,X3,2025-07-31,2025-06-11,,2035-08-01,2035-08-01,,89254021394274518850,639021397451885,,
|
||||
865135061049001,Parked - KMGK 596V,KMGK 596V,mtc,KMGK 596V,Motorbike,,Parked,,0768697064,,,,Dept: DELIVERIES,Default Group,DELIVERIES,fireside,Fireside Group HQ,X3,2025-07-31,2025-06-11,,2035-08-01,2035-08-01,,89254021394274518884,639021397451888,,
|
||||
862798052715220,Rofas Njagi - KDT 728R,KDT 728R,automobile,KDT 728R,Probox,,Rofas Njagi,,0704573658,,,,Dept: REGIONAL,Default Group,REGIONAL,fireside,Fireside Group HQ,JC400P,2025-07-16,2025-06-11,,2035-07-17,2035-07-17,,89254021334258495873,639021335849587,,
|
||||
865135061037980,Emmanuel Luseno - KDS 453Y,KDS 453Y,automobile,KDS 453Y,Pick-Up,,Emmanuel Luseno,,0790176734,,,,Dept: GENERAL,Default Group,GENERAL,fireside,Fireside Group HQ,X3,2025-07-15,2025-06-11,,,2035-07-15,,89254021394215205856,639021391520585,,
|
||||
865135061035778,John Kimeria - KDS 525D,KDS 525D,truck,KDS 525D,Crane,,John Kimeria,,0790176738,,,,Dept: GENERAL,Default Group,GENERAL,fireside,Fireside Group HQ,X3,2025-07-11,2025-06-11,,2035-07-12,2035-07-12,,89254021394215205922,639021391520592,,
|
||||
865135061053748,Rashid Hassan - KDM 840V,KDM 840V,automobile,KDM 840V,Probox,,Rashid Hassan,,0768445963,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,X3,2025-07-10,2025-06-11,,2035-07-11,2035-07-11,,89254021334212352574,639021331235257,,
|
||||
865135061042261,Kelvin Wambugu - KDR 592N,KDR 592N,automobile,KDR 592N,Probox,,Kelvin Wambugu,,0797680464,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group HQ,X3,2025-07-10,2025-06-11,,2035-07-11,2035-07-11,,89254021334258159693,639021335815969,,
|
||||
862798052713811,James Onyango - KDU 613B,KDU 613B,automobile,KDU 613B,Probox,,James Onyango,,0790176542,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2025-07-09,2025-06-11,,2035-07-10,2035-07-10,,89254021394215205880,639021391520588,,
|
||||
865135061047435,Management_Mazda - KDU 613A,KDU 613A,automobile,KDU 613A,Mazda,,Management_Mazda,,0790175971,,,,Dept: MGT,Default Group,MGT,fireside,Fireside Group HQ,X3,2025-07-09,2025-06-11,,2035-07-10,2035-07-10,,89254021394215205971,639021391520597,,
|
||||
862798050522743,Charles Nyambane - KCB 711C,KCB 711C,automobile,KCB 711C,Probox,,Charles Nyambane,,0768657106,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2023-12-22,2024-11-08,,2033-12-23,2033-12-23,,,,,
|
||||
862798050525225,Sadique Wakayula - KDC 490Q,KDC 490Q,truck,KDC 490Q,Crane,,Sadique Wakayula,,0768652386,,,,Dept: GENERAL,Default Group,GENERAL,fireside,Fireside Group HQ,JC400P,2023-12-22,2024-11-08,,2043-12-22,2043-12-22,,,,,
|
||||
862798050525068,Samuel Ng'ang'a - KDE 264M,KDE 264M,automobile,KDE 264M,Probox,,Samuel Ng'ang'a,,0768658564,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2023-12-22,2024-11-08,,2033-12-23,2033-12-23,,,,,
|
||||
862798050525837,Kennedy Ondieki - KCU 237Z,KCU 237Z,automobile,KCU 237Z,Probox,,Kennedy Ondieki,,0113669852,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2023-12-21,,,2033-12-22,2033-12-22,,,,,
|
||||
862798050523618,Geoffrey Too - KDM 308S,KDM 308S,automobile,KDM 308S,Probox,,Geoffrey Too,,0701211625,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,JC400P,2023-08-15,2023-08-22,,2033-08-16,2033-08-16,,,,,
|
||||
862798050523816,Job Ngare - KDM 309S,KDM 309S,automobile,KDM 309S,Probox,,Job Ngare,,0707936781,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2023-08-15,2023-08-22,,2033-08-16,2033-08-16,,,,,
|
||||
359857082912239,Dickson Jaoko - KDK 815R,KDK 815R,automobile,KDK 815R,Probox,,Dickson Jaoko,,0706392117,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,GT06E,2023-06-21,2023-07-27,,2033-06-22,2033-06-22,,89254021234296021287,639021239602128,,
|
||||
359857082897091,Peter Mbugua - KDK 728K,KDK 728K,automobile,KDK 728K,Probox,,Peter Mbugua,,0790262984,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,GT06E,2022-12-14,2022-12-16,,2042-12-15,2042-12-15,,89254021234222500396,639021232250039,,
|
||||
862798050524608,Peter Mbugua - KDK 728K,KDK 728K,automobile,KDK 728K,Probox,,Peter Mbugua,,0706742413,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2022-12-03,2022-12-15,,2042-12-04,2042-12-04,,,,,
|
||||
862798050524368,862798050524368,,automobile,,,,,,,,,,,Default Group,,fireside,Fireside Group HQ,JC400P,2022-10-29,2022-12-17,,2042-10-30,2042-10-30,,,,,
|
||||
862798050524558,Mutuku Joseph - KDC 739F,KDC 739F,automobile,KDC 739F,Probox,,Mutuku Joseph,,0100858817,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group HQ,JC400P,2022-01-22,2022-01-25,,2042-01-23,2042-01-23,,,,,
|
||||
862798050524897,Cornelius Kimutai - KCU 938R,KCU 938R,automobile,KCU 938R,Van,,Cornelius Kimutai,,0114924404,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group HQ,JC400P,2022-01-22,2022-01-25,,2042-01-23,2042-01-23,,,,,
|
||||
862798050522107,Cassius Wakiyo - KDB 323M,KDB 323M,automobile,KDB 323M,Probox,,Cassius Wakiyo,,0114149576,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,JC400P,2022-01-22,2022-01-25,,2042-01-23,2042-01-23,,,,,
|
||||
862798050524657,Felix Andole - KDC 207R,KDC 207R,automobile,KDC 207R,Probox,,Felix Andole,,0758689195,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2022-01-22,2022-01-25,,2042-01-23,2042-01-23,,,,,
|
||||
862798050523386,George Ochieng' - KDD 684Y,KDD 684Y,automobile,KDD 684Y,Probox,,George Ochieng',,0785586834,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,JC400P,2022-01-22,2022-01-27,,2042-01-23,2042-01-23,,,,,
|
||||
862798050524384,Hamisi Pande - KDD 689Y,KDD 689Y,automobile,KDD 689Y,Probox,,Hamisi Pande,,0701211744,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2022-01-22,2022-01-27,,2042-01-23,2042-01-23,,,,,
|
||||
862798050525589,Simon Kamau - KCE 090R,KCE 090R,automobile,KCE 090R,Probox,,Simon Kamau,,0796276387,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2022-01-19,2022-01-17,,2042-01-20,2042-01-20,,,,,
|
||||
862798050525423,Makori John - KDB 585E,KDB 585E,automobile,KDB 585E,Probox,,Makori John,,0701211724,,,,Dept: PLANNING,Default Group,PLANNING,fireside,Fireside Group HQ,JC400P,2022-01-15,2022-01-17,,2042-01-16,2042-01-16,,,,,
|
||||
862798050525951,Wright Oseko - KCG 668W,KCG 668W,automobile,KCG 668W,Probox,,Wright Oseko,,0741943212,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,JC400P,2022-01-15,2022-01-17,,2042-01-16,2042-01-16,,,,,
|
||||
862798050522859,Garage - KCH 167M,KCH 167M,automobile,KCH 167M,Probox,,Garage,,0706740252,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,JC400P,2022-01-15,2022-01-17,,2042-01-16,2042-01-16,,,,,
|
||||
862798050524707,Garage - KCE 699F,KCE 699F,automobile,KCE 699F,Probox,,Garage,,0110525751,,,,Dept: ROLLOUT,Default Group,ROLLOUT,fireside,Fireside Group HQ,JC400P,2022-01-15,2022-01-17,,2042-01-16,2042-01-16,,,,,
|
||||
862798050522883,Dan Watila - KDE 638J,KDE 638J,automobile,KDE 638J,Probox,,Dan Watila,,0112615393,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2022-01-15,2022-01-17,,2042-01-16,2042-01-16,,,,,
|
||||
862798050525605,John Ondego - KCA 542Q,KCA 542Q,automobile,KCA 542Q,Probox,,John Ondego,,0110526783,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2022-01-15,2022-01-17,,2042-01-16,2042-01-16,,,,,
|
||||
862798050288360,Brian Ngetich - KDA 717B,KDA 717B,automobile,KDA 717B,Probox,,Brian Ngetich,,0717867861,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,JC400P,2021-11-05,2021-11-08,,2041-11-06,2041-11-06,,,,,
|
||||
862798050288261,Patric Bett - KDA 609E,KDA 609E,automobile,KDA 609E,Probox,,Patric Bett,0112693340,0790176509,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,JC400P,2021-10-23,2021-10-25,,2041-10-24,2041-10-24,,,,,
|
||||
359857082042052,Gabriel Musumba - KCE 690F,KCE 690F,automobile,KCE 690F,Probox,,Gabriel Musumba,,0110094466,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,GT06E,2020-04-03,2020-04-16,,2040-04-04,2040-04-04,,89254021164215938024,639021161593802,,
|
||||
359857081885410,Allan Owana - KDK 780K,KDK 780K,automobile,KDK 780K,Probox,,Allan Owana,,0703616117,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,GT06E,2019-06-19,2019-07-01,,2039-06-20,2039-06-20,,89254021234222499854,639021232249985,,
|
||||
359857081891798,Garage - KCH 167M,KCH 167M,automobile,KCH 167M,Probox,,Garage,,0746760102,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group HQ,GT06E,2019-06-16,2019-07-01,,2039-06-17,2039-06-17,,89254021084186499493,639021088649949,,
|
||||
359857081891632,John Ondego - KCA 542Q,KCA 542Q,automobile,KCA 542Q,Probox,,John Ondego,,0746760038,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group HQ,GT06E,2019-06-15,2019-07-01,,2039-06-16,2039-06-16,,89254021084186499485,639021088649948,,
|
||||
862798052708035,862798052708035,,automobile,,Probox,,,,,,,,,Default Group,,fireside,Fireside Group HQ,JC400P,Inactive,2025-06-11,,120Month,——,,,,,
|
||||
865135061563597,Dominic Wambua - KDV 683Z,KDV 683Z,automobile,KDV 683Z,Probox,,Dominic Wambua,,0758052405,,,,Dept: ROLLOUT,Default Group,ROLLOUT,fireside,Fireside Telematics ,X3,2026-01-30,2026-02-24,,2036-01-31,2036-01-31,,89254021414206816733,639021410681673,,
|
||||
865135061562722,John Mbugua - KDW 573B,KDW 573B,automobile,KDW 573B,Probox,,John Mbugua,,0758052508,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,X3,2026-01-30,2026-02-24,,2036-01-31,2036-01-31,,89254021414206816832,639021410681683,,
|
||||
862798052708282,Godffrey Nandwa - KCN 496A,KCN 496A,automobile,KCN 496A,Probox,,Godffrey Nandwa,,0758047934,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,JC400P,2026-01-25,2026-02-20,,2036-01-26,2036-01-26,,89254021414206816865,639021410681686,,
|
||||
862798052707888,Benjamin Ananda - KDV 438W,KDV 438W,automobile,KDV 438W,Probox,,Benjamin Ananda,,0758047312,,,,Dept: PLANNING,Default Group,PLANNING,fireside,Fireside Telematics ,JC400P,2025-12-15,2026-02-20,,2035-12-16,2035-12-16,,89254021414206816980,639021410681698,,
|
||||
862798052708076,Albert Mutwiri - KDV 437W,KDV 437W,automobile,KDV 437W,Probox,,Albert Mutwiri,,0758047094,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Telematics ,JC400P,2025-12-13,2026-02-20,,2035-12-14,2035-12-14,,89254021414206816782,639021410681678,,
|
||||
865135061562847,Levine Wasike - KDV 439W,KDV 439W,automobile,KDV 439W,Probox,,Levine Wasike,,0758047032,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,X3,2025-12-13,2026-02-24,,2035-12-14,2035-12-14,,89254021414206816840,639021410681684,,
|
||||
862798052714066,862798052714066,,automobile,,Probox,,,,,,,,,Default Group,,fireside,Fireside Telematics ,JC400P,2025-11-21,2025-06-11,,2035-11-22,2035-11-22,,89254021414206378684,639021410637868,,
|
||||
862798052713837,Kennedy Ondieki - KCU 237Z,KCU 237Z,automobile,KCU 237Z,Probox,,Kennedy Ondieki,,0113669852,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,JC400P,2025-10-08,2026-02-20,,2035-10-09,2035-10-09,,89254021414206327855,639021410632785,,
|
||||
862798052713696,862798052713696,,automobile,,Probox,,,,,,,,,Default Group,,fireside,Fireside Telematics ,JC400P,2025-09-02,2025-06-11,,2035-09-03,2035-09-03,,89254021394215205906,639021391520590,,
|
||||
862798052713985,Timothy Gitau - KDT 916R,KDT 916R,automobile,KDT 916R,Probox,,Timothy Gitau,,0768696668,,,,Dept: REGIONAL,Default Group,REGIONAL,fireside,Fireside Telematics ,JC400P,2025-08-02,2026-02-20,,2035-08-03,2035-08-03,,89254021394274518892,639021397451889,,
|
||||
865135061035653,Richardson Komu - KDT 923R,KDT 923R,automobile,KDT 923R,Probox,,Richardson Komu,,0768697292,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,X3,2025-08-02,2026-02-24,,2035-08-03,2035-08-03,,89254021394274518942,639021397451894,,
|
||||
865135061048466,Samuel Muriithy - KDR 594N,KDR 594N,automobile,KDR 594N,Probox,,Samuel Muriithy,,0797680395,,,,Dept: ROLLOUT,Default Group,ROLLOUT,fireside,Fireside Telematics ,X3,2025-07-24,2026-02-24,,2035-07-25,2035-07-25,,89254021334258159628,639021335815962,,
|
||||
865135061054555,Rofas Njagi - KDT 728R,KDT 728R,automobile,KDT 728R,Probox,,Rofas Njagi,,0790176726,,,,Dept: REGIONAL,Default Group,REGIONAL,fireside,Fireside Telematics ,X3,2025-07-16,2026-02-24,,2035-07-17,2035-07-17,,89254021394215205823,639021391520582,,
|
||||
862798052713761,Management_Mazda - KDU 613A,KDU 613A,automobile,KDU 613A,Mazda,,Management_Mazda,,0790176786,,,,Dept: MGT,Default Group,MGT,fireside,Fireside Telematics ,JC400P,2025-07-09,2026-02-20,,2035-07-10,2035-07-10,,89254021394215205955,639021391520595,,
|
||||
865135061054548,James Onyango - KDU 613B,KDU 613B,automobile,KDU 613B,Probox,,James Onyango,,0790175997,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,X3,2025-07-09,2026-02-24,,2035-07-10,2035-07-10,,89254021394215205948,639021391520594,,
|
||||
862798050526231,Rashid Hassan - KDM 840V,KDM 840V,automobile,KDM 840V,Probox,,Rashid Hassan,,0790175526,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,JC400P,2023-12-22,2026-02-20,,2043-12-23,2043-12-23,,,,,
|
||||
862798050523139,Mike Wanaswa - KDT 724R,KDT 724R,automobile,KDT 724R,Probox,,Mike Wanaswa,,0790175045,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,JC400P,2023-12-22,2026-02-20,,2043-12-23,2043-12-23,,,,,
|
||||
862798050523063,Kelvin Wambugu - KDR 594N,KDR 594N,automobile,KDR 594N,Probox,,Kelvin Wambugu,,0701211876,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Telematics ,JC400P,2023-12-22,2026-02-20,,2043-12-22,2043-12-22,,,,,
|
||||
862798050523626,Major Simiyu - KDS 949Y,KDS 949Y,automobile,KDS 949Y,Probox,,Major Simiyu,,0701211892,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Telematics ,JC400P,2023-12-22,2026-02-20,,2033-12-23,2033-12-23,,,,,
|
||||
862798050523337,Victor Kimutai - KDS 919Y,KDS 919Y,automobile,KDS 919Y,Probox,,Victor Kimutai,,0700242527,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,JC400P,2023-12-22,2026-02-20,,2043-12-22,2043-12-22,,,,,
|
||||
862798050523295,Emmanuel Luseno - KDS 453 Y,KDS 453 Y,automobile,KDS 453 Y,Pick-Up,,Emmanuel Luseno,,0700242474,,,,Dept: GENERAL,Default Group,GENERAL,fireside,Fireside Telematics ,JC400P,2023-12-22,2026-02-20,,2033-12-23,2033-12-23,,,,,
|
||||
862798050523014,Samuel Muriithy - KDR 594N,KDR 594N,automobile,KDR 594N,Probox,,Samuel Muriithy,,0790175423,,,,Dept: ROLLOUT,Default Group,ROLLOUT,fireside,Fireside Telematics ,JC400P,2023-12-21,2026-02-20,,2033-12-22,2033-12-22,,,,,
|
||||
862798050521521,John Kimeria - KDS 525D,KDS 525D,truck,KDS 525D,Crane,,John Kimeria,,0752958416,,,,Dept: GENERAL,Default Group,GENERAL,fireside,Fireside Telematics ,JC400P,2023-11-26,2026-02-20,,2033-11-27,2033-11-27,,,,,
|
||||
862798050524533,Leonard Nzai - KDM 306S,KDM 306S,automobile,KDM 306S,Probox,,Leonard Nzai,,0703487162,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,JC400P,2023-08-21,2026-02-20,,2033-08-22,2033-08-22,,,,,
|
||||
359857082898016,Job Ngare - KDM 309S,KDM 309S,automobile,KDM 309S,Probox,,Job Ngare,,0706895756,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,GT06E,2023-08-15,2026-02-24,,2033-08-16,2033-08-16,,89254021324273007563,639021327300756,,
|
||||
862798050525266,Dickson Jaoko - KDK 815R,KDK 815R,automobile,KDK 815R,Probox,,Dickson Jaoko,,0706665867,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,JC400P,2023-06-21,2026-02-20,,2033-06-22,2033-06-22,,,,,
|
||||
862798050523527,Allan Owana - KDK 780K,KDK 780K,automobile,KDK 780K,Probox,,Allan Owana,,0792375024,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,JC400P,2022-12-03,2026-02-20,,2042-12-04,2042-12-04,,,,,
|
||||
862798050524426,Amani Sulubu - KCY 090X,KCY 090X,automobile,KCY 090X,Probox,,Amani Sulubu,,0113823350,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,JC400P,2022-01-16,2026-02-20,,2042-01-17,2042-01-17,,,,,
|
||||
862798050522065,Gideon Kiprono - KCQ 215F,KCQ 215F,automobile,KCQ 215F,Probox,,Gideon Kiprono,,0113343715,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,JC400P,2022-01-16,2026-02-20,,2042-01-17,2042-01-17,,,,,
|
||||
862798050525670,Gabriel Musumba - KCE 690F,KCE 690F,automobile,KCE 690F,Probox,,Gabriel Musumba,,0701211996,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,JC400P,2022-01-15,2026-02-20,,2042-01-16,2042-01-16,,,,,
|
||||
862798050288345,Santoes Omondi - KCZ 181P,KCZ 181P,automobile,KCZ 181P,Pick-Up,,Santoes Omondi,,0768446105,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,JC400P,2021-11-06,2026-02-20,,2041-11-07,2041-11-07,,,,,
|
||||
862798050288303,Elias Baya - KCZ 476E,KCZ 476E,automobile,KCZ 476E,Probox,,Elias Baya,,0115870439,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Telematics ,JC400P,2021-11-06,2026-02-20,,2041-11-07,2041-11-07,,,,,
|
||||
862798050288212,Nicholas Erastus - KCQ 581M,KCQ 581M,automobile,KCQ 581M,Probox,,Nicholas Erastus,,0746979531,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,JC400P,2021-11-02,2026-02-20,,2041-11-03,2041-11-03,,,,,
|
||||
359857082898008,Samuel Ng'ang'a - KDE 264M,KDE 264M,automobile,KDE 264M,Probox,,Samuel Ng'ang'a,,0711731539,,,,Dept: ISP,Default Group,ISP ,fireside,Fireside Telematics ,GT06E,2021-10-28,2026-02-24,,2041-10-29,2041-10-29,,89254021264260342245,639021266034224,,
|
||||
359857082898487,Dan Watila - KDE 638J,KDE 638J,automobile,KDE 638J,Probox,,Dan Watila,,0116242996,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,GT06E,2021-10-21,2026-02-24,,2041-10-22,2041-10-22,,89254021334258404214,639021335840421,,
|
||||
359857082900358,Geoffrey Too - KDM 308S,KDM 308S,automobile,KDM 308S,Probox,,Geoffrey Too,,0796527601,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,GT06E,2021-10-21,2026-02-24,,2041-10-22,2041-10-22,,89254021264260126572,639021266012657,,
|
||||
359857082896911,Hamisi Pande - KDD 689Y,KDD 689Y,automobile,KDD 689Y,Probox,,Hamisi Pande,,0112714612,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,GT06E,2021-09-17,2026-02-24,,2041-09-18,2041-09-18,,89254021214211314660,639021211131466,,
|
||||
359857082900697,George Ochieng' - KDD 684Y,KDD 684Y,automobile,KDD 684Y,Probox,,George Ochieng',,0114879518,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,GT06E,2021-09-17,2026-02-24,,2041-09-18,2041-09-18,,89254021214211314678,639021211131467,,
|
||||
359857082897257,Cassius Wakiyo - KDB 323M,KDB 323M,automobile,KDB 323M,Probox,,Cassius Wakiyo,,0746428882,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,GT06E,2021-08-29,2026-02-24,,2041-08-29,2041-08-29,,89254021234222500818,639021232250081,,
|
||||
359857082897737,John Makori - KDB 585E,KDB 585E,automobile,KDB 585E,Probox,,John Makori,,0114596734,,,,Dept: PLANNING,Default Group,PLANNING,fireside,Fireside Telematics ,GT06E,2021-08-29,2026-02-24,,2041-08-29,2041-08-29,,89254021214211145262,639021211114526,,
|
||||
359857082911983,Brian Ngetich - KDA 717B,KDA 717B,automobile,KDA 717B,Probox,,Brian Ngetich,0795188807,0795188807,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,GT06E,2021-08-29,2026-02-24,,2041-08-29,2041-08-29,,89254021214211145288,639021211114528,,
|
||||
359857082902461,Sadique Wakayula - KDC 490Q,KDC 490Q,truck,KDC 490Q,Crane,,Sadique Wakayula,,0757556468,,,,Dept: GENERAL,Default Group,GENERAL,fireside,Fireside Telematics ,GT06E,2021-05-22,2026-02-24,,2041-05-22,2041-05-22,,89254021154296722488,639021159672248,,
|
||||
359857082902503,Felix Andole - KDC 207R,KDC 207R,automobile,KDC 207R,Probox,,Felix Andole,,0794820817,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,GT06E,2021-05-15,2026-02-24,,2041-05-15,2041-05-15,,89254021224270993254,639021227099325,,
|
||||
359857082897794,Mutuku Joseph - KDC 739F,KDC 739F,automobile,KDC 739F,Probox,,Mutuku Joseph,0115019037,0115019037,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Telematics ,GT06E,2021-04-10,2026-02-24,,2041-04-10,2041-04-10,,89254021224222632356,639021222263235,,
|
||||
359857082910589,Patric Bett - KDA 609E,KDA 609E,automobile,KDA 609E,Probox,,Patric Bett,,0797622637,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,GT06E,2020-10-26,2026-02-24,,2040-10-27,2040-10-27,,89254021154296722496,639021159672249,,
|
||||
359857082918012,Charles Nyambane - KCB 711C,KCB 711C,automobile,KCB 711C,Probox,,Charles Nyambane,,0793704231,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,GT06E,2020-09-21,2026-02-24,,2040-09-22,2040-09-22,,89254021154287138363,639021158713836,,
|
||||
359857081887069,Wright Oseko - KCG 668W,KCG 668W,automobile,KCG 668W,Probox,,Wright Oseko,,0746763106,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Telematics ,GT06E,2019-06-30,2026-02-24,,2039-07-01,2039-07-01,,89254021084186499915,639021088649991,,
|
||||
359857081891590,Garage - KCE 699F,KCE 699F,automobile,KCE 699F,Probox,,Garage,,0746760215,,,,Dept: ROLLOUT,Default Group,ROLLOUT,fireside,Fireside Telematics ,GT06E,2019-06-16,2026-02-24,,2039-06-17,2039-06-17,,89254021084186499519,639021088649951,,
|
||||
359857081891566,Simon Kamau - KCE 090R,KCE 090R,automobile,KCE 090R,Probox,,Simon Kamau,,0746760404,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,GT06E,2019-06-16,2026-02-24,,2039-06-17,2039-06-17,,89254021084186499527,639021088649952,,
|
||||
359857081892101,Cornelius Kimutai - KCU 938R,KCU 938R,automobile,KCU 938R,Van,,Cornelius Kimutai,,0746759919,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Telematics ,GT06E,2019-06-12,2026-02-24,,2039-06-13,2039-06-13,,89254021084186499451,639021088649945,,2019-06-12
|
||||
359857081892309,Nicholas Erastus - KCQ 581M,KCQ 581M,automobile,KCQ 581M,Probox,,Nicholas Erastus,,0700023776,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Telematics ,GT06E,2019-06-09,2026-02-24,,2039-06-10,2039-06-10,,89254021084178504672,639021087850467,,
|
||||
865135061563415,Barack Orwa - KDW 781E,KDW 781E,automobile,KDW 781E,Vazel,,Barack Orwa,,0758052541,,,,Dept: MGT,Default Group,MGT,fireside,Fireside Group MSA,X3,2026-01-13,2025-09-08,,2036-01-14,2036-01-14,,89254021414206816931,639021410681693,,
|
||||
865135061035133,Major Simiyu - KDS 949Y,KDS 949Y,automobile,KDS 949Y,Probox,,Major Simiyu,,0768696642,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group MSA,X3,2025-08-02,2025-06-11,,2035-08-03,2035-08-03,,89254021394274518918,639021397451891,,
|
||||
865135061043079,Mike Wanaswa - KDT 724R,KDT 724R,automobile,KDT 724R,Probox,,Mike Wanaswa,,0768696664,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,X3,2025-08-02,2025-06-11,,2035-08-03,2035-08-03,,89254021394274518959,639021397451895,,
|
||||
865135061048953,Timothy Gitau - KDT 916R,KDT 916R,automobile,KDT 916R,Probox,,Timothy Gitau,,0768697056,,,,Dept: REGIONAL,Default Group,REGIONAL,fireside,Fireside Group MSA,X3,2025-08-02,2025-06-11,,2035-08-03,2035-08-03,,89254021394274518967,639021397451896,,
|
||||
865135061048276,Victor Kimutai - KDS 919Y,KDS 919Y,automobile,KDS 919Y,Probox,,Victor Kimutai,,0768696755,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,X3,2025-08-02,2025-06-11,,2035-08-03,2035-08-03,,89254021394274518900,639021397451890,,
|
||||
862798050526256,Ian Dancun - KDT 923R,KDT 923R,automobile,KDT 923R,Probox,,Ian Dancun,,0794873610,,,,Dept: QEHS,Default Group,QEHS,fireside,Fireside Group MSA,JC400P,2023-12-22,,,2043-12-22,2043-12-22,,,,,
|
||||
862798050526165,Wilfred Kinyanjui - KCU 729C,KCU 729C,truck,KCU 729C,Crane,,Wilfred Kinyanjui,,0790564929,,,,Dept: GENERAL,Default Group,GENERAL,fireside,Fireside Group MSA,JC400P,2023-11-26,2024-11-08,,2033-11-27,2033-11-27,,,,,
|
||||
359857082916826,Denis Kazungu - KDM 794R,KDM 794R,automobile,KDM 794R,Probox,,Denis Kazungu,,0705700971,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group MSA,GT06E,2023-08-21,2023-08-22,,2033-08-22,2033-08-22,,89254021324273006854,639021327300685,,
|
||||
359857082898073,Mutuku Antony - KDK 732K,KDK 732K,automobile,KDK 732K,Probox,,Mutuku Antony,,0793026954,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group MSA,GT06E,2022-12-20,2022-12-20,,2042-12-21,2042-12-21,,89254021234222387539,639021232238753,,
|
||||
862798050524681,Mutuku Antony - KDK 732K,KDK 732K,automobile,KDK 732K,Probox,,Mutuku Antony,,0796275746,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group MSA,JC400P,2022-12-06,2022-12-16,,2042-12-07,2042-12-07,,,,,
|
||||
862798050524566,Makanda Andrew - KCZ 155P,KCZ 155P,automobile,KCZ 155P,Pick-Up,,Makanda Andrew,,0758781444,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,JC400P,2022-01-22,2025-02-24,,2042-01-23,2042-01-23,,,,,
|
||||
862798050521612,Denis Kazungu - KDM 794R,KDM 794R,automobile,KDM 794R,Probox,,Denis Kazungu,,0704113731,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group MSA,JC400P,2022-01-22,2024-11-19,,2042-01-23,2042-01-23,,,,,
|
||||
862798050522719,Mbuvi Kioko - KCZ 199P,KCZ 199P,automobile,KCZ 199P,Pick-Up,,Mbuvi Kioko,,0768218655,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,JC400P,2022-01-16,2022-12-16,,2042-01-17,2042-01-17,,,,,
|
||||
862798050524087,Felix Muema - KCZ 223P,KCZ 223P,automobile,KCZ 223P,Pick-Up,,Felix Muema,,0113973875,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,JC400P,2022-01-16,2024-12-30,,2042-01-17,2042-01-17,,,,,
|
||||
862798050522891,Lawrence Kijogi - KCY 080X,KCY 080X,automobile,KCY 080X,Pick-Up,,Lawrence Kijogi,,0113287191,,,,Dept: ROLLOUT,Default Group,ROLLOUT,fireside,Fireside Group MSA,JC400P,2022-01-16,2022-12-16,,2042-01-17,2042-01-17,,,,,
|
||||
862798050524392,Ndegwa Dancun - KCG 669W,KCG 669W,automobile,KCG 669W,Probox,,Ndegwa Dancun,,0113799173,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,JC400P,2022-01-16,2022-12-16,,2042-01-17,2042-01-17,,,,,
|
||||
862798050521752,Simon Munda - KCZ 154S,KCZ 154S,automobile,KCZ 154S,Probox,,Simon Munda,,0113805921,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,JC400P,2022-01-16,2022-12-16,,2042-01-17,2042-01-17,,,,,
|
||||
862798050524012,Moses Wambua - KCZ 751V,KCZ 751V,automobile,KCZ 751V,Probox,,Moses Wambua,,0113313797,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,JC400P,2022-01-16,2022-12-16,,2042-01-17,2042-01-17,,,,,
|
||||
862798050523204,Amani Kazungu - KCY 084X,KCY 084X,automobile,KCY 084X,Probox,,Amani Kazungu,,0707892547,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,JC400P,2022-01-16,2022-12-16,,2042-01-17,2042-01-17,,,,,
|
||||
862798050523949,Joseph Kabandi - KCY 076X,KCY 076X,automobile,KCY 076X,Probox,,Joseph Kabandi,,0113288492,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,JC400P,2022-01-16,2022-12-16,,2042-01-17,2042-01-17,,,,,
|
||||
862798050525613,Kennedy Chege - KCQ 618K,KCQ 618K,automobile,KCQ 618K,Probox,,Kennedy Chege,,0729994247,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,JC400P,2022-01-16,2022-12-19,,2042-01-17,2042-01-17,,,,,
|
||||
862798050525753,Noel Merengeni - KCY 838X,KCY 838X,automobile,KCY 838X,Probox,,Noel Merengeni,,,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group MSA,JC400P,2022-01-15,2023-08-23,,2042-01-16,2042-01-16,,,,,
|
||||
359857082925330,Noel Merengeni - KCY 838X,KCY 838X,automobile,KCY 838X,Probox,,Noel Merengeni,,0794873610,,,,Dept: FDS,Default Group,FDS,fireside,Fireside Group MSA,GT06E,2020-10-26,2023-08-22,,2040-10-27,2040-10-27,,89254021154296723429,639021159672342,,
|
||||
359857082900341,Simon Munda - KCZ 154S,KCZ 154S,automobile,KCZ 154S,Probox,,Simon Munda,,0757236135,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,GT06E,2020-09-23,2022-12-16,,2040-09-24,2040-09-24,,89254021154296723312,639021159672331,,
|
||||
359857082912486,Moses Wambua - KCZ 751V,KCZ 751V,automobile,KCZ 751V,Probox,,Moses Wambua,,0792756503,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,GT06E,2020-09-23,2022-12-16,,2040-09-24,2040-09-24,,89254021154296723437,639021159672343,,
|
||||
353549090553685,Daniel Omondi - KMFF 099Z,KMFF 099Z,mtc,KMFF 099Z,Motorbike,,Daniel Omondi,0112794067,0759336150,,,,Dept: OSP-PATROL,Default Group,OSP-PATROL,fireside,Fireside Group MSA,AT4,2020-09-23,2022-12-16,,2040-09-24,2040-09-24,,89254021334258404099,639021335840409,,
|
||||
353549090567685,Daniel Kipkirui - KMFF 162Z,KMFF 162Z,mtc,KMFF 162Z,Motorbike,,Daniel Kipkirui,0112795498,0742532058,,,,Dept: OSP-PATROL,Default Group,OSP-PATROL,fireside,Fireside Group MSA,AT4,2020-09-23,2022-12-16,,2040-09-24,2040-09-24,,89254021264260388966,639021266038896,,
|
||||
359857082910886,Makanda Andrew - KCZ 155P,KCZ 155P,automobile,KCZ 155P,Pick-Up,,Makanda Andrew,,0745067338,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,GT06E,2020-08-23,2025-02-24,,2040-08-24,2040-08-24,,89254021154287138397,639021158713839,,
|
||||
359857082908500,Santoes Omondi - KCZ 181P,KCZ 181P,automobile,KCZ 181P,Pick-Up,,Santoes Omondi,,0701211974,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,GT06E,2020-08-23,2022-12-16,,2040-08-24,2040-08-24,,89254021374215155087,639021371515508,,
|
||||
359857082918038,Mbuvi Kioko - KCC 199P,KCC 199P,automobile,KCC 199P,Pick-Up,,Mbuvi Kioko,,0797318126,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,GT06E,2020-08-22,2022-12-16,,2040-08-23,2040-08-23,,89254021154287138389,639021158713838,,
|
||||
359857082907973,Felix Muema - KCZ 223P,KCZ 223P,automobile,KCZ 223P,Probox,,Felix Muema,,0757843826,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,GT06E,2020-08-22,2024-12-30,,2040-08-23,2040-08-23,,89254021154287138371,639021158713837,,
|
||||
359857082042854,Elias Baya - KCZ 476E,KCZ 476E,automobile,KCZ 476E,Probox,,Elias Baya,,0110941187,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,GT06E,2020-08-09,2022-12-16,,2040-08-10,2040-08-10,,89254021164224352993,639021162435299,,
|
||||
359857082044280,Lawrence Kijogi - KCY 080X,KCY 080X,automobile,KCY 080X,Probox,,Lawrence Kijogi,,0708155933,,,,Dept: ROLLOUT,Default Group,ROLLOUT,fireside,Fireside Group MSA,GT06E,2020-07-13,2022-12-16,,2040-07-13,2040-07-13,,89254029851005131222,639029850513122,,
|
||||
359857082037185,Amani Kazungu - KCY 084X,KCY 084X,automobile,KCY 084X,Probox,,Amani Kazungu,,0757338522,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,GT06E,2020-07-13,2022-12-16,,2040-07-14,2040-07-14,,89254021154287000597,639021158700059,,
|
||||
359857082046145,Joseph Kabandi - KCY 076X,KCY 076X,automobile,KCY 076X,Probox,,Joseph Kabandi,,0110850007,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,GT06E,2020-07-13,2022-12-16,,2040-07-14,2040-07-14,,89254021164223447158,639021162344715,,
|
||||
359857082040981,Amani Sulubu - KCY 090X,KCY 090X,automobile,KCY 090X,Probox,,Amani Sulubu,,0793375853,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,GT06E,2020-07-13,2022-12-16,,2040-07-14,2040-07-14,,89254021064168004164,639021066800416,,
|
||||
359857082038977,Wilfred Kinyanjui - KCU 729C,KCU 729C,truck,KCU 729C,Crane,,Wilfred Kinyanjui,,0110094469,,,,Dept: GENERAL,Default Group,GENERAL,fireside,Fireside Group MSA,GT06E,2020-04-05,2022-12-16,,2040-04-06,2040-04-06,,89254021164215938057,639021161593805,,
|
||||
359857081886467,Gideon Kiprono - KCQ 215F,KCQ 215F,automobile,KCQ 215F,Probox,,Gideon Kiprono,,0746763076,,,,Dept: ISP,Default Group,ISP,fireside,Fireside Group MSA,GT06E,2019-06-30,2022-12-16,,2039-07-01,2039-07-01,,89254021084186499865,639021088649986,,
|
||||
359857081886905,Kennedy Chege - KCQ 618K,KCQ 618K,automobile,KCQ 618K,Probox,,Kennedy Chege,,0746763132,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,GT06E,2019-06-30,2022-12-16,,2039-07-01,2039-07-01,,89254021084186499923,639021088649992,,
|
||||
359857081887192,Ndegwa Dancun - KCG 669W,KCG 669W,automobile,KCG 669W,Probox,,Ndegwa Dancun,,0746760191,,,,Dept: OSP,Default Group,OSP,fireside,Fireside Group MSA,GT06E,2019-06-15,2022-12-16,,2039-06-16,2039-06-16,,89254021084186499501,639021088649950,,
|
||||
|
|
|
@ -1,8 +1,24 @@
|
|||
"""
|
||||
import_drivers_csv.py — Fireside Communications · Driver & Vehicle CSV Import
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
One-shot script: reads 20260414_FS__Logistics - final_fixed.csv, compares
|
||||
each row against the current tracksolid.devices values, and updates the DB.
|
||||
One-shot script: reads the snake_case Fireside Group vehicle CSV
|
||||
(`20260427_FSG_Vehicles_mitieng.csv`), compares each row against the
|
||||
current `tracksolid.devices` values, and updates the DB.
|
||||
|
||||
The CSV columns mirror the DB schema directly — no inference. Cells with the
|
||||
literal string "NULL" are treated as missing.
|
||||
|
||||
Fields imported (per Phase 0.1 of the Business Analytics redesign plan):
|
||||
Identity : driver_name, driver_phone, vehicle_number, vehicle_name,
|
||||
vehicle_models, mc_type, device_name
|
||||
SIM : sim, iccid, imsi
|
||||
Lifecycle : activation_time, expiration
|
||||
Business meta : assigned_city, cost_centre, assigned_route,
|
||||
vehicle_category, vehicle_brand, fuel_100km, depot_address
|
||||
|
||||
`depot_geom` (PostGIS Point) is intentionally NOT imported — needs WKT and
|
||||
isn't present as coordinates in the CSV. Set it via a follow-up migration
|
||||
when geofences are loaded.
|
||||
|
||||
Usage:
|
||||
# Dry-run — shows diff, writes nothing
|
||||
|
|
@ -17,68 +33,75 @@ Usage:
|
|||
# Only fill fields that are currently NULL in the DB (never overwrite)
|
||||
python import_drivers_csv.py --only-null --apply
|
||||
|
||||
# Use a different CSV
|
||||
python import_drivers_csv.py --csv path/to/file.csv
|
||||
|
||||
Pre-requisite:
|
||||
Migration 06 must be applied first (adds assigned_city / cost_centre columns).
|
||||
Migrations 02, 05, 06 must be applied (they add the metadata columns).
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
from ts_shared_rev import clean, clean_num, clean_ts, get_conn, get_logger
|
||||
|
||||
log = get_logger("csv_import")
|
||||
|
||||
CSV_PATH = Path(__file__).parent / "20260414_FS__Logistics - final_fixed.csv"
|
||||
DEFAULT_CSV_PATH = Path(__file__).parent / "20260427_FSG_Vehicles_mitieng.csv"
|
||||
|
||||
# Columns fetched from DB for comparison
|
||||
# Columns fetched from DB for diff comparison.
|
||||
DB_COLS = [
|
||||
"imei", "driver_name", "driver_phone", "vehicle_number", "vehicle_name",
|
||||
"vehicle_models", "cost_centre", "sim", "iccid", "imsi", "mc_type",
|
||||
"activation_time", "expiration", "device_name", "assigned_city",
|
||||
"imei",
|
||||
# Identity
|
||||
"driver_name", "driver_phone", "vehicle_number", "vehicle_name",
|
||||
"vehicle_models", "mc_type", "device_name",
|
||||
# SIM
|
||||
"sim", "iccid", "imsi",
|
||||
# Lifecycle
|
||||
"activation_time", "expiration",
|
||||
# Business metadata (Phase 0.1 additions)
|
||||
"assigned_city", "cost_centre", "assigned_route",
|
||||
"vehicle_category", "vehicle_brand", "fuel_100km", "depot_address",
|
||||
]
|
||||
|
||||
# Driver Name values that are placeholders — skip writing driver_name for these
|
||||
# Driver Name values that are placeholders — skip writing driver_name for these.
|
||||
_DRIVER_SKIP = {"identification", "ug"}
|
||||
|
||||
# Columns that need an explicit cast in the UPDATE statement.
|
||||
_TIMESTAMPTZ_COLS = {"activation_time", "expiration"}
|
||||
_NUMERIC_COLS = {"fuel_100km"}
|
||||
|
||||
def _infer_city(plate: str) -> str | None:
|
||||
"""Derive assigned_city from license plate prefix."""
|
||||
p = (plate or "").strip().upper()
|
||||
if p.startswith("UMA") or p.startswith("UAG"):
|
||||
return "KLA"
|
||||
if p.startswith("K"):
|
||||
return "NBO"
|
||||
|
||||
def _read(row: dict, col: str) -> str | None:
|
||||
"""Read a CSV column treating literal 'NULL'/'None' (case-insensitive) as missing."""
|
||||
v = clean(row.get(col))
|
||||
if v is None:
|
||||
return None
|
||||
return None if v.upper() in ("NULL", "NONE") else v
|
||||
|
||||
|
||||
def _clean_date(v: str) -> str | None:
|
||||
"""Accept YYYY-MM-DD and return as ISO string suitable for TIMESTAMPTZ cast."""
|
||||
s = (v or "").strip()
|
||||
if not s:
|
||||
return None
|
||||
try:
|
||||
date.fromisoformat(s)
|
||||
return s
|
||||
except ValueError:
|
||||
return None
|
||||
def _read_num(row: dict, col: str) -> float | None:
|
||||
v = _read(row, col)
|
||||
return clean_num(v) if v is not None else None
|
||||
|
||||
|
||||
def load_csv() -> dict[str, dict]:
|
||||
def _read_ts(row: dict, col: str) -> str | None:
|
||||
v = _read(row, col)
|
||||
return clean_ts(v) if v is not None else None
|
||||
|
||||
|
||||
def load_csv(csv_path: Path) -> dict[str, dict]:
|
||||
"""Load CSV into a dict keyed by IMEI."""
|
||||
rows: dict[str, dict] = {}
|
||||
with open(CSV_PATH, encoding="utf-8-sig", newline="") as f:
|
||||
with open(csv_path, encoding="utf-8-sig", newline="") as f:
|
||||
for row in csv.DictReader(f):
|
||||
imei = (row.get("IMEI") or "").strip()
|
||||
imei = (row.get("imei") or "").strip()
|
||||
if not imei:
|
||||
continue
|
||||
rows[imei] = row
|
||||
log.info("CSV loaded: %d rows from %s", len(rows), CSV_PATH.name)
|
||||
log.info("CSV loaded: %d rows from %s", len(rows), csv_path.name)
|
||||
return rows
|
||||
|
||||
|
||||
|
|
@ -102,42 +125,46 @@ def build_update(csv_row: dict, db_row: dict | None, only_null: bool) -> dict[st
|
|||
When only_null=True, skip any DB column that already has a value.
|
||||
The driver_name column is skipped for placeholder-labelled devices.
|
||||
"""
|
||||
driver_raw = clean(csv_row.get("Driver Name")) or ""
|
||||
plate = clean(csv_row.get("License Plate No.")) or ""
|
||||
driver_raw = (_read(csv_row, "driver_name") or "")
|
||||
is_placeholder = driver_raw.lower() in _DRIVER_SKIP
|
||||
skip_row = driver_raw.lower() == "identification"
|
||||
|
||||
if skip_row:
|
||||
if driver_raw.lower() == "identification":
|
||||
return {}
|
||||
|
||||
proposed: dict[str, object] = {
|
||||
"vehicle_number": clean(plate),
|
||||
"vehicle_name": clean(plate),
|
||||
"vehicle_models": clean(csv_row.get("Vehicle Model")),
|
||||
"cost_centre": clean(csv_row.get("Department")),
|
||||
"sim": clean(csv_row.get("SIM")),
|
||||
"iccid": clean(csv_row.get("ICCID")),
|
||||
"imsi": clean(csv_row.get("IMSI")),
|
||||
"mc_type": clean(csv_row.get("Model")),
|
||||
"activation_time": _clean_date(csv_row.get("Activated Date", "")),
|
||||
"expiration": _clean_date(csv_row.get("Subscription Expiration", "")),
|
||||
"driver_phone": clean(csv_row.get("Telephone")),
|
||||
"assigned_city": _infer_city(plate),
|
||||
# Identity
|
||||
"driver_phone": _read(csv_row, "driver_phone"),
|
||||
"vehicle_number": _read(csv_row, "vehicle_number"),
|
||||
"vehicle_name": _read(csv_row, "vehicle_name"),
|
||||
"vehicle_models": _read(csv_row, "vehicle_models"),
|
||||
"mc_type": _read(csv_row, "mc_type"),
|
||||
"device_name": _read(csv_row, "device_name"),
|
||||
# SIM
|
||||
"sim": _read(csv_row, "sim"),
|
||||
"iccid": _read(csv_row, "iccid"),
|
||||
"imsi": _read(csv_row, "imsi"),
|
||||
# Lifecycle
|
||||
"activation_time": _read_ts(csv_row, "activation_time"),
|
||||
"expiration": _read_ts(csv_row, "expiration"),
|
||||
# Business metadata
|
||||
"assigned_city": _read(csv_row, "assigned_city"),
|
||||
"cost_centre": _read(csv_row, "cost_centre"),
|
||||
"assigned_route": _read(csv_row, "assigned_route"),
|
||||
"vehicle_category": _read(csv_row, "vehicle_category"),
|
||||
"vehicle_brand": _read(csv_row, "vehicle_brand"),
|
||||
"fuel_100km": _read_num(csv_row, "fuel_100km"),
|
||||
"depot_address": _read(csv_row, "depot_address"),
|
||||
}
|
||||
if not is_placeholder:
|
||||
proposed["driver_name"] = driver_raw or None
|
||||
if not is_placeholder and driver_raw:
|
||||
proposed["driver_name"] = driver_raw
|
||||
|
||||
# Drop None values — no point sending a NULL to overwrite another NULL
|
||||
# Drop None values — no point sending NULL to overwrite NULL.
|
||||
proposed = {k: v for k, v in proposed.items() if v is not None}
|
||||
|
||||
if not only_null or db_row is None:
|
||||
return proposed
|
||||
|
||||
# only_null: drop any column that already has a non-null value in the DB
|
||||
return {
|
||||
k: v for k, v in proposed.items()
|
||||
if db_row.get(k) is None
|
||||
}
|
||||
# only_null: drop any column that already has a non-null value in the DB.
|
||||
return {k: v for k, v in proposed.items() if db_row.get(k) is None}
|
||||
|
||||
|
||||
def print_diff(imei: str, updates: dict[str, object], db_row: dict | None) -> None:
|
||||
|
|
@ -148,12 +175,31 @@ def print_diff(imei: str, updates: dict[str, object], db_row: dict | None) -> No
|
|||
print(f"\n IMEI {imei}:")
|
||||
for col, new_val in sorted(updates.items()):
|
||||
old_val = db.get(col)
|
||||
if old_val != new_val:
|
||||
if str(old_val) != str(new_val):
|
||||
print(f" {col:<20} {str(old_val):<30} → {new_val}")
|
||||
|
||||
|
||||
def run(apply: bool, only_null: bool, filter_imei: str | None) -> None:
|
||||
csv_rows = load_csv()
|
||||
def _set_clause(col: str) -> str:
|
||||
"""SQL fragment for `col = ...` honouring per-column casts."""
|
||||
if col in _TIMESTAMPTZ_COLS:
|
||||
return f"{col} = COALESCE(%s::TIMESTAMPTZ, {col})"
|
||||
if col in _NUMERIC_COLS:
|
||||
# %s already a float; no NULLIF dance needed.
|
||||
return f"{col} = COALESCE(%s::NUMERIC, {col})"
|
||||
return f"{col} = COALESCE(NULLIF(%s, ''), {col})"
|
||||
|
||||
|
||||
def _placeholder(col: str) -> str:
|
||||
"""SQL fragment for a single VALUES placeholder honouring per-column casts."""
|
||||
if col in _TIMESTAMPTZ_COLS:
|
||||
return "%s::TIMESTAMPTZ"
|
||||
if col in _NUMERIC_COLS:
|
||||
return "%s::NUMERIC"
|
||||
return "%s"
|
||||
|
||||
|
||||
def run(apply: bool, only_null: bool, filter_imei: str | None, csv_path: Path) -> None:
|
||||
csv_rows = load_csv(csv_path)
|
||||
db_rows = load_db_devices()
|
||||
|
||||
if filter_imei:
|
||||
|
|
@ -168,12 +214,10 @@ def run(apply: bool, only_null: bool, filter_imei: str | None) -> None:
|
|||
with conn.cursor() as cur:
|
||||
for imei, csv_row in csv_rows.items():
|
||||
db_row = db_rows.get(imei)
|
||||
|
||||
updates = build_update(csv_row, db_row, only_null)
|
||||
|
||||
if not updates:
|
||||
# Either an "Identification" placeholder or nothing to change
|
||||
driver_raw = (csv_row.get("Driver Name") or "").strip().lower()
|
||||
driver_raw = (_read(csv_row, "driver_name") or "").lower()
|
||||
if driver_raw == "identification":
|
||||
skipped += 1
|
||||
else:
|
||||
|
|
@ -181,20 +225,15 @@ def run(apply: bool, only_null: bool, filter_imei: str | None) -> None:
|
|||
continue
|
||||
|
||||
if db_row is None:
|
||||
# Device not yet synced from API — insert a stub row now so
|
||||
# Device not yet synced from API — insert a stub row so
|
||||
# incoming alarms / positions don't trip the FK constraint.
|
||||
print(f"\n [NEW] IMEI {imei}:")
|
||||
for col, new_val in sorted(updates.items()):
|
||||
print(f" {col:<20} → {new_val}")
|
||||
if apply:
|
||||
cols = ["imei"] + list(updates.keys())
|
||||
vals = [imei] + [str(v) if v is not None else None for v in updates.values()]
|
||||
placeholders = []
|
||||
for col in cols:
|
||||
if col in ("activation_time", "expiration"):
|
||||
placeholders.append("%s::TIMESTAMPTZ")
|
||||
else:
|
||||
placeholders.append("%s")
|
||||
vals = [imei] + list(updates.values())
|
||||
placeholders = ["%s"] + [_placeholder(c) for c in updates.keys()]
|
||||
cur.execute(
|
||||
f"INSERT INTO tracksolid.devices ({', '.join(cols)}) "
|
||||
f"VALUES ({', '.join(placeholders)}) "
|
||||
|
|
@ -207,20 +246,10 @@ def run(apply: bool, only_null: bool, filter_imei: str | None) -> None:
|
|||
print_diff(imei, updates, db_row)
|
||||
|
||||
if apply:
|
||||
set_clauses = []
|
||||
params = []
|
||||
for col, val in updates.items():
|
||||
if col in ("activation_time", "expiration"):
|
||||
set_clauses.append(f"{col} = COALESCE(%s::TIMESTAMPTZ, {col})")
|
||||
else:
|
||||
set_clauses.append(
|
||||
f"{col} = COALESCE(NULLIF(%s, ''), {col})"
|
||||
)
|
||||
params.append(str(val) if val is not None else None)
|
||||
|
||||
set_clauses = [_set_clause(c) for c in updates.keys()]
|
||||
params = list(updates.values())
|
||||
set_clauses.append("updated_at = NOW()")
|
||||
params.append(imei)
|
||||
|
||||
cur.execute(
|
||||
f"UPDATE tracksolid.devices SET {', '.join(set_clauses)} WHERE imei = %s",
|
||||
params,
|
||||
|
|
@ -234,7 +263,7 @@ def run(apply: bool, only_null: bool, filter_imei: str | None) -> None:
|
|||
print(f" {mode} COMPLETE")
|
||||
print(f"{'='*60}")
|
||||
print(f" Would update / updated : {updated}")
|
||||
print(f" Would insert / inserted: {inserted}")
|
||||
print(f" Would insert / inserted : {inserted}")
|
||||
print(f" No change needed : {no_change}")
|
||||
print(f" Skipped (Identification): {skipped}")
|
||||
if not apply:
|
||||
|
|
@ -242,10 +271,19 @@ def run(apply: bool, only_null: bool, filter_imei: str | None) -> None:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Import driver/vehicle details from CSV into tracksolid.devices")
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Import driver/vehicle details from CSV into tracksolid.devices"
|
||||
)
|
||||
parser.add_argument("--apply", action="store_true", help="Write changes to DB (default: dry-run)")
|
||||
parser.add_argument("--only-null", action="store_true", help="Only update fields currently NULL in the DB")
|
||||
parser.add_argument("--imei", default=None, help="Limit to a single IMEI")
|
||||
parser.add_argument("--csv", default=str(DEFAULT_CSV_PATH),
|
||||
help=f"Path to the CSV (default: {DEFAULT_CSV_PATH.name})")
|
||||
args = parser.parse_args()
|
||||
|
||||
run(apply=args.apply, only_null=args.only_null, filter_imei=args.imei)
|
||||
csv_path = Path(args.csv)
|
||||
if not csv_path.exists():
|
||||
log.error("CSV file not found: %s", csv_path)
|
||||
raise SystemExit(1)
|
||||
|
||||
run(apply=args.apply, only_null=args.only_null, filter_imei=args.imei, csv_path=csv_path)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ REVISIONS (QA-Verified):
|
|||
Tracksolid sub-accounts. sync_devices, poll_live_positions
|
||||
and poll_parking now iterate every target in TRACKSOLID_TARGETS
|
||||
and dedupe/scope per-target before writing.
|
||||
[FIX-M20] Trip enrichment: poll_trips now backfills start_geom/end_geom/
|
||||
route_geom/waypoints_count from position_history at insert
|
||||
time, extracts idleSecond, reverse-geocodes start/end addresses
|
||||
(Nominatim), and caches vehicle_plate from devices. Closes the
|
||||
NULL-column gaps that were inherent to jimi.device.track.mileage
|
||||
(it does not return coordinates, idle, or trip sequence).
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"""
|
||||
|
||||
|
|
@ -63,6 +69,7 @@ from ts_shared_rev import (
|
|||
clean_int,
|
||||
clean_ts,
|
||||
get_logger,
|
||||
reverse_geocode,
|
||||
safe_task,
|
||||
setup_shutdown,
|
||||
)
|
||||
|
|
@ -259,6 +266,57 @@ def poll_live_positions():
|
|||
|
||||
# ── 3. Trip Reports (Every 15m) ───────────────────────────────────────────────
|
||||
|
||||
# [FIX-M20] Migration 09 added route_geom, start/end_address, vehicle_plate,
|
||||
# waypoints_count to tracksolid.trips. poll_trips now enriches every poll-
|
||||
# ingested trip from position_history (start/end fix + LineString polyline)
|
||||
# and reverse-geocodes the endpoints, since jimi.device.track.mileage does
|
||||
# not return coordinates. ON CONFLICT preserves webhook-supplied data when
|
||||
# /pushtripreport later delivers native coords.
|
||||
|
||||
# Per-trip enrichment from position_history. Four readable scalar subqueries
|
||||
# rather than a tighter CTE — runs sub-ms each given the (imei, gps_time) PK,
|
||||
# and the readable form survives the edge case where start_time is just
|
||||
# before the first available fix in the window (single CTE bounded by
|
||||
# BETWEEN would return NULL there).
|
||||
_ENRICH_QUERY = """
|
||||
SELECT
|
||||
(SELECT geom FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time >= %s
|
||||
ORDER BY gps_time ASC LIMIT 1) AS start_geom,
|
||||
(SELECT ST_Y(geom) FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time >= %s
|
||||
ORDER BY gps_time ASC LIMIT 1) AS start_lat,
|
||||
(SELECT ST_X(geom) FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time >= %s
|
||||
ORDER BY gps_time ASC LIMIT 1) AS start_lng,
|
||||
(SELECT geom FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time <= %s
|
||||
ORDER BY gps_time DESC LIMIT 1) AS end_geom,
|
||||
(SELECT ST_Y(geom) FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time <= %s
|
||||
ORDER BY gps_time DESC LIMIT 1) AS end_lat,
|
||||
(SELECT ST_X(geom) FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time <= %s
|
||||
ORDER BY gps_time DESC LIMIT 1) AS end_lng,
|
||||
(SELECT ST_MakeLine(geom ORDER BY gps_time)
|
||||
FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time BETWEEN %s AND %s
|
||||
AND geom IS NOT NULL) AS route_geom,
|
||||
(SELECT COUNT(*) FROM tracksolid.position_history
|
||||
WHERE imei = %s AND gps_time BETWEEN %s AND %s) AS waypoints_count
|
||||
"""
|
||||
|
||||
|
||||
def _load_plates_cache(cur) -> dict[str, str]:
|
||||
"""Build {imei: vehicle_number} for active devices once per poll cycle."""
|
||||
cur.execute("""
|
||||
SELECT imei, vehicle_number
|
||||
FROM tracksolid.devices
|
||||
WHERE enabled_flag = 1 AND vehicle_number IS NOT NULL
|
||||
""")
|
||||
return {imei: plate for imei, plate in cur.fetchall()}
|
||||
|
||||
|
||||
def poll_trips():
|
||||
t0 = time.time()
|
||||
token, imeis = get_token(), get_active_imeis()
|
||||
|
|
@ -270,6 +328,8 @@ def poll_trips():
|
|||
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
plates = _load_plates_cache(cur)
|
||||
|
||||
for i in range(0, len(imeis), 50):
|
||||
batch = imeis[i:i+50]
|
||||
resp = api_post("jimi.device.track.mileage", {
|
||||
|
|
@ -288,20 +348,67 @@ def poll_trips():
|
|||
# Divide by 1000 to store as distance_km.
|
||||
raw_dist = clean_num(t.get("distance"))
|
||||
dist_km = round(raw_dist / 1000.0, 4) if raw_dist is not None else None
|
||||
|
||||
imei = t.get("imei")
|
||||
trip_start = clean_ts(t.get("startTime"))
|
||||
trip_end = clean_ts(t.get("endTime"))
|
||||
idle_s = clean_int(t.get("idleSecond"))
|
||||
|
||||
# [FIX-M20] Enrich from position_history. trip_start/end
|
||||
# may be None (rare malformed payload) — skip enrichment
|
||||
# in that case so we still capture the row.
|
||||
start_geom = end_geom = route_geom = None
|
||||
start_lat = start_lng = end_lat = end_lng = None
|
||||
waypoints_count = 0
|
||||
if trip_start and trip_end:
|
||||
cur.execute(_ENRICH_QUERY, (
|
||||
imei, trip_start, # start_geom
|
||||
imei, trip_start, # start_lat
|
||||
imei, trip_start, # start_lng
|
||||
imei, trip_end, # end_geom
|
||||
imei, trip_end, # end_lat
|
||||
imei, trip_end, # end_lng
|
||||
imei, trip_start, trip_end, # route_geom
|
||||
imei, trip_start, trip_end, # waypoints_count
|
||||
))
|
||||
(start_geom, start_lat, start_lng,
|
||||
end_geom, end_lat, end_lng,
|
||||
route_geom, waypoints_count) = cur.fetchone()
|
||||
|
||||
start_address = reverse_geocode(start_lat, start_lng)
|
||||
end_address = reverse_geocode(end_lat, end_lng)
|
||||
vehicle_plate = plates.get(imei)
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO tracksolid.trips (
|
||||
imei, start_time, end_time, distance_km,
|
||||
avg_speed_kmh, max_speed_kmh, driving_time_s, source
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, 'poll')
|
||||
avg_speed_kmh, max_speed_kmh, driving_time_s, idle_time_s,
|
||||
start_geom, end_geom, route_geom, waypoints_count,
|
||||
start_address, end_address, vehicle_plate, source
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s,
|
||||
%s, %s, %s, 'poll')
|
||||
ON CONFLICT (imei, start_time) DO UPDATE SET
|
||||
end_time = EXCLUDED.end_time,
|
||||
distance_km = EXCLUDED.distance_km,
|
||||
max_speed_kmh = COALESCE(EXCLUDED.max_speed_kmh, tracksolid.trips.max_speed_kmh),
|
||||
driving_time_s = COALESCE(EXCLUDED.driving_time_s, tracksolid.trips.driving_time_s)
|
||||
driving_time_s = COALESCE(EXCLUDED.driving_time_s, tracksolid.trips.driving_time_s),
|
||||
idle_time_s = COALESCE(EXCLUDED.idle_time_s, tracksolid.trips.idle_time_s),
|
||||
start_geom = COALESCE(tracksolid.trips.start_geom, EXCLUDED.start_geom),
|
||||
end_geom = COALESCE(EXCLUDED.end_geom, tracksolid.trips.end_geom),
|
||||
route_geom = COALESCE(EXCLUDED.route_geom, tracksolid.trips.route_geom),
|
||||
waypoints_count = EXCLUDED.waypoints_count,
|
||||
start_address = COALESCE(tracksolid.trips.start_address, EXCLUDED.start_address),
|
||||
end_address = COALESCE(EXCLUDED.end_address, tracksolid.trips.end_address),
|
||||
vehicle_plate = COALESCE(EXCLUDED.vehicle_plate, tracksolid.trips.vehicle_plate)
|
||||
""", (
|
||||
t.get("imei"), clean_ts(t.get("startTime")), clean_ts(t.get("endTime")),
|
||||
dist_km, clean_num(t.get("avgSpeed")),
|
||||
clean_num(t.get("maxSpeed")), clean_int(t.get("runTimeSecond"))
|
||||
imei, trip_start, trip_end, dist_km,
|
||||
clean_num(t.get("avgSpeed")),
|
||||
clean_num(t.get("maxSpeed")),
|
||||
clean_int(t.get("runTimeSecond")),
|
||||
idle_s,
|
||||
start_geom, end_geom, route_geom, waypoints_count,
|
||||
start_address, end_address, vehicle_plate,
|
||||
))
|
||||
cur.execute("RELEASE SAVEPOINT sp")
|
||||
inserted += cur.rowcount
|
||||
|
|
|
|||
5
new_feature.txt
Normal file
5
new_feature.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
What I'd skip (for now)
|
||||
|
||||
- ML/forecasting sections until 6+ months of trip data exist — with 3 recorded trips, anything is noise.
|
||||
- Temperature, fuel-theft, OBD-RPM queries — already gated behind webhook registration in §8; duplicating them in §3 just adds clutter.
|
||||
- Route-optimisation queries — that's a separate product, not an analytics-doc addition.
|
||||
0
push_webhook.md
Normal file
0
push_webhook.md
Normal file
|
|
@ -31,6 +31,8 @@ MIGRATIONS = [
|
|||
"05_enhancement_migration.sql", # new tables, OBD columns, dwh_gold expansion
|
||||
"06_business_analytics_migration.sql", # ops schema, dispatch_log, assigned_city
|
||||
"07_analytics_views.sql", # Grafana-facing views in tracksolid.*
|
||||
"08_analytics_config.sql", # ops.cost_rates, ops.kpi_targets + seed data
|
||||
"09_trips_enrichment.sql", # trips.route_geom + addresses + plate + v_trips_enriched
|
||||
]
|
||||
|
||||
# ── Tables that must exist before the service is allowed to start ─────────────
|
||||
|
|
|
|||
377
tracksolid_analytics_pipeline.txt
Normal file
377
tracksolid_analytics_pipeline.txt
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
pgcli "postgres://postgres:U1pm3f5SX34DXkHoW6aKFsBHOlMA9binDPNG4aT0FAcg7AubEvYm0e6kU2dZiYrR@stage.rahamafresh.com:5433/tracksolid_db"
|
||||
|
||||
postgresql://postgres:U1pm3f5SX34DXkHoW6aKFsBHOlMA9binDPNG4aT0FAcg7AubEvYm0e6kU2dZiYrR@timescale_db-bo3nov2ija7g8wn9b1g2paxs-104717464280:5432/tracksolid_db
|
||||
|
||||
---- CURRENT LIVE POSITIONS ------- All devices with a position in the last hour ----
|
||||
SELECT
|
||||
d.device_name,
|
||||
d.mc_type,
|
||||
ROUND(lp.lat::numeric, 5) AS lat,
|
||||
ROUND(lp.lng::numeric, 5) AS lng,
|
||||
lp.speed,
|
||||
lp.acc_status,
|
||||
lp.gps_signal,
|
||||
lp.gps_num AS satellites,
|
||||
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_fix_eat,
|
||||
ROUND(EXTRACT(EPOCH FROM (now() - lp.gps_time)) / 60.0, 0) AS mins_ago
|
||||
FROM tracksolid.live_positions lp
|
||||
JOIN tracksolid.devices d ON d.imei = lp.imei
|
||||
WHERE lp.gps_time > now() - interval '1 hour'
|
||||
ORDER BY lp.gps_time DESC;
|
||||
|
||||
----- All 19 live positions — current snapshot ----
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
d.mc_type,
|
||||
ROUND(lp.lat::numeric, 5) AS lat,
|
||||
ROUND(lp.lng::numeric, 5) AS lng,
|
||||
lp.speed,
|
||||
lp.acc_status,
|
||||
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_fix_eat,
|
||||
ROUND(EXTRACT(EPOCH FROM (now() - lp.gps_time)) / 3600.0, 1) AS hours_ago,
|
||||
lp.current_mileage AS odometer_km
|
||||
FROM tracksolid.live_positions lp
|
||||
JOIN tracksolid.devices d ON d.imei = lp.imei
|
||||
ORDER BY lp.gps_time DESC;
|
||||
|
||||
-------- Devices with no position (silent fleet) -----
|
||||
|
||||
SELECT d.imei, d.device_name, d.mc_type, d.sim, d.expiration::date
|
||||
FROM tracksolid.devices d
|
||||
LEFT JOIN tracksolid.live_positions lp ON lp.imei = d.imei
|
||||
WHERE lp.imei IS NULL
|
||||
ORDER BY d.mc_type, d.device_name;
|
||||
|
||||
---- Vehicles currently moving (ACC on OR speed > 0)
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
lp.speed,
|
||||
lp.acc_status,
|
||||
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_fix_eat
|
||||
FROM tracksolid.live_positions lp
|
||||
JOIN tracksolid.devices d ON d.imei = lp.imei
|
||||
WHERE lp.speed > 0 OR lp.acc_status = '1'
|
||||
ORDER BY lp.speed DESC;
|
||||
|
||||
---- Vehicles in Uganda -----
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
ROUND(lp.lat::numeric, 4) AS lat,
|
||||
ROUND(lp.lng::numeric, 4) AS lng,
|
||||
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_fix_eat
|
||||
FROM tracksolid.live_positions lp
|
||||
JOIN tracksolid.devices d ON d.imei = lp.imei
|
||||
WHERE lp.lat NOT BETWEEN -5.0 AND 5.0
|
||||
OR lp.lng NOT BETWEEN 33.9 AND 42.0;
|
||||
|
||||
---- Vehicles in Kenya -----
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
ROUND(lp.lat::numeric, 4) AS lat,
|
||||
ROUND(lp.lng::numeric, 4) AS lng,
|
||||
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_fix_eat
|
||||
FROM tracksolid.live_positions lp
|
||||
JOIN tracksolid.devices d ON d.imei = lp.imei
|
||||
WHERE lp.lat BETWEEN -5.0 AND 5.0
|
||||
OR lp.lng BETWEEN 33.9 AND 42.0;
|
||||
|
||||
|
||||
========== TRIPS AND MOVEMENT ======
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
d.vehicle_number,
|
||||
d.driver_name,
|
||||
COUNT(*) AS trips,
|
||||
ROUND(SUM(t.distance_km)::numeric, 2) AS total_km,
|
||||
ROUND(AVG(t.avg_speed_kmh)::numeric, 1) AS avg_speed_kmh,
|
||||
MAX(t.max_speed_kmh) AS top_speed_kmh,
|
||||
ROUND(SUM(t.driving_time_s) / 3600.0, 2) AS drive_hours,
|
||||
ROUND(SUM(t.idle_time_s) / 3600.0, 2) AS idle_hours,
|
||||
MIN(t.start_time AT TIME ZONE 'Africa/Nairobi') AS day_start,
|
||||
MAX(t.end_time AT TIME ZONE 'Africa/Nairobi') AS day_end
|
||||
FROM tracksolid.trips t
|
||||
JOIN tracksolid.devices d ON d.imei = t.imei
|
||||
WHERE t.start_time >= CURRENT_DATE AT TIME ZONE 'Africa/Nairobi'
|
||||
GROUP BY d.device_name, d.vehicle_number, d.driver_name
|
||||
ORDER BY total_km DESC;
|
||||
|
||||
------ trips summary LAST 24 HOURS per vehicle -------
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
t.start_time AT TIME ZONE 'Africa/Nairobi' AS start_eat,
|
||||
t.end_time AT TIME ZONE 'Africa/Nairobi' AS end_eat,
|
||||
ROUND(t.distance_km::numeric / 1000.0, 2) AS distance_km,
|
||||
t.avg_speed_kmh,
|
||||
t.max_speed_kmh,
|
||||
ROUND(t.driving_time_s / 60.0, 0) AS drive_mins,
|
||||
ROUND(t.idle_time_s / 60.0, 0) AS idle_mins,
|
||||
t.source
|
||||
FROM tracksolid.trips t
|
||||
JOIN tracksolid.devices d ON d.imei = t.imei
|
||||
WHERE t.start_time > now() - interval '24 hours'
|
||||
ORDER BY t.start_time DESC;
|
||||
|
||||
------- Fleet utilisation rate per vehicle (today)
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
d.driver_name,
|
||||
ROUND(SUM(t.driving_time_s) / 3600.0, 2) AS drive_hours,
|
||||
ROUND(SUM(t.idle_time_s) / 3600.0, 2) AS idle_hours,
|
||||
LEAST(ROUND(SUM(t.driving_time_s + COALESCE(t.idle_time_s, 0)) / (12.0 * 3600) * 100, 1), 100) AS utilisation_pct
|
||||
FROM tracksolid.trips t
|
||||
JOIN tracksolid.devices d ON d.imei = t.imei
|
||||
WHERE t.start_time >= CURRENT_DATE AT TIME ZONE 'Africa/Nairobi'
|
||||
GROUP BY d.device_name, d.driver_name
|
||||
ORDER BY utilisation_pct DESC;
|
||||
|
||||
KDK 829A GP | | 1.87 | | 15.5
|
||||
FRED KMGW 538W HULETI | | 1.65 | | 13.7
|
||||
X3-63282 | | 1.25 | | 10.4
|
||||
|
||||
-----Tracking to this point of the day ----
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
d.driver_name,
|
||||
ROUND(SUM(t.driving_time_s) / 3600.0, 2) AS drive_hours,
|
||||
ROUND(SUM(t.idle_time_s) / 3600.0, 2) AS idle_hours,
|
||||
LEAST(ROUND(
|
||||
SUM(t.driving_time_s + COALESCE(t.idle_time_s, 0))
|
||||
/ (EXTRACT(EPOCH FROM (
|
||||
LEAST(now(), CURRENT_DATE::timestamp AT TIME ZONE 'Africa/Nairobi' + interval '19 hours 30 minutes')
|
||||
- (CURRENT_DATE::timestamp AT TIME ZONE 'Africa/Nairobi' + interval '7 hours 30 minutes')
|
||||
))) * 100
|
||||
, 1), 100) AS utilisation_pct_so_far
|
||||
FROM tracksolid.trips t
|
||||
JOIN tracksolid.devices d ON d.imei = t.imei
|
||||
WHERE t.start_time >= (CURRENT_DATE::timestamp AT TIME ZONE 'Africa/Nairobi' + interval '7 hours 30 minutes')
|
||||
GROUP BY d.device_name, d.driver_name
|
||||
ORDER BY utilisation_pct_so_far DESC;
|
||||
|
||||
|
||||
------ Vehicles that have not moved today -------
|
||||
|
||||
SELECT d.device_name, d.mc_type, d.driver_name
|
||||
FROM tracksolid.devices d
|
||||
LEFT JOIN tracksolid.trips t
|
||||
ON t.imei = d.imei
|
||||
AND t.start_time >= CURRENT_DATE AT TIME ZONE 'Africa/Nairobi'
|
||||
WHERE t.imei IS NULL
|
||||
ORDER BY d.device_name;
|
||||
|
||||
------ Distance per driver — last 30 days
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
d.driver_name,
|
||||
COUNT(DISTINCT DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')) AS active_days,
|
||||
COUNT(*) AS total_trips,
|
||||
ROUND(SUM(t.distance_km / 1000.0)::numeric, 0) AS total_km,
|
||||
ROUND(AVG(t.distance_km / 1000.0)::numeric, 1) AS avg_km_per_trip,
|
||||
MAX(t.max_speed_kmh) AS top_speed_ever
|
||||
FROM tracksolid.trips t
|
||||
JOIN tracksolid.devices d ON d.imei = t.imei
|
||||
WHERE t.start_time > now() - interval '30 days'
|
||||
GROUP BY d.device_name, d.driver_name
|
||||
ORDER BY total_km DESC;
|
||||
|
||||
|
||||
====== ALL ALARMS =====
|
||||
|
||||
----Alarms in 24 hours ----
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
a.alarm_type,
|
||||
a.alarm_name,
|
||||
a.alarm_time AT TIME ZONE 'Africa/Nairobi' AS alarm_time_eat,
|
||||
ROUND(a.lat::numeric, 5) AS lat,
|
||||
ROUND(a.lng::numeric, 5) AS lng,
|
||||
a.speed,
|
||||
a.severity,
|
||||
a.acknowledged_at
|
||||
FROM tracksolid.alarms a
|
||||
JOIN tracksolid.devices d ON d.imei = a.imei
|
||||
WHERE a.alarm_time > now() - interval '24 hours'
|
||||
ORDER BY a.alarm_time DESC;
|
||||
|
||||
----Alarms in 7 Days ----
|
||||
SELECT
|
||||
a.alarm_name,
|
||||
a.alarm_type,
|
||||
COUNT(*) AS occurrences,
|
||||
COUNT(DISTINCT a.imei) AS devices_affected,
|
||||
MAX(a.alarm_time AT TIME ZONE 'Africa/Nairobi') AS last_seen_eat
|
||||
FROM tracksolid.alarms a
|
||||
WHERE a.alarm_time > now() - interval '7 days'
|
||||
GROUP BY a.alarm_name, a.alarm_type
|
||||
ORDER BY occurrences DESC;
|
||||
|
||||
------ unacknowledged alarms ------
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
a.alarm_name,
|
||||
a.alarm_time AT TIME ZONE 'Africa/Nairobi' AS alarm_time_eat,
|
||||
ROUND(EXTRACT(EPOCH FROM (now() - a.alarm_time)) / 3600.0, 1) AS hours_open
|
||||
FROM tracksolid.alarms a
|
||||
JOIN tracksolid.devices d ON d.imei = a.imei
|
||||
WHERE a.acknowledged_at IS NULL
|
||||
ORDER BY a.alarm_time DESC;
|
||||
|
||||
------ acknowledged alarms ------
|
||||
|
||||
UPDATE tracksolid.alarms
|
||||
SET acknowledged_at = now(),
|
||||
acknowledged_by = 'operator_name'
|
||||
WHERE id = <alarm_id>;
|
||||
|
||||
===========
|
||||
|
||||
------ Position history by source — counts ----
|
||||
|
||||
SELECT
|
||||
source,
|
||||
COUNT(*) AS fixes,
|
||||
MIN(gps_time AT TIME ZONE 'Africa/Nairobi') AS earliest,
|
||||
MAX(gps_time AT TIME ZONE 'Africa/Nairobi') AS latest
|
||||
FROM tracksolid.position_history
|
||||
GROUP BY source;
|
||||
|
||||
--------- Route replay for a specific vehicle — last 24 hours
|
||||
|
||||
device = name <'FRED KMGW 538W HULETI'>
|
||||
|
||||
SELECT
|
||||
ph.gps_time AT TIME ZONE 'Africa/Nairobi' AS gps_time_eat,
|
||||
ROUND(ph.lat::numeric, 5) AS lat,
|
||||
ROUND(ph.lng::numeric, 5) AS lng,
|
||||
ph.speed,
|
||||
ph.direction,
|
||||
ph.acc_status,
|
||||
ph.source
|
||||
FROM tracksolid.position_history ph
|
||||
JOIN tracksolid.devices d ON d.imei = ph.imei
|
||||
WHERE d.device_name = <'FRED KMGW 538W HULETI'>
|
||||
AND ph.gps_time > now() - interval '24 hours'
|
||||
ORDER BY ph.gps_time ASC;
|
||||
|
||||
|
||||
-------- Fix density per device — last 24 hours ---------
|
||||
|
||||
SELECT
|
||||
d.device_name,
|
||||
COUNT(*) AS total_fixes,
|
||||
COUNT(*) FILTER (WHERE ph.source = 'poll') AS poll_fixes,
|
||||
COUNT(*) FILTER (WHERE ph.source = 'track_list') AS track_list_fixes,
|
||||
MIN(ph.gps_time AT TIME ZONE 'Africa/Nairobi') AS first_fix,
|
||||
MAX(ph.gps_time AT TIME ZONE 'Africa/Nairobi') AS last_fix
|
||||
FROM tracksolid.position_history ph
|
||||
JOIN tracksolid.devices d ON d.imei = ph.imei
|
||||
WHERE ph.gps_time > now() - interval '24 hours'
|
||||
GROUP BY d.device_name
|
||||
ORDER BY total_fixes DESC;
|
||||
|
||||
### Device & Fleet Registry
|
||||
|
||||
-------- Full fleet — all 63 devices ------
|
||||
|
||||
SELECT
|
||||
device_name,
|
||||
mc_type,
|
||||
vehicle_number,
|
||||
driver_name,
|
||||
sim,
|
||||
ROUND(current_mileage_km::numeric, 0) AS odometer_km,
|
||||
expiration::date AS expires,
|
||||
enabled_flag
|
||||
FROM tracksolid.devices
|
||||
ORDER BY mc_type, device_name;
|
||||
|
||||
-------- Fleet by devices model ------
|
||||
|
||||
SELECT mc_type, COUNT(*) AS devices
|
||||
FROM tracksolid.devices
|
||||
GROUP BY mc_type ORDER BY devices DESC;
|
||||
|
||||
|
||||
------ fleet by odometer readings -----
|
||||
|
||||
SELECT
|
||||
device_name,
|
||||
mc_type,
|
||||
sim,
|
||||
ROUND(current_mileage_km::numeric, 0) AS odometer_km,
|
||||
expiration::date AS expires
|
||||
FROM tracksolid.devices
|
||||
WHERE current_mileage_km IS NOT NULL AND current_mileage_km > 0
|
||||
ORDER BY current_mileage_km DESC
|
||||
LIMIT 15;
|
||||
|
||||
---- fleet blank driver sim vehicle number blanks ----
|
||||
|
||||
SELECT device_name, mc_type, sim
|
||||
FROM tracksolid.devices
|
||||
WHERE vehicle_number IS NULL OR vehicle_number = ''
|
||||
OR driver_name IS NULL OR driver_name = ''
|
||||
ORDER BY mc_type, device_name;
|
||||
|
||||
----- fleet by expiry dates ---
|
||||
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE expiration IS NULL) AS no_expiry_set,
|
||||
COUNT(*) FILTER (WHERE expiration < now()) AS already_expired,
|
||||
COUNT(*) FILTER (WHERE expiration BETWEEN now() AND now() + interval '90 days') AS expiring_90days,
|
||||
COUNT(*) FILTER (WHERE expiration > now() + interval '90 days') AS valid_long_term
|
||||
FROM tracksolid.devices;
|
||||
|
||||
|
||||
-----no of rows in db -----
|
||||
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM tracksolid.devices) AS devices,
|
||||
(SELECT COUNT(*) FROM tracksolid.live_positions) AS live_positions,
|
||||
(SELECT COUNT(*) FROM tracksolid.position_history) AS position_history,
|
||||
(SELECT COUNT(*) FROM tracksolid.trips) AS trips,
|
||||
(SELECT COUNT(*) FROM tracksolid.alarms) AS alarms,
|
||||
(SELECT COUNT(*) FROM tracksolid.parking_events) AS parking_events,
|
||||
(SELECT COUNT(*) FROM tracksolid.obd_readings) AS obd_readings,
|
||||
(SELECT COUNT(*) FROM tracksolid.device_events) AS device_events,
|
||||
(SELECT COUNT(*) FROM tracksolid.fuel_readings) AS fuel_readings,
|
||||
(SELECT COUNT(*) FROM tracksolid.temperature_readings) AS temperature_readings,
|
||||
(SELECT COUNT(*) FROM tracksolid.lbs_readings) AS lbs_readings,
|
||||
(SELECT COUNT(*) FROM tracksolid.ingestion_log) AS ingestion_log;
|
||||
|
||||
=====
|
||||
|
||||
SELECT 'devices' AS table_name, COUNT(*) FROM tracksolid.devices
|
||||
UNION ALL
|
||||
SELECT 'live_positions', COUNT(*) FROM tracksolid.live_positions
|
||||
UNION ALL
|
||||
SELECT 'position_history', COUNT(*) FROM tracksolid.position_history
|
||||
UNION ALL
|
||||
SELECT 'trips', COUNT(*) FROM tracksolid.trips
|
||||
UNION ALL
|
||||
SELECT 'alarms', COUNT(*) FROM tracksolid.alarms
|
||||
UNION ALL
|
||||
SELECT 'parking_events', COUNT(*) FROM tracksolid.parking_events
|
||||
UNION ALL
|
||||
SELECT 'obd_readings', COUNT(*) FROM tracksolid.obd_readings
|
||||
UNION ALL
|
||||
SELECT 'device_events', COUNT(*) FROM tracksolid.device_events
|
||||
UNION ALL
|
||||
SELECT 'fuel_readings', COUNT(*) FROM tracksolid.fuel_readings
|
||||
UNION ALL
|
||||
SELECT 'temperature_readings', COUNT(*) FROM tracksolid.temperature_readings
|
||||
UNION ALL
|
||||
SELECT 'lbs_readings', COUNT(*) FROM tracksolid.lbs_readings
|
||||
UNION ALL
|
||||
SELECT 'ingestion_log', COUNT(*) FROM tracksolid.ingestion_log
|
||||
ORDER BY table_name;
|
||||
297
tracksolid_extract.py
Normal file
297
tracksolid_extract.py
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tracksolid Pro - Device List Extractor
|
||||
Calls jimi.user.device.list and saves all vehicle/device data to CSV and JSON.
|
||||
|
||||
Uses the same signing approach as tracksolid_update.py (confirmed working):
|
||||
- POST as x-www-form-urlencoded
|
||||
- All parameter values cast to strings before signing
|
||||
|
||||
Usage:
|
||||
python3 tracksolid_extract.py
|
||||
python3 tracksolid_extract.py --target "Fireside Communications"
|
||||
python3 tracksolid_extract.py --format json
|
||||
python3 tracksolid_extract.py --format both
|
||||
|
||||
Environment variables (same .env file as tracksolid_update.py):
|
||||
TS_USER_ID - Your Tracksolid account username
|
||||
TS_USER_PWD_MD5 - MD5 hash of your password (lowercase)
|
||||
TS_APP_KEY - Your appKey from JIMI
|
||||
TS_APP_SECRET - Your appSecret from JIMI
|
||||
TS_API_URL - API base URL (defaults to EU node)
|
||||
TS_TARGET - Account to query (defaults to TS_USER_ID)
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
import pandas as pd
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# CONFIGURATION — reads from environment / same .env as the updater
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
CONFIG = {
|
||||
"user_id": os.getenv("TS_USER_ID", "Fireside Communications"),
|
||||
"user_pwd_md5": os.getenv("TS_USER_PWD_MD5", "81a1b005efd3596073e38efd8a2fd3fd"),
|
||||
"app_key": os.getenv("TS_APP_KEY", "8FB345B8693CCD00BB70D528C0D4019E"),
|
||||
"app_secret": os.getenv("TS_APP_SECRET", "3177c89993b446c6aced0d7c56375d2c"),
|
||||
"api_url": os.getenv("TS_API_URL", "https://eu-open.tracksolidpro.com/route/rest"),
|
||||
"target": os.getenv("TS_TARGET", ""), # account to query; defaults to user_id
|
||||
"expires_in": "7200",
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# LOGGING
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)-8s %(message)s",
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout),
|
||||
logging.FileHandler("tracksolid_extract.log", encoding="utf-8"),
|
||||
],
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# SIGNING UTILITIES (identical to tracksolid_update.py)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def utc_timestamp() -> str:
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def build_sign(params: dict, app_secret: str) -> str:
|
||||
sorted_keys = sorted(
|
||||
k for k in params
|
||||
if k != "sign" and params[k] is not None and str(params[k]).strip() != ""
|
||||
)
|
||||
param_string = "".join(f"{k}{params[k]}" for k in sorted_keys)
|
||||
raw_string = f"{app_secret}{param_string}{app_secret}"
|
||||
return hashlib.md5(raw_string.encode("utf-8")).hexdigest().upper()
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# TRACKSOLID CLIENT
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TracksolidClient:
|
||||
def __init__(self, cfg: dict):
|
||||
self.cfg = cfg
|
||||
self._token: str | None = None
|
||||
self._token_expires_at: float = 0.0
|
||||
self.session = requests.Session()
|
||||
|
||||
def _post(self, params: dict) -> dict:
|
||||
str_params = {
|
||||
k: str(v)
|
||||
for k, v in params.items()
|
||||
if v is not None and str(v).strip() != ""
|
||||
}
|
||||
str_params["sign"] = build_sign(str_params, self.cfg["app_secret"])
|
||||
|
||||
log.debug("POST %s params=%s", self.cfg["api_url"], str_params)
|
||||
resp = self.session.post(
|
||||
self.cfg["api_url"],
|
||||
data=str_params, # form-encoded — confirmed working
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
log.debug("Response: %s", json.dumps(data))
|
||||
return data
|
||||
|
||||
def _common_params(self, method: str) -> dict:
|
||||
return {
|
||||
"method": method,
|
||||
"timestamp": utc_timestamp(),
|
||||
"app_key": self.cfg["app_key"],
|
||||
"sign_method": "md5",
|
||||
"v": "1.0",
|
||||
"format": "json",
|
||||
}
|
||||
|
||||
def get_token(self) -> str:
|
||||
if self._token and time.time() < self._token_expires_at - 60:
|
||||
return self._token
|
||||
|
||||
log.info("Obtaining access token ...")
|
||||
params = self._common_params("jimi.oauth.token.get")
|
||||
params.update({
|
||||
"user_id": self.cfg["user_id"],
|
||||
"user_pwd_md5": self.cfg["user_pwd_md5"],
|
||||
"expires_in": self.cfg["expires_in"],
|
||||
})
|
||||
|
||||
data = self._post(params)
|
||||
if data.get("code") != 0:
|
||||
raise RuntimeError(
|
||||
f"Auth failed — code={data.get('code')} message={data.get('message')}"
|
||||
)
|
||||
|
||||
self._token = data["result"]["accessToken"]
|
||||
self._token_expires_at = time.time() + int(data["result"]["expiresIn"])
|
||||
log.info("Token acquired. Valid for %s seconds.", data["result"]["expiresIn"])
|
||||
return self._token
|
||||
|
||||
def get_device_list(self, target: str) -> list[dict]:
|
||||
"""
|
||||
Call jimi.user.device.list for the given target account.
|
||||
Returns the full list of device/vehicle records.
|
||||
"""
|
||||
log.info("Fetching device list for account: %s", target)
|
||||
token = self.get_token()
|
||||
|
||||
params = self._common_params("jimi.user.device.list")
|
||||
params["access_token"] = token
|
||||
params["target"] = target
|
||||
|
||||
data = self._post(params)
|
||||
|
||||
if data.get("code") != 0:
|
||||
raise RuntimeError(
|
||||
f"Device list failed — code={data.get('code')} message={data.get('message')}"
|
||||
)
|
||||
|
||||
devices = data.get("result", [])
|
||||
log.info("Retrieved %d devices.", len(devices))
|
||||
return devices
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# OUTPUT HELPERS
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Friendly column names for the CSV output
|
||||
COLUMN_RENAME = {
|
||||
"imei": "IMEI",
|
||||
"deviceName": "Device Name",
|
||||
"mcType": "Model",
|
||||
"mcTypeUseScope": "Vehicle Type",
|
||||
"sim": "SIM",
|
||||
"expiration": "Platform Expiry",
|
||||
"activationTime": "Activated",
|
||||
"reMark": "Remarks",
|
||||
"vehicleName": "Vehicle Name",
|
||||
"vehicleIcon": "Icon",
|
||||
"vehicleNumber": "License Plate",
|
||||
"vehicleModels": "Vehicle Model",
|
||||
"carFrame": "VIN",
|
||||
"driverName": "Driver Name",
|
||||
"driverPhone": "Driver Phone",
|
||||
"enabledFlag": "Active",
|
||||
"engineNumber": "Engine Number",
|
||||
"deviceGroupId": "Group ID",
|
||||
"deviceGroup": "Group",
|
||||
}
|
||||
|
||||
def save_csv(devices: list[dict], path: str):
|
||||
df = pd.DataFrame(devices)
|
||||
df.rename(columns=COLUMN_RENAME, inplace=True)
|
||||
# Put the most useful columns first
|
||||
priority = ["IMEI", "License Plate", "Driver Name", "Driver Phone",
|
||||
"Device Name", "Vehicle Model", "Vehicle Type", "Group",
|
||||
"SIM", "Platform Expiry", "Activated", "Active",
|
||||
"VIN", "Engine Number", "Remarks"]
|
||||
ordered = [c for c in priority if c in df.columns]
|
||||
rest = [c for c in df.columns if c not in ordered]
|
||||
df = df[ordered + rest]
|
||||
df.to_csv(path, index=False)
|
||||
log.info("CSV saved → %s (%d rows, %d columns)", path, len(df), len(df.columns))
|
||||
|
||||
|
||||
def save_json(devices: list[dict], path: str):
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(devices, f, indent=2, ensure_ascii=False)
|
||||
log.info("JSON saved → %s (%d records)", path, len(devices))
|
||||
|
||||
|
||||
def print_summary(devices: list[dict]):
|
||||
df = pd.DataFrame(devices)
|
||||
total = len(df)
|
||||
active = df["enabledFlag"].eq(1).sum() if "enabledFlag" in df.columns else "?"
|
||||
groups = df["deviceGroup"].nunique() if "deviceGroup" in df.columns else "?"
|
||||
with_plate = df["vehicleNumber"].notna().sum() if "vehicleNumber" in df.columns else "?"
|
||||
with_driver = df["driverName"].notna().sum() if "driverName" in df.columns else "?"
|
||||
|
||||
print()
|
||||
print("=" * 50)
|
||||
print(" DEVICE LIST SUMMARY")
|
||||
print("=" * 50)
|
||||
print(f" Total devices : {total}")
|
||||
print(f" Active : {active}")
|
||||
print(f" Device groups : {groups}")
|
||||
print(f" With plate no. : {with_plate}")
|
||||
print(f" With driver name : {with_driver}")
|
||||
print("=" * 50)
|
||||
|
||||
if "deviceGroup" in df.columns:
|
||||
print("\n Breakdown by group:")
|
||||
for group, count in df["deviceGroup"].value_counts().items():
|
||||
print(f" {group:<25} {count} devices")
|
||||
print()
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# MAIN
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Extract Tracksolid device list to CSV / JSON."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--target", default="",
|
||||
help="Account to query (default: same as TS_USER_ID / user_id in CONFIG)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--format", choices=["csv", "json", "both"], default="csv",
|
||||
help="Output format (default: csv)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out", default="",
|
||||
help="Output filename without extension (default: tracksolid_devices_YYYYMMDD_HHMMSS)."
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Resolve target account
|
||||
target = args.target or CONFIG["target"] or CONFIG["user_id"]
|
||||
|
||||
# Resolve output filename base
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
out_base = args.out or f"tracksolid_devices_{timestamp}"
|
||||
|
||||
# ── Fetch ──────────────────────────────────────────────────────────────────
|
||||
client = TracksolidClient(CONFIG)
|
||||
try:
|
||||
devices = client.get_device_list(target)
|
||||
except Exception as exc:
|
||||
log.error("Failed to fetch device list: %s", exc)
|
||||
sys.exit(1)
|
||||
|
||||
if not devices:
|
||||
log.warning("No devices returned for account: %s", target)
|
||||
sys.exit(0)
|
||||
|
||||
# ── Save ───────────────────────────────────────────────────────────────────
|
||||
if args.format in ("csv", "both"):
|
||||
save_csv(devices, f"{out_base}.csv")
|
||||
|
||||
if args.format in ("json", "both"):
|
||||
save_json(devices, f"{out_base}.json")
|
||||
|
||||
# ── Summary ────────────────────────────────────────────────────────────────
|
||||
print_summary(devices)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
56
tracksolid_ingestion_pipeline.txt
Normal file
56
tracksolid_ingestion_pipeline.txt
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
### Pipeline health — last hour (key check)
|
||||
|
||||
SELECT
|
||||
endpoint,
|
||||
COUNT(*) AS calls,
|
||||
SUM(rows_upserted) AS upserted,
|
||||
SUM(rows_inserted) AS inserted,
|
||||
ROUND(AVG(duration_ms)::numeric, 0) AS avg_ms,
|
||||
COUNT(*) FILTER (WHERE success = false) AS failures,
|
||||
MAX(run_at AT TIME ZONE 'Africa/Nairobi') AS last_call_eat
|
||||
FROM tracksolid.ingestion_log
|
||||
WHERE run_at > now() - interval '1 hour'
|
||||
GROUP BY endpoint
|
||||
ORDER BY calls DESC;
|
||||
|
||||
-------- Ingestion Pipeline Health
|
||||
|
||||
SELECT
|
||||
endpoint,
|
||||
COUNT(*) AS total_calls,
|
||||
SUM(rows_upserted) AS total_upserted,
|
||||
SUM(rows_inserted) AS total_inserted,
|
||||
ROUND(AVG(duration_ms)::numeric, 0) AS avg_ms,
|
||||
COUNT(*) FILTER (WHERE success = false) AS failures,
|
||||
MIN(run_at AT TIME ZONE 'Africa/Nairobi') AS first_call,
|
||||
MAX(run_at AT TIME ZONE 'Africa/Nairobi') AS last_call
|
||||
FROM tracksolid.ingestion_log
|
||||
GROUP BY endpoint
|
||||
ORDER BY total_calls DESC;
|
||||
|
||||
### Recent calls — last 20 entries
|
||||
|
||||
SELECT
|
||||
run_at AT TIME ZONE 'Africa/Nairobi' AS run_eat,
|
||||
endpoint,
|
||||
imei_count,
|
||||
rows_upserted,
|
||||
rows_inserted,
|
||||
duration_ms,
|
||||
success,
|
||||
error_message
|
||||
FROM tracksolid.ingestion_log
|
||||
ORDER BY run_at DESC
|
||||
LIMIT 20;
|
||||
|
||||
### Recent calls — FAILED CALLS entries
|
||||
|
||||
SELECT
|
||||
run_at AT TIME ZONE 'Africa/Nairobi' AS run_eat,
|
||||
endpoint,
|
||||
error_code,
|
||||
error_message
|
||||
FROM tracksolid.ingestion_log
|
||||
WHERE success = false
|
||||
ORDER BY run_at DESC;
|
||||
|
||||
359
tracksolid_update_v2.py
Normal file
359
tracksolid_update_v2.py
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tracksolid Pro - Bulk Vehicle Information Updater
|
||||
Updates vehicle details via the jimi.open.device.update API endpoint
|
||||
using data from the Fireside logistics CSV.
|
||||
|
||||
Signing approach taken directly from tspostman.py (confirmed working):
|
||||
- POST as x-www-form-urlencoded (NOT JSON)
|
||||
- All parameter values cast to strings before signing
|
||||
- expires_in passed as string '7200', not integer
|
||||
|
||||
Usage:
|
||||
python tracksolid_update.py [--dry-run] [--csv path/to/file.csv] [--limit N]
|
||||
|
||||
Environment variables (or edit CONFIG below):
|
||||
TS_USER_ID - Your Tracksolid account username
|
||||
TS_USER_PWD_MD5 - MD5 hash of your password (lowercase)
|
||||
TS_APP_KEY - Your appKey from JIMI
|
||||
TS_APP_SECRET - Your appSecret from JIMI
|
||||
TS_API_URL - API base URL (defaults to EU node)
|
||||
TS_CSV_PATH - Path to the logistics CSV
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
import pandas as pd
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# CONFIGURATION — edit here or set environment variables
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
CONFIG = {
|
||||
"user_id": os.getenv("TS_USER_ID", "Fireside Communications"),
|
||||
"user_pwd_md5": os.getenv("TS_USER_PWD_MD5", "81a1b005efd3596073e38efd8a2fd3fd"),
|
||||
"app_key": os.getenv("TS_APP_KEY", "8FB345B8693CCD00BB70D528C0D4019E"),
|
||||
"app_secret": os.getenv("TS_APP_SECRET", "3177c89993b446c6aced0d7c56375d2c"),
|
||||
# EU node confirmed for this account
|
||||
"api_url": os.getenv("TS_API_URL", "https://eu-open.tracksolidpro.com/route/rest"),
|
||||
"expires_in": "7200", # string, not int — matches tspostman.py
|
||||
"request_delay": 0.5, # seconds between API calls
|
||||
}
|
||||
|
||||
CSV_PATH = os.getenv("TS_CSV_PATH", "20260414_FS__Logistics_-_final_fixed.csv")
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# LOGGING
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)-8s %(message)s",
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout),
|
||||
logging.FileHandler("tracksolid_update.log", encoding="utf-8"),
|
||||
],
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# SIGNING UTILITIES (ported directly from tspostman.py)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def utc_timestamp() -> str:
|
||||
"""UTC time formatted as yyyy-MM-dd HH:mm:ss."""
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def build_sign(params: dict, app_secret: str) -> str:
|
||||
"""
|
||||
Tracksolid signing algorithm (matches tspostman.py exactly):
|
||||
1. Sort all param keys alphabetically, exclude 'sign' and empty values.
|
||||
2. Concatenate key+value pairs with no separators.
|
||||
3. Wrap with appSecret on both sides.
|
||||
4. MD5 -> UPPERCASE 32-char string.
|
||||
|
||||
All values must already be strings before calling this.
|
||||
"""
|
||||
sorted_keys = sorted(
|
||||
k for k in params
|
||||
if k != "sign" and params[k] is not None and str(params[k]).strip() != ""
|
||||
)
|
||||
param_string = "".join(f"{k}{params[k]}" for k in sorted_keys)
|
||||
raw_string = f"{app_secret}{param_string}{app_secret}"
|
||||
return hashlib.md5(raw_string.encode("utf-8")).hexdigest().upper()
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# TRACKSOLID CLIENT
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TracksolidClient:
|
||||
def __init__(self, cfg: dict):
|
||||
self.cfg = cfg
|
||||
self._token: str | None = None
|
||||
self._token_expires_at: float = 0.0
|
||||
self.session = requests.Session()
|
||||
# No Content-Type set here — requests sets it automatically to
|
||||
# application/x-www-form-urlencoded when data= is used (matching tspostman.py)
|
||||
|
||||
def _post(self, params: dict) -> dict:
|
||||
"""
|
||||
Sign and POST params using x-www-form-urlencoded encoding.
|
||||
Confirmed working approach from tspostman.py:
|
||||
- Cast all values to strings
|
||||
- Use data= (form-encoded), NOT json=
|
||||
"""
|
||||
str_params = {
|
||||
k: str(v)
|
||||
for k, v in params.items()
|
||||
if v is not None and str(v).strip() != ""
|
||||
}
|
||||
str_params["sign"] = build_sign(str_params, self.cfg["app_secret"])
|
||||
|
||||
log.debug("POST %s params=%s", self.cfg["api_url"], str_params)
|
||||
resp = self.session.post(
|
||||
self.cfg["api_url"],
|
||||
data=str_params, # form-encoded, NOT json=
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
log.debug("Response: %s", json.dumps(data))
|
||||
return data
|
||||
|
||||
def _common_params(self, method: str) -> dict:
|
||||
return {
|
||||
"method": method,
|
||||
"timestamp": utc_timestamp(),
|
||||
"app_key": self.cfg["app_key"],
|
||||
"sign_method": "md5",
|
||||
"v": "1.0",
|
||||
"format": "json",
|
||||
}
|
||||
|
||||
def get_token(self) -> str:
|
||||
"""Return a valid access token, fetching a new one only when needed."""
|
||||
if self._token and time.time() < self._token_expires_at - 60:
|
||||
log.debug("Reusing cached token.")
|
||||
return self._token
|
||||
|
||||
log.info("Obtaining new access token ...")
|
||||
params = self._common_params("jimi.oauth.token.get")
|
||||
params.update({
|
||||
"user_id": self.cfg["user_id"],
|
||||
"user_pwd_md5": self.cfg["user_pwd_md5"],
|
||||
"expires_in": self.cfg["expires_in"], # already a string per tspostman.py
|
||||
})
|
||||
|
||||
data = self._post(params)
|
||||
if data.get("code") != 0:
|
||||
raise RuntimeError(
|
||||
f"Auth failed — code={data.get('code')} message={data.get('message')}"
|
||||
)
|
||||
|
||||
self._token = data["result"]["accessToken"]
|
||||
self._token_expires_at = time.time() + int(data["result"]["expiresIn"])
|
||||
log.info("Token acquired. Valid for %s seconds.", data["result"]["expiresIn"])
|
||||
return self._token
|
||||
|
||||
def update_vehicle(self, imei: str, fields: dict, dry_run: bool = False) -> dict:
|
||||
"""
|
||||
Call jimi.open.device.update for one IMEI.
|
||||
fields: dict using local key names (see row_to_fields below).
|
||||
Returns the API response dict.
|
||||
"""
|
||||
if dry_run:
|
||||
log.info("[DRY-RUN] IMEI %s -> %s", imei, fields)
|
||||
return {"code": 0, "message": "dry-run"}
|
||||
|
||||
token = self.get_token()
|
||||
params = self._common_params("jimi.open.device.update")
|
||||
params["access_token"] = token
|
||||
params["imei"] = str(imei)
|
||||
|
||||
# Map local field names -> Tracksolid API field names
|
||||
api_field_map = {
|
||||
"license_plate": "vehicle_number",
|
||||
"vehicle_name": "vehicle_name",
|
||||
"vehicle_icon": "vehicle_icon",
|
||||
"driver_name": "driver_name",
|
||||
"driver_phone": "driver_phone",
|
||||
"vehicle_model": "vehicle_models",
|
||||
"vin": "carFrame",
|
||||
"engine_number": "engineNumber",
|
||||
"device_name": "device_name",
|
||||
"fuel_per_100": "oilWear",
|
||||
"sim": "sim",
|
||||
"remarks": "remarks",
|
||||
}
|
||||
for local_key, api_key in api_field_map.items():
|
||||
val = fields.get(local_key)
|
||||
if val:
|
||||
params[api_key] = str(val)
|
||||
|
||||
return self._post(params)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# CSV PARSING
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def clean(val) -> str | None:
|
||||
"""Return None for NaN/empty values, else a stripped string."""
|
||||
if pd.isna(val) or str(val).strip() in ("", "nan", "NaN"):
|
||||
return None
|
||||
s = str(val).strip()
|
||||
if s.endswith(".0") and s[:-2].lstrip("+-").isdigit():
|
||||
s = s[:-2]
|
||||
return s
|
||||
|
||||
|
||||
def row_to_fields(row: pd.Series) -> dict:
|
||||
"""Map one CSV row to local field names used by update_vehicle()."""
|
||||
return {
|
||||
"device_name": clean(row.get("device_name")),
|
||||
"vehicle_name": clean(row.get("vehicle_name")),
|
||||
"vehicle_icon": clean(row.get("vehicle_icon")),
|
||||
"license_plate": clean(row.get("vehicle_number")),
|
||||
"vehicle_model": clean(row.get("vehicle_models")),
|
||||
"driver_name": clean(row.get("driver_name")),
|
||||
"driver_phone": clean(row.get("driver_phone")),
|
||||
"sim": clean(row.get("sim")),
|
||||
"vin": clean(row.get("vin")),
|
||||
"engine_number": clean(row.get("engine_number")),
|
||||
"fuel_per_100": clean(row.get("fuel_100km")),
|
||||
"remarks": clean(row.get("remarks")),
|
||||
}
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# MAIN
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Bulk-update Tracksolid vehicle info from CSV."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run", action="store_true",
|
||||
help="Show what would be sent without making update API calls."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--csv", default=CSV_PATH,
|
||||
help=f"Path to the logistics CSV (default: {CSV_PATH})"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--limit", type=int, default=None,
|
||||
help="Only process the first N rows (useful for testing)."
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# ── Load CSV ───────────────────────────────────────────────────────────────
|
||||
csv_file = Path(args.csv)
|
||||
if not csv_file.exists():
|
||||
log.error("CSV file not found: %s", csv_file)
|
||||
sys.exit(1)
|
||||
|
||||
df = pd.read_csv(csv_file, dtype={"imei": str})
|
||||
log.info("Loaded %d rows from %s", len(df), csv_file)
|
||||
|
||||
if args.limit:
|
||||
df = df.head(args.limit)
|
||||
log.info("Limiting to first %d rows.", args.limit)
|
||||
|
||||
df = df[df["imei"].notna() & (df["imei"].str.strip() != "")]
|
||||
log.info("%d rows have a valid IMEI.", len(df))
|
||||
|
||||
if df.empty:
|
||||
log.warning("No rows to process. Exiting.")
|
||||
sys.exit(0)
|
||||
|
||||
# ── Initialise client & verify auth ───────────────────────────────────────
|
||||
client = TracksolidClient(CONFIG)
|
||||
|
||||
if not args.dry_run:
|
||||
try:
|
||||
client.get_token()
|
||||
except Exception as exc:
|
||||
log.error("Authentication failed: %s", exc)
|
||||
log.error("Check TS_USER_ID, TS_USER_PWD_MD5, TS_APP_KEY, TS_APP_SECRET.")
|
||||
sys.exit(1)
|
||||
|
||||
# ── Process rows ───────────────────────────────────────────────────────────
|
||||
results = []
|
||||
success = 0
|
||||
failed = 0
|
||||
skipped = 0
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
imei = str(row["imei"]).strip()
|
||||
fields = row_to_fields(row)
|
||||
|
||||
# Skip rows where every updatable field is empty
|
||||
if not any(fields.values()):
|
||||
log.warning("Row %d (IMEI %s): no updatable fields, skipping.", idx + 1, imei)
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
log.info(
|
||||
"Row %d/%d — IMEI: %s | Plate: %s | Driver: %s",
|
||||
idx + 1, len(df), imei,
|
||||
fields.get("license_plate") or "—",
|
||||
fields.get("driver_name") or "—",
|
||||
)
|
||||
|
||||
try:
|
||||
resp = client.update_vehicle(imei, fields, dry_run=args.dry_run)
|
||||
if resp.get("code") == 0:
|
||||
log.info(" OK")
|
||||
success += 1
|
||||
else:
|
||||
log.warning(
|
||||
" API error — code=%s message=%s",
|
||||
resp.get("code"), resp.get("message")
|
||||
)
|
||||
failed += 1
|
||||
|
||||
results.append({
|
||||
"imei": imei,
|
||||
**fields,
|
||||
"api_code": resp.get("code"),
|
||||
"api_message": resp.get("message"),
|
||||
})
|
||||
|
||||
except Exception as exc:
|
||||
log.error(" Exception for IMEI %s: %s", imei, exc)
|
||||
failed += 1
|
||||
results.append({
|
||||
"imei": imei,
|
||||
**fields,
|
||||
"api_code": "EXCEPTION",
|
||||
"api_message": str(exc),
|
||||
})
|
||||
|
||||
if not args.dry_run:
|
||||
time.sleep(CONFIG["request_delay"])
|
||||
|
||||
# ── Summary & audit CSV ───────────────────────────────────────────────────
|
||||
log.info("-" * 60)
|
||||
log.info(
|
||||
"Done. OK: %d Failed: %d Skipped: %d | Total: %d",
|
||||
success, failed, skipped, success + failed + skipped
|
||||
)
|
||||
|
||||
out_path = "tracksolid_update_results.csv"
|
||||
pd.DataFrame(results).to_csv(out_path, index=False)
|
||||
log.info("Audit results written to %s", out_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
348
tracksolid_vehicle_update.py
Normal file
348
tracksolid_vehicle_update.py
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tracksolid Pro - Bulk Vehicle Information Updater
|
||||
Updates vehicle details via the jimi.open.device.update API endpoint
|
||||
using data from the Fireside logistics CSV.
|
||||
|
||||
Signing approach taken directly from tspostman.py (confirmed working):
|
||||
- POST as x-www-form-urlencoded (NOT JSON)
|
||||
- All parameter values cast to strings before signing
|
||||
- expires_in passed as string '7200', not integer
|
||||
|
||||
Usage:
|
||||
python tracksolid_update.py [--dry-run] [--csv path/to/file.csv] [--limit N]
|
||||
|
||||
Environment variables (or edit CONFIG below):
|
||||
TS_USER_ID - Your Tracksolid account username
|
||||
TS_USER_PWD_MD5 - MD5 hash of your password (lowercase)
|
||||
TS_APP_KEY - Your appKey from JIMI
|
||||
TS_APP_SECRET - Your appSecret from JIMI
|
||||
TS_API_URL - API base URL (defaults to EU node)
|
||||
TS_CSV_PATH - Path to the logistics CSV
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
import pandas as pd
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# CONFIGURATION — edit here or set environment variables
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
CONFIG = {
|
||||
"user_id": os.getenv("TS_USER_ID", "Fireside Communications"),
|
||||
"user_pwd_md5": os.getenv("TS_USER_PWD_MD5", "81a1b005efd3596073e38efd8a2fd3fd"),
|
||||
"app_key": os.getenv("TS_APP_KEY", "8FB345B8693CCD00BB70D528C0D4019E"),
|
||||
"app_secret": os.getenv("TS_APP_SECRET", "3177c89993b446c6aced0d7c56375d2c"),
|
||||
# EU node confirmed for this account
|
||||
"api_url": os.getenv("TS_API_URL", "https://eu-open.tracksolidpro.com/route/rest"),
|
||||
"expires_in": "7200", # string, not int — matches tspostman.py
|
||||
"request_delay": 0.5, # seconds between API calls
|
||||
}
|
||||
|
||||
CSV_PATH = os.getenv("TS_CSV_PATH", "20260414_FS__Logistics_-_final_fixed.csv")
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# LOGGING
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)-8s %(message)s",
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout),
|
||||
logging.FileHandler("tracksolid_update.log", encoding="utf-8"),
|
||||
],
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# SIGNING UTILITIES (ported directly from tspostman.py)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def utc_timestamp() -> str:
|
||||
"""UTC time formatted as yyyy-MM-dd HH:mm:ss."""
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def build_sign(params: dict, app_secret: str) -> str:
|
||||
"""
|
||||
Tracksolid signing algorithm (matches tspostman.py exactly):
|
||||
1. Sort all param keys alphabetically, exclude 'sign' and empty values.
|
||||
2. Concatenate key+value pairs with no separators.
|
||||
3. Wrap with appSecret on both sides.
|
||||
4. MD5 -> UPPERCASE 32-char string.
|
||||
|
||||
All values must already be strings before calling this.
|
||||
"""
|
||||
sorted_keys = sorted(
|
||||
k for k in params
|
||||
if k != "sign" and params[k] is not None and str(params[k]).strip() != ""
|
||||
)
|
||||
param_string = "".join(f"{k}{params[k]}" for k in sorted_keys)
|
||||
raw_string = f"{app_secret}{param_string}{app_secret}"
|
||||
return hashlib.md5(raw_string.encode("utf-8")).hexdigest().upper()
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# TRACKSOLID CLIENT
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TracksolidClient:
|
||||
def __init__(self, cfg: dict):
|
||||
self.cfg = cfg
|
||||
self._token: str | None = None
|
||||
self._token_expires_at: float = 0.0
|
||||
self.session = requests.Session()
|
||||
# No Content-Type set here — requests sets it automatically to
|
||||
# application/x-www-form-urlencoded when data= is used (matching tspostman.py)
|
||||
|
||||
def _post(self, params: dict) -> dict:
|
||||
"""
|
||||
Sign and POST params using x-www-form-urlencoded encoding.
|
||||
Confirmed working approach from tspostman.py:
|
||||
- Cast all values to strings
|
||||
- Use data= (form-encoded), NOT json=
|
||||
"""
|
||||
str_params = {
|
||||
k: str(v)
|
||||
for k, v in params.items()
|
||||
if v is not None and str(v).strip() != ""
|
||||
}
|
||||
str_params["sign"] = build_sign(str_params, self.cfg["app_secret"])
|
||||
|
||||
log.debug("POST %s params=%s", self.cfg["api_url"], str_params)
|
||||
resp = self.session.post(
|
||||
self.cfg["api_url"],
|
||||
data=str_params, # form-encoded, NOT json=
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
log.debug("Response: %s", json.dumps(data))
|
||||
return data
|
||||
|
||||
def _common_params(self, method: str) -> dict:
|
||||
return {
|
||||
"method": method,
|
||||
"timestamp": utc_timestamp(),
|
||||
"app_key": self.cfg["app_key"],
|
||||
"sign_method": "md5",
|
||||
"v": "1.0",
|
||||
"format": "json",
|
||||
}
|
||||
|
||||
def get_token(self) -> str:
|
||||
"""Return a valid access token, fetching a new one only when needed."""
|
||||
if self._token and time.time() < self._token_expires_at - 60:
|
||||
log.debug("Reusing cached token.")
|
||||
return self._token
|
||||
|
||||
log.info("Obtaining new access token ...")
|
||||
params = self._common_params("jimi.oauth.token.get")
|
||||
params.update({
|
||||
"user_id": self.cfg["user_id"],
|
||||
"user_pwd_md5": self.cfg["user_pwd_md5"],
|
||||
"expires_in": self.cfg["expires_in"], # already a string per tspostman.py
|
||||
})
|
||||
|
||||
data = self._post(params)
|
||||
if data.get("code") != 0:
|
||||
raise RuntimeError(
|
||||
f"Auth failed — code={data.get('code')} message={data.get('message')}"
|
||||
)
|
||||
|
||||
self._token = data["result"]["accessToken"]
|
||||
self._token_expires_at = time.time() + int(data["result"]["expiresIn"])
|
||||
log.info("Token acquired. Valid for %s seconds.", data["result"]["expiresIn"])
|
||||
return self._token
|
||||
|
||||
def update_vehicle(self, imei: str, fields: dict, dry_run: bool = False) -> dict:
|
||||
"""
|
||||
Call jimi.open.device.update for one IMEI.
|
||||
fields: dict using local key names (see row_to_fields below).
|
||||
Returns the API response dict.
|
||||
"""
|
||||
if dry_run:
|
||||
log.info("[DRY-RUN] IMEI %s -> %s", imei, fields)
|
||||
return {"code": 0, "message": "dry-run"}
|
||||
|
||||
token = self.get_token()
|
||||
params = self._common_params("jimi.open.device.update")
|
||||
params["access_token"] = token
|
||||
params["imei"] = str(imei)
|
||||
|
||||
# Map local field names -> Tracksolid API field names
|
||||
api_field_map = {
|
||||
"license_plate": "vehicleNumber",
|
||||
"driver_name": "driverName",
|
||||
"driver_phone": "driverPhone",
|
||||
"vehicle_model": "vehicleModels",
|
||||
"vin": "carFrame",
|
||||
"engine_number": "engineNumber",
|
||||
"device_name": "deviceName",
|
||||
"fuel_per_100": "fuelPer100km",
|
||||
}
|
||||
for local_key, api_key in api_field_map.items():
|
||||
val = fields.get(local_key)
|
||||
if val:
|
||||
params[api_key] = str(val)
|
||||
|
||||
return self._post(params)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# CSV PARSING
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def clean(val) -> str | None:
|
||||
"""Return None for NaN/empty values, else a stripped string."""
|
||||
if pd.isna(val) or str(val).strip() in ("", "nan", "NaN"):
|
||||
return None
|
||||
return str(val).strip()
|
||||
|
||||
|
||||
def row_to_fields(row: pd.Series) -> dict:
|
||||
"""Map one CSV row to local field names used by update_vehicle()."""
|
||||
return {
|
||||
"license_plate": clean(row.get("License Plate No.")),
|
||||
"driver_name": clean(row.get("Driver Name")),
|
||||
"driver_phone": clean(row.get("Telephone")),
|
||||
"vehicle_model": clean(row.get("Vehicle Model")),
|
||||
"vin": clean(row.get("VIN")),
|
||||
"engine_number": clean(row.get("Engine Number")),
|
||||
"device_name": clean(row.get("Device Name")),
|
||||
"fuel_per_100": clean(row.get("Fuel/100km")),
|
||||
}
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# MAIN
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Bulk-update Tracksolid vehicle info from CSV."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run", action="store_true",
|
||||
help="Show what would be sent without making update API calls."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--csv", default=CSV_PATH,
|
||||
help=f"Path to the logistics CSV (default: {CSV_PATH})"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--limit", type=int, default=None,
|
||||
help="Only process the first N rows (useful for testing)."
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# ── Load CSV ───────────────────────────────────────────────────────────────
|
||||
csv_file = Path(args.csv)
|
||||
if not csv_file.exists():
|
||||
log.error("CSV file not found: %s", csv_file)
|
||||
sys.exit(1)
|
||||
|
||||
df = pd.read_csv(csv_file, dtype={"IMEI": str})
|
||||
log.info("Loaded %d rows from %s", len(df), csv_file)
|
||||
|
||||
if args.limit:
|
||||
df = df.head(args.limit)
|
||||
log.info("Limiting to first %d rows.", args.limit)
|
||||
|
||||
df = df[df["IMEI"].notna() & (df["IMEI"].str.strip() != "")]
|
||||
log.info("%d rows have a valid IMEI.", len(df))
|
||||
|
||||
if df.empty:
|
||||
log.warning("No rows to process. Exiting.")
|
||||
sys.exit(0)
|
||||
|
||||
# ── Initialise client & verify auth ───────────────────────────────────────
|
||||
client = TracksolidClient(CONFIG)
|
||||
|
||||
if not args.dry_run:
|
||||
try:
|
||||
client.get_token()
|
||||
except Exception as exc:
|
||||
log.error("Authentication failed: %s", exc)
|
||||
log.error("Check TS_USER_ID, TS_USER_PWD_MD5, TS_APP_KEY, TS_APP_SECRET.")
|
||||
sys.exit(1)
|
||||
|
||||
# ── Process rows ───────────────────────────────────────────────────────────
|
||||
results = []
|
||||
success = 0
|
||||
failed = 0
|
||||
skipped = 0
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
imei = str(row["IMEI"]).strip()
|
||||
fields = row_to_fields(row)
|
||||
|
||||
# Skip rows where every updatable field is empty
|
||||
if not any(fields.values()):
|
||||
log.warning("Row %d (IMEI %s): no updatable fields, skipping.", idx + 1, imei)
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
log.info(
|
||||
"Row %d/%d — IMEI: %s | Plate: %s | Driver: %s",
|
||||
idx + 1, len(df), imei,
|
||||
fields.get("license_plate") or "—",
|
||||
fields.get("driver_name") or "—",
|
||||
)
|
||||
|
||||
try:
|
||||
resp = client.update_vehicle(imei, fields, dry_run=args.dry_run)
|
||||
if resp.get("code") == 0:
|
||||
log.info(" OK")
|
||||
success += 1
|
||||
else:
|
||||
log.warning(
|
||||
" API error — code=%s message=%s",
|
||||
resp.get("code"), resp.get("message")
|
||||
)
|
||||
failed += 1
|
||||
|
||||
results.append({
|
||||
"imei": imei,
|
||||
**fields,
|
||||
"api_code": resp.get("code"),
|
||||
"api_message": resp.get("message"),
|
||||
})
|
||||
|
||||
except Exception as exc:
|
||||
log.error(" Exception for IMEI %s: %s", imei, exc)
|
||||
failed += 1
|
||||
results.append({
|
||||
"imei": imei,
|
||||
**fields,
|
||||
"api_code": "EXCEPTION",
|
||||
"api_message": str(exc),
|
||||
})
|
||||
|
||||
if not args.dry_run:
|
||||
time.sleep(CONFIG["request_delay"])
|
||||
|
||||
# ── Summary & audit CSV ───────────────────────────────────────────────────
|
||||
log.info("-" * 60)
|
||||
log.info(
|
||||
"Done. OK: %d Failed: %d Skipped: %d | Total: %d",
|
||||
success, failed, skipped, success + failed + skipped
|
||||
)
|
||||
|
||||
out_path = "tracksolid_update_results.csv"
|
||||
pd.DataFrame(results).to_csv(out_path, index=False)
|
||||
log.info("Audit results written to %s", out_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -24,9 +24,11 @@ import logging
|
|||
import os
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from functools import lru_cache
|
||||
from typing import Optional, Any
|
||||
|
||||
import psycopg2
|
||||
|
|
@ -310,3 +312,81 @@ def _update_token_cache(r: dict) -> str:
|
|||
""", (USER_ID, token, r.get("refreshToken"), expires_at))
|
||||
conn.commit()
|
||||
return token
|
||||
|
||||
# ── Reverse Geocoding (Nominatim) ────────────────────────────────────────────
|
||||
# Best-effort lookup used by poll_trips() to populate trips.start_address /
|
||||
# end_address. Must NEVER raise — failure returns None and the trip insert
|
||||
# proceeds without the address.
|
||||
|
||||
_NOMINATIM_URL = os.getenv(
|
||||
"NOMINATIM_URL",
|
||||
"https://nominatim.openstreetmap.org/reverse",
|
||||
)
|
||||
_NOMINATIM_USER_AGENT = os.getenv(
|
||||
"NOMINATIM_USER_AGENT",
|
||||
"fireside-tracksolid/1.0 (kianiadee@gmail.com)",
|
||||
)
|
||||
_GEOCODE_LOCK = threading.Lock()
|
||||
_GEOCODE_LAST_CALL_AT: float = 0.0
|
||||
_GEOCODE_MIN_INTERVAL_S: float = 1.0 # Nominatim TOS — 1 req/sec absolute max
|
||||
|
||||
|
||||
def _geocode_throttle() -> None:
|
||||
"""Sleep just long enough since the previous call to honour 1 req/sec."""
|
||||
global _GEOCODE_LAST_CALL_AT
|
||||
with _GEOCODE_LOCK:
|
||||
elapsed = time.monotonic() - _GEOCODE_LAST_CALL_AT
|
||||
if elapsed < _GEOCODE_MIN_INTERVAL_S:
|
||||
time.sleep(_GEOCODE_MIN_INTERVAL_S - elapsed)
|
||||
_GEOCODE_LAST_CALL_AT = time.monotonic()
|
||||
|
||||
|
||||
@lru_cache(maxsize=2048)
|
||||
def _reverse_geocode_cached(lat_round: float, lng_round: float) -> Optional[str]:
|
||||
"""Cached HTTP call. Key is lat/lng rounded to 4 dp (~11 m precision)."""
|
||||
_geocode_throttle()
|
||||
try:
|
||||
r = _session.get(
|
||||
_NOMINATIM_URL,
|
||||
params={
|
||||
"lat": lat_round,
|
||||
"lon": lng_round,
|
||||
"format": "json",
|
||||
"zoom": 18,
|
||||
"addressdetails": 0,
|
||||
},
|
||||
headers={"User-Agent": _NOMINATIM_USER_AGENT},
|
||||
timeout=10,
|
||||
)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
addr = data.get("display_name")
|
||||
if addr:
|
||||
return addr.strip()
|
||||
return None
|
||||
except (requests.RequestException, ValueError) as e:
|
||||
_log.warning("reverse_geocode failed lat=%s lng=%s: %s",
|
||||
lat_round, lng_round, e)
|
||||
return None
|
||||
|
||||
|
||||
def reverse_geocode(lat: Any, lng: Any) -> Optional[str]:
|
||||
"""
|
||||
Reverse-geocode a coordinate to a human-readable address via Nominatim.
|
||||
|
||||
Best-effort. Never raises. Returns None on:
|
||||
• missing / invalid lat or lng
|
||||
• HTTP/timeout/JSON failure
|
||||
• Nominatim returns no display_name
|
||||
|
||||
Cached on lat/lng rounded to 4 decimal places (~11 m), which keeps
|
||||
repeated visits to the same depot/site from re-querying.
|
||||
"""
|
||||
flat, flng = clean_num(lat), clean_num(lng)
|
||||
if flat is None or flng is None:
|
||||
return None
|
||||
if flat == 0.0 and flng == 0.0:
|
||||
return None
|
||||
if not (-90 <= flat <= 90 and -180 <= flng <= 180):
|
||||
return None
|
||||
return _reverse_geocode_cached(round(flat, 4), round(flng, 4))
|
||||
Loading…
Reference in a new issue