Implementation Plan · 17_fleetfuel

FleetFuel

Ingest the RustFS fuel bucket → shared database → a new Fuel Log tab in FleetOps.

RustFS fuel bucket 17_fleetfuel ingestion fuel schema · tracksolid_db dashboard_api FleetOps “Fuel Log” tab

·Context

FleetOps (15_fleetops) is the fleet operations analytics SPA. It already has a trip-derived fuel panel (GET /analytics/fuel), but that data is effectively empty — estimated fuel needs devices.fuel_100km (NULL fleet-wide) and actual oils is sparse.

A real fuel-spend feed now lands in the RustFS fuel bucket: WhatsApp fuel-update messages, extracted by an n8n CDC job from logistics_department.fuel_records1,922 rows (Feb–Jun 2026) of actual fills (litres, KES amount, odometer, fuel type, driver, department), keyed by number plate.

Goal (full vertical): a new 17_fleetfuel module pulls the bucket into the shared tracksolid_db under its own fuel schema, exposes it via dashboard_api, and adds a new “Fuel Log” tab to FleetOps. The existing trip-derived panel stays as-is — the two coexist. This mirrors the proven 16_fleettickets module pattern exactly.

Credential note: the RUSTFS_* keys were pasted in chat. They go only in a gitignored .env (never committed), and the shared secret should be rotated after this work, since plaintext-in-chat counts as exposed.

·The data (confirmed by reading the bucket)

Bucket layout (s3.rahamafresh.com, path-style, region us-east-1):

Record shape (stable PK id):

id, record_datetime, department, driver, car, liters, amount, fuel_type, odometer,
sender_name, sender_phone, raw_message, source, source_instance, source_message_id,
source_event_timestamp, message_fingerprint, deleted_at, deleted_by, delete_reason,
delete_source, created_at

The data is messy (WhatsApp-sourced) → normalization is essential:

·Pattern mirrored: 16_fleettickets

Self-contained Python module → reads a RustFS bucket → upserts raw-jsonb rows into a namespaced schema in the shared tracksolid_db → idempotent migrations with a schema_migrations ledger → a reporting.* view consumed by dashboard_api → surfaced as a FleetOps tab. Reuse: shared.py (DB ctx-mgr + clean), run_migrations.py (ledger runner), the dry-run/--apply CLI convention, and the .env/pyproject/README layout.

AIngestion repo — 17_fleetfuel new

Created in /Users/kianiadee/Downloads/projects/17_fleetfuel, files mirroring fleettickets:

import_fuel.py

Bmigrations/01_fuel_schema.sql — the fuel schema new

Idempotent, lives in shared tracksolid_db:

Cdashboard_api read endpoints edit

In dashboard_api_rev.py, add endpoints reusing _analytics_window + _dim_filters + RealDictCursor:

No business logic in the API — it only selects from the reporting.* views.

DFleetOps SPA — new “Fuel Log” tab edit

In 15_fleetops/src/index.html (single-file SPA, inline JS + Chart.js). Leave the existing fuel panel untouched; add a “Fuel Log” tab:

EGit & deploy

·Critical files

FileAction
17_fleetfuel/import_fuel.py, shared.py, run_migrations.py, pyproject.toml, .env.example, README.mdnew mirror 16_fleettickets/*
17_fleetfuel/migrations/01_fuel_schema.sqlnew fuel schema + normalizers + reporting.v_fuel_fills
tracksolid_timescale_grafana_prod/dashboard_api_rev.pyedit add /analytics/fuel-fills[/recent], extend /analytics/filters
15_fleetops/src/index.htmledit add “Fuel Log” tab

Reuse: 16_fleettickets/shared.py, run_migrations.py, the _scrub_nan/upsert shape; dashboard_api_rev.py:444 _dim_filters, _analytics_window; the stdlib SigV4 reader already proven this session (fallback if boto3 is undesirable).

Verification (end-to-end)

  1. Bucket read — already proven this session (listed 14 objects, parsed latest.json = 1922 rows).
  2. Ingestion dry-runpython import_fuel.py --snapshot (no --apply): logs parsed/valid/skipped, no DB writes.
  3. Migrate + applypython run_migrations.py then import_fuel.py --snapshot --apply. Spot-check: SELECT count(*), count(*) FILTER (WHERE deleted_at IS NULL) FROM fuel.records; (≈1922 / ≈1888) and plate-match rate in reporting.v_fuel_fills.
  4. APIcurl "$API/analytics/fuel-fills?period=90d" → totals/rows/by_department/trend non-empty; /analytics/filters includes departments.
  5. Frontend — build & run fleetops locally, open the Fuel Log tab, confirm KPIs/chart/tables render and filters drive the queries.
  6. /verify the fleetops change once wired.