docs: reflect deployed state — migrations 02/03 (EAT), read-side push status

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kianiadee 2026-06-12 00:02:41 +03:00
parent 1bf7e63c62
commit f0def8d781
2 changed files with 20 additions and 3 deletions

View file

@ -12,7 +12,7 @@ keyed by number plate (`car`).
| Piece | What | | Piece | What |
|---|---| |---|---|
| `migrations/01_fuel_schema.sql` | The `fuel` schema: `fuel.records` (raw-jsonb-first + trigger-derived columns), `fuel.norm_plate` / `fuel.canon_fuel_type` / `fuel.canon_department` normalizers, `fuel.ingest_state` (CDC watermark), and `reporting.v_fuel_fills` / `reporting.v_fuel_efficiency` (the read views) | | `migrations/*.sql` | The `fuel` schema: `fuel.records` (raw-jsonb-first + trigger-derived columns), `fuel.norm_plate` / `fuel.canon_fuel_type` / `fuel.canon_department` normalizers, `fuel.ingest_state` (CDC watermark), and `reporting.v_fuel_fills` / `reporting.v_fuel_efficiency` (the read views). `01` base schema · `02` one-device-per-fill join fix · `03` standardize timestamps to **Africa/Nairobi (EAT)** |
| `import_fuel.py` | Pulls fuel records from the rustfs `fuel` bucket and upserts them on `id` | | `import_fuel.py` | Pulls fuel records from the rustfs `fuel` bucket and upserts them on `id` |
| `s3util.py` | rustfs (S3-compatible) client factory — path-style, custom endpoint | | `s3util.py` | rustfs (S3-compatible) client factory — path-style, custom endpoint |
| `run_migrations.py` | Applies `migrations/*.sql` in order (ledger: `fuel.schema_migrations`) | | `run_migrations.py` | Applies `migrations/*.sql` in order (ledger: `fuel.schema_migrations`) |
@ -34,8 +34,12 @@ normalizers (`fuel.norm_plate`, `fuel.canon_fuel_type`, `fuel.canon_department`)
source of truth. Soft-deletes (`deleted_at`) are kept on the row and excluded by the read views. source of truth. Soft-deletes (`deleted_at`) are kept on the row and excluded by the read views.
The fuel record links to the fleet by plate: `reporting.v_fuel_fills` joins The fuel record links to the fleet by plate: `reporting.v_fuel_fills` joins
`fuel.norm_plate(car)` to `fuel.norm_plate(devices.vehicle_number)` to pick up `cost_centre` / `fuel.norm_plate(car)` to `fuel.norm_plate(devices.vehicle_number)` (LATERAL + `LIMIT 1`, since a
`assigned_city` / `imei`. plate can map to more than one device over time) to pick up `cost_centre` / `assigned_city` / `imei`.
**Timezone:** the source stamps `record_datetime` in UTC; FleetFuel stores all fuel timestamps as
**Africa/Nairobi (EAT, UTC+3)** wall-clock (`timestamp` without tz), so `record_datetime::date`
(the daily-trend bucket) is the Kenyan calendar day. See `migrations/03_fuel_timezone_eat.sql`.
## Bucket layout ## Bucket layout

View file

@ -64,6 +64,14 @@
<div class="crumbs">See also: <a href="plan.html">plan.html</a> (implementation plan) · host: <code>twala.rahamafresh.com</code></div> <div class="crumbs">See also: <a href="plan.html">plan.html</a> (implementation plan) · host: <code>twala.rahamafresh.com</code></div>
</header> </header>
<div class="note" style="margin-top:18px">
<b>Status (2026-06-12):</b> ingestion is <b>live in the prod DB</b> — migrations <code>0103</code> applied,
~1,900 rows ingested, <code>reporting.v_fuel_fills</code> = 1,888 rows / 1,775 matched (94%). Read-side is
<b>pushed, awaiting promotion</b>: FleetOps tab on branch <code>staging</code> (auto-deploys staging);
<code>dashboard_api</code> on <code>feat/staging-fleetops-architecture</code> (PR #17) — promote to
<code>staging</code> then <code>main</code> to light up the API.
</div>
<h2><span class="num">1</span>Solution flow</h2> <h2><span class="num">1</span>Solution flow</h2>
<p>Five stages. The WhatsApp fuel feed is exported to object storage by n8n; FleetFuel pulls it, normalizes <p>Five stages. The WhatsApp fuel feed is exported to object storage by n8n; FleetFuel pulls it, normalizes
and stores it, the API reads it back, and the SPA renders it.</p> and stores it, the API reads it back, and the SPA renders it.</p>
@ -184,6 +192,11 @@ python run_migrations.py <span class="c"># creates fuel.* + reportin
python import_fuel.py --snapshot <span class="c"># DRY-RUN: parse + log counts, writes nothing</span> python import_fuel.py --snapshot <span class="c"># DRY-RUN: parse + log counts, writes nothing</span>
python import_fuel.py --snapshot --apply <span class="c"># full reconcile from fuel_records/latest.json</span></code></pre> python import_fuel.py --snapshot --apply <span class="c"># full reconcile from fuel_records/latest.json</span></code></pre>
<div class="note"><code>run_migrations.py</code> applies the whole set in order (ledger <code>fuel.schema_migrations</code>):
<code>01</code> base schema · <code>02</code> one-device-per-fill join fix (a plate can map to several
<code>devices</code> rows) · <code>03</code> standardize all fuel timestamps to <b>Africa/Nairobi (EAT)</b> so
<code>record_datetime::date</code> buckets by the Kenyan day. All idempotent.</div>
<h3>3b · Recurring: the ingest cron</h3> <h3>3b · Recurring: the ingest cron</h3>
<p>A scheduled container that keeps the DB current. The <code>--snapshot</code> full-reconcile is idempotent and <p>A scheduled container that keeps the DB current. The <code>--snapshot</code> full-reconcile is idempotent and
self-healing (picks up edits + soft-deletes), so a simple hourly run is safe:</p> self-healing (picks up edits + soft-deletes), so a simple hourly run is safe:</p>