New website under development. In the meantime, follow EV Fleet (Pty) Ltd on LinkedIn for updates.

EV-Fleet


Documentation:

Postgres Implementation Plan — ev-fleet-sim

What this is: the phase-gated execution plan for building Postgres summary storage into ev-fleet-sim — Step 2 (“Saving summaries into PostgreSQL”) and the Postgres-read half of Step 4 (“Set up methods to read summary … data”) from Chris Abraham’s FigJam board, as recorded in ../brain/skills/SIM_DATABASE_IMPLEMENTATION.md → “Current Direction”. That document is the source of truth for why this project exists and how it fits the larger Sim-becomes-a-database-service pivot; this document is the source of truth for how this one slice gets built, phase by phase, gated the same way that guide gates its own phases.

Technical design reference: docs/docs/postgres-setup.md (same repo) has the actual schema DDL, config format, docker-compose file, and library choice with justification. This plan sequences that design into gated phases with tests and sign-off — read docs/docs/postgres-setup.md for what to build, this doc for in what order and how each step gets verified.

Ownership split (per the board): Chris owns Step 1 (Postgres/DuckDB/Parquet plumbing — shared foundational setup, starred as his current task), Step 3 (Parquet/DuckDB time-series), the DuckDB-read half of Step 4, and Step 5 (REST API). This plan covers the Postgres-only slice: standing up Postgres plumbing (the Postgres half of Step 1, since nobody has started it yet), Step 2, and the Postgres-read half of Step 4.

Sim Boundary note: this plan modifies ev-fleet-sim’s own source (src/data_processing_ev/). That is normally off-limits per Rule 0 of SIM_DATABASE_IMPLEMENTATION.md — but this entire project traces to Chris’s own explicit direction (his FigJam board), which is exactly the Maintainer-directed exception that rule carves out. Scope discipline still applies: build what’s specified below, nothing extra “while we’re in here.”


Non-negotiable rules (inherited from SIM_DATABASE_IMPLEMENTATION.md §1, applied here)

  1. No hallucinated facts. Before editing any line referencing a file/function/line number, re-grep/re-Read it — line numbers below were verified once; they drift as code changes.
  2. No invented dependencies. Only psycopg[binary] is approved for this slice (see docs/docs/postgres-setup.md §3 for why). Anything else — stop and flag before adding.
  3. Ask, don’t assume, on genuine ambiguity. The run-identity decision (append-only vs. overwrite, docs/docs/postgres-setup.md §4) was confirmed 2026-07-23 (owner decision): append-only, run_id UNIQUE. Phase 1’s schema task and Phase 2 are unblocked.
  4. The agent tests its own work. No task is done until its Agent Self-Test passes with real command output, and the diff is re-read once after to confirm it matches intent.
  5. Additive-first. Every phase runs alongside existing JSON-file writes, never replaces them, until Brain’s own consumer migration (Step 6, a separate project) makes the JSON files genuinely obsolete. No phase here deletes stats_fleet.json/stats_*.json writes.
  6. One phase = one reviewable unit. Don’t bundle phases into one commit/MR.

Phase 0 — Preconditions

Status: Complete (2026-07-23)

Tasks

Trigger prompts & tests

| Task | Trigger prompt | Test | |—|—|—| | PG-T0.1 | “Run PG-T0.1: fetch origin, diff against brain-phase1-skip-plots-flags, decide target branch with the owner.” | Branch decision recorded in writing (this doc or a commit message), git status clean or intentionally staged only | | PG-T0.2 | “Run PG-T0.2: confirm append-only vs. upsert run-identity with Chris before Phase 2 starts.” | Decision recorded in docs/docs/postgres-setup.md §4, no longer marked “not yet confirmed” | | PG-T0.3 | “Run PG-T0.3: confirm with Chris/owner whether the FigJam diagram supersedes the MinIO-deferred decision.” | Decision recorded in writing in this doc, no longer marked PENDING | | PG-T0.4 | “Run PG-T0.4: confirm ownership of the diagram’s scope (Phases 5-8) with Chris/owner.” | Decision recorded in writing in this doc, no longer marked PENDING |

Completion Markers

Rollback

N/A — no code changes in this phase, only decisions and git hygiene.


Phase 1 — Config, Connection, and Schema Plumbing

Status: Complete (2026-07-27) — commit ad4c55b on postgres-phase1-config-connection-schema (branched off postgres-baseline, 06a6bb4).

(This is the Postgres half of the board’s Step 1 — nobody has started it yet, confirmed with the user directly.)

Preconditions

Tasks

Trigger prompts & tests

| Task | Trigger prompt | Test | |—|—|—| | PG-T1.1 | “Run PG-T1.1: create the db/ subpackage skeleton.” | python -c "from data_processing_ev import db" imports with no error | | PG-T1.2 | “Run PG-T1.2: implement db/config.py per docs/docs/postgres-setup.md §1.” | Unit test: fixture INI parses correctly; missing file returns None, not an exception | | PG-T1.3 | “Run PG-T1.3: implement db/connection.py’s get_connection() and ensure_schema().” | Against docker-compose Postgres: get_connection() returns a live connection; ensure_schema() run twice is a no-op the second time | | PG-T1.4 | “Run PG-T1.4: write db/schema.sql per docs/docs/postgres-setup.md §4, using the confirmed run-identity decision from PG-T0.2.” | ensure_schema() creates both tables with the exact columns listed in docs/docs/postgres-setup.md §4 (verify via \d fleet_stats / \d vehicle_stats) | | PG-T1.5 | “Run PG-T1.5: add psycopg[binary] and the test extra to setup.cfg.” | pip install -e . succeeds; pip install -e .[test] succeeds | | PG-T1.6 | “Run PG-T1.6: add docker-compose.yml and db_config.ini.example.” | docker compose up -d postgres exits 0, pg_isready healthy |

Phase-specific guardrails

Agent Self-Test

Manual Verification (human)

  1. Inspect fleet_stats/vehicle_stats schema via psql against the compose Postgres.
  2. Confirm db_config.ini.example matches the compose file’s credentials exactly.
  3. Confirm .gitignore excludes the real db_config.ini.

Completion Markers

Rollback

Delete src/data_processing_ev/db/, revert setup.cfg, remove docker-compose.yml and db_config.ini.example — no existing code touched, clean revert.


Phase 2 — Write Path (Step 2)

Status: Complete (2026-07-27) — commit 36c2d56.

Preconditions

Tasks

Trigger prompts & tests

| Task | Trigger prompt | Test | |—|—|—| | PG-T2.1 | “Run PG-T2.1: implement db/summary_writer.py per docs/docs/postgres-setup.md §5.” | Unit test with a mock/fixture stats_tree: row appears in Postgres with correct values; with no config file present, function returns cleanly with no exception | | PG-T2.2 | “Run PG-T2.2: hook write_ev_stats/write_fleet_stats into ev_results_analysis.py at the verified line numbers.” | Real my-scenario run with db_config.ini present: rows appear in both tables AND stats_fleet.json/stats_*.json are still written unchanged |

Phase-specific guardrails

Agent Self-Test

Manual Verification (human)

  1. Run my-scenario end-to-end with Postgres up; inspect rows via psql.
  2. Confirm stats_fleet.json content still matches what’s in fleet_stats.raw_stats exactly.
  3. Kill Postgres mid-run once, confirm the simulation still finishes and JSON files are correct.

Completion Markers

Rollback

Revert the two-line-plus-one-import hook in ev_results_analysis.py; delete summary_writer.py. JSON-writing behavior is untouched throughout, so rollback carries zero risk to existing consumers.


Phase 3 — Read Path (Postgres half of Step 4)

Status: Complete (2026-07-27) — commit 063d025.

Preconditions

Tasks

Trigger prompts & tests

| Task | Trigger prompt | Test | |—|—|—| | PG-T3.1 | “Run PG-T3.1: implement db/reader.py per docs/docs/postgres-setup.md §6.” | Round-trip test: write a known stats_tree fixture, read it back via each reader function, assert deep-equality with the original |

Phase-specific guardrails

Agent Self-Test

Manual Verification (human)

  1. Run my-scenario twice, confirm get_latest_run_id returns the second run’s id, and list_vehicle_stats for that run only returns that run’s vehicles (not the first run’s).

Completion Markers

Rollback

Delete db/reader.py — no other module depends on it yet (Step 5 is Chris’s later work).


Phase 4 — Tests & CI

Status: Complete (2026-07-27) — commit 22dca31.

Preconditions

Tasks

Trigger prompts & tests

| Task | Trigger prompt | Test | |—|—|—| | PG-T4.1 | “Run PG-T4.1: write the db test suite per docs/docs/postgres-setup.md §7.” | docker compose up -d postgres && pytest tests/ -q exits 0 | | PG-T4.2 | “Run PG-T4.2: propose a Postgres-backed CI job to Chris, do not merge without his sign-off.” | Explicit approval recorded before merge |

Phase-specific guardrails

Agent Self-Test

Manual Verification (human)

  1. Run the full suite locally from a clean docker compose down -v && up.
  2. If PG-T4.2 shipped, confirm the CI job actually appears and passes on GitLab.

Completion Markers

Rollback

Delete tests/; revert .gitlab-ci.yml if PG-T4.2 shipped.


Phase 5 — Scenario & Run Metadata (Execution Tracking)

Status: Complete (2026-07-28) — commit 021b065 on postgres-implementation, in ev-fleet-sim-master.

(New scope introduced by the “posgres - parquet integration” FigJam diagram, shown directly by the user 2026-07-27 — not part of the original board text this plan was built from.)

Preconditions

Tasks

Trigger prompts & tests

| Task | Trigger prompt | Test | |—|—|—| | PG-T5.1 | “Run PG-T5.1: add the scenarios table to schema.sql.” | ensure_schema() creates scenarios with the agreed columns | | PG-T5.2 | “Run PG-T5.2: add the runs table to schema.sql, FK’d to scenarios.” | ensure_schema() creates runs; FK constraint verified (insert with bad scenario_id fails) | | PG-T5.3 | “Run PG-T5.3: hook execution-status/timing writes into the real run-launching code, re-grepped fresh.” | A real (or simulated) run writes queuedrunningsucceeded/failed with real timestamps | | PG-T5.4 | “Run PG-T5.4: implement CPU/RAM/storage measurement for a run.” | Measured values are non-null and plausible for a real run | | PG-T5.5 | “Run PG-T5.5: implement db/run_tracker.py.” | Unit + integration tests: create/update/finalize a run, values land correctly; failure path never raises | | PG-T5.6 | “Run PG-T5.6: implement scenario/run reader functions.” | Round-trip test against real written data |

Phase-specific guardrails

Agent Self-Test

Manual Verification (human)

  1. Trigger a real run, inspect its runs row mid-execution (running) and after (succeeded or failed) via psql.
  2. Confirm scenarios-level averages update (or compute correctly, if a view) after multiple runs of the same scenario.

Completion Markers

Rollback

Delete the new tables and db/run_tracker.py; revert the instrumentation hook in sim_worker/. fleet_stats/vehicle_stats and their write path are untouched throughout.


Phase 6 — File Catalog (Inputs/Outputs per Run)

Status: Complete (2026-07-29) — commit 4720f3c on postgres-implementation, in ev-fleet-sim-master.

Preconditions

Tasks

Trigger prompts & tests

| Task | Trigger prompt | Test | |—|—|—| | PG-T6.1 | “Run PG-T6.1: add the file-catalog table(s) to schema.sql.” | ensure_schema() creates the table(s) with agreed columns and FK to runs | | PG-T6.2 | “Run PG-T6.2: implement db/file_catalog.py per the diagram’s Inputs/Outputs spec.” | Fixture test: recording each file kind produces a correct row; never raises on failure | | PG-T6.3 | “Run PG-T6.3: implement file-catalog reader functions.” | Round-trip test against real written data, filtered correctly by kind/vehicle |

Phase-specific guardrails

Agent Self-Test

Manual Verification (human)

  1. Inspect a real run’s full file catalog via psql, cross-check against what files actually exist for that run.

Completion Markers

Rollback

Delete the new table(s) and db/file_catalog.py. Nothing else depends on this phase yet.


Phase 7 — Database-Level Aggregate Metadata

Status: Complete (2026-07-29) — commit 2099d27 on postgres-implementation, in ev-fleet-sim-master.

Preconditions

Tasks

Trigger prompts & tests

| Task | Trigger prompt | Test | |—|—|—| | PG-T7.1 | “Run PG-T7.1: add the global-stats aggregate view/queries.” | Query against known fixture data returns exactly the expected counts/averages | | PG-T7.2 | “Run PG-T7.2: implement get_global_stats().” | Returns correct values against real multi-run data |

Phase-specific guardrails

Agent Self-Test

Manual Verification (human)

  1. Cross-check get_global_stats()’s output against a manual SELECT count(*) ... for each field.

Completion Markers

Rollback

Delete the view/queries and db/dashboard_reader.py.


Phase 8 — Parquet + MinIO Storage Pipeline

Status: Not Started

(This is the piece most likely to belong to Chris/the Parquet-DuckDB side of the original ownership split, not this Postgres-only plan — flagged loudly, not assumed. Confirm ownership via PG-T0.4 before starting, even after Phases 5-7 are done.)

Preconditions

Tasks

Trigger prompts & tests

| Task | Trigger prompt | Test | |—|—|—| | PG-T8.1 | “Run PG-T8.1: confirm/stand up MinIO for this pipeline.” | MinIO reachable, bucket created | | PG-T8.2 | “Run PG-T8.2: implement Parquet conversion for run input/output files, per the existing ADR-storage-partitioning.md strategy.” | Converted Parquet files readable, row-count parity vs. source confirmed (same method as Phase 0’s spike) | | PG-T8.3 | “Run PG-T8.3: implement the Run-ID-based bucket layout.” | Real run’s files land in the correct Input Folder/Output Folder structure | | PG-T8.4 | “Run PG-T8.4: wire file-catalog entries to the real MinIO objects.” | A file-catalog row’s link actually resolves to the real stored object |

Phase-specific guardrails

Agent Self-Test

Manual Verification (human)

  1. Inspect the MinIO bucket structure for a real run directly, confirm it matches the diagram.
  2. Spot-check a converted Parquet file’s contents against its source file.

Completion Markers

Rollback

Remove the MinIO wiring and Parquet conversion step; file catalog (Phase 6) degrades to storing links that don’t resolve, but doesn’t break — Postgres-side data is unaffected.


Summary sequencing

Phase Work Gate to next phase
0 Git reconciliation + run-identity decision Both confirmed in writing
1 Config/connection/schema plumbing, deps, docker-compose Self-test passing on real local Postgres
2 Write path + hook into ev_results_analysis.py Real-run verification, zero JSON regression
3 Read path Round-trip + multi-run verification
4 Tests + CI Suite green, CI decision explicit
5 Scenario & run metadata (execution tracking) Real run lifecycle verified, PG-T0.3/T0.4 resolved first
6 File catalog (inputs/outputs per run) Real run’s file catalog verified against actual files
7 Database-level aggregate metadata Aggregates verified correct against real multi-run data
8 Parquet + MinIO storage pipeline Real round-trip verified, ownership (PG-T0.4) re-confirmed

Do not start a phase’s tasks before the prior phase’s Completion Markers are checked — same gating discipline SIM_DATABASE_IMPLEMENTATION.md uses for its own phases. Phases 5-8 are additionally gated on PG-T0.3/PG-T0.4 (diagram-scope and ownership), unresolved as of 2026-07-27 — do not start Phase 5 until those are answered, even though Phases 0-4 are done.

Do not start a phase’s tasks before the prior phase’s Completion Markers are checked — same gating discipline as SIM_DATABASE_IMPLEMENTATION.md.