Skip to content

About

Two-engine EV charging platform — Oracle money path + TimescaleDB telemetry + OCPP 1.6J gateway. Race-tested in CI on both engines.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

101 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VoltHub CSMS — two engines, one transaction boundary: Oracle 23ai money path, TimescaleDB telemetry, OCPP 1.6J gateway, race-tested in CI
VoltHub CSMS — mission card

🎮 MISSION BRIEFING

🎯 OBJECTIVE One transaction boundary across two database engines — without losing a single cent
🧠 INTEL Oracle 23ai owns the ACID money path · TimescaleDB owns telemetry · OCPP 1.6J gateway drives a simulator fleet
💣 BOSS FIGHT Race conditions — respawned in CI on every push until they stayed dead
🏆 LOOT CI + release pipelines · Codecov coverage · OpenSSF Scorecard · typography-led Next.js ops console
🎮 DIFFICULTY ★★★★★ · no hard mode, just engineering

Two-engine EV charging management: Oracle for ACID billing/reservations, TimescaleDB for telemetry — driven by an OCPP 1.6J gateway + simulator fleet, race-tested in CI on both database engines, with a typography-led Next.js operations console.

ci release codecov OpenSSF Scorecard Ask DeepWiki node oracle timescale license docs site

New here? Pick a 5-minute path.

You are… Read, in this order
🧭 Recruiter / curious the 30-second demo below → What's actually here → the docs site → docs/resume-bullets.md
🔧 Engineer Quickstart → ARCHITECTURE.md → ADR index → run the race suite yourself
🔍 Reviewer / sceptic ADRs (trade-offs named) → docs/verification.md (every claim has a receipt) → docs/perf.md (numbers, methodology) → SECURITY.md
72 2 7 16 6 0
Spec'd API paths, OpenAPI drift-gated DB engines behind one store port PL/SQL packages own the writes ADRs, trade-offs named CI jobs on every push known CVEs, npm audit gated

VoltHub CSMS architecture — Oracle money path + TimescaleDB telemetry joined by an outbox relay

Live OCPP session: real MeterValues drive kWh/kW/cost in the UI
Live session — real OCPP MeterValues tick the UI (re-record anytime: scripts/screenshots/ — see its README).

Grid Current home — editorial hero with live gateway state Operations dashboard
Live charge session Telemetry load curve Itemized invoice
Real pixels from the running stack (API :4000 + OCPP 1.6J WebSocket session + web console). More at the documentation site.

30-second demo

Two terminals reserve the same connector → one 201 BOOKED, one 409 OVERLAP → plug-in → live kWh/kW/cost → itemized invoice → wallet pay. (Record the terminal race with scripts/demo.sh.)

Stack, and why (trade-offs, not fashion)

Choice Why (and the trade-off accepted)
Oracle 23ai for the money path row-lock semantics + PL/SQL packages own the writes (guard trigger blocks direct connector.status UPDATEs). Trade-off: a real Oracle dependency — CI runs it as a service container every push
TimescaleDB for telemetry 90 %+ of rows are immutable time-ordered ticks — hypertables/caggs/compression are the right shape. Trade-off: caggs can only query one hypertable, so enrichment is query-time views + a relay-maintained station_map
outbox + relay between them same-transaction outbox on Oracle + ack-after-COMMIT relay gives effectively-once without a broker. Trade-off: single-writer worker, not a fan-out bus
Express 5 + pino 10 boring, dependency-light API core; request-ID error envelopes; drift-gated OpenAPI. Trade-off: hand-rolled middlewares instead of a framework galaxy
Next.js 16 + React 19 App Router with real client components; CSP set at the Next layer. Trade-off: server/client boundary discipline is on the team
Node 22 LTS CI runs a 20/22 matrix so engines >=20 stays honest; images ship 22
eslint 10 flat config + prettier + npm audit gate the lint gate is deliberately stricter than the pre-flat era (it immediately caught two latent issues); npm audit fails CI on either lockfile
c8 coverage → Codecov (informational) the coverage badge is a receipt, not a target — unit-tier suites instrumented with V8 coverage on every push; DB-backed suites stay in db-tests on purpose
Compose + Caddy, one VM honest deploy target (ADR-0001); graceful SIGTERM drains matched to compose stop-grace periods. Trade-off: no K8s story by design

Why two databases (4 lines)

  • Sessions/billing are ACID-relational (money path, overlap exclusion, ledger) → Oracle 23ai, with the money logic in PL/SQL packages (RESERVATION_PKG, CHARGE_SESSION_PKG, BILLING_PKG) — not in app code.
  • 90%+ of rows are immutable, time-ordered meter ticks read as window aggregates — the wrong shape for a row-store → TimescaleDB hypertables + 1m/1h continuous aggregates + 7-day compression + 90-day retention.
  • Same event, two resolutions: every OCPP MeterValues writes METER_READING (billing record, Oracle) and meter_tick (analytics record, Timescale) via an outbox + relay (at-least-once delivery, idempotent replay = effectively-once).
  • Race safety is proven, not claimed: CI fires parallel double-reserves and double-pays and asserts exactly one winner — against the in-process store and against real Oracle row locks.

System at a glance

flowchart TB
  W["Next.js web console (Grid Current)"] --> REST
  SIM["OCPP 1.6J simulator fleet<br/>normal · race · fault · no-show · burst"] --> GW
  SIM --> REST
  subgraph API["apps/api — one port :4000"]
    REST["REST /api/v1 — 66 spec'd paths, OpenAPI drift-gated"]
    GW["OCPP 1.6J gateway<br/>(WS · Basic auth · 10 msg/s)"]
  end
  REST --> ST
  GW --> ST
  subgraph ENG["the two engines (store port, ADR-0005)"]
    ORA[("Oracle 23ai<br/>29 relations · 7 PL/SQL packages<br/>money path — reservations · billing · ledger")]
    TS[("TimescaleDB<br/>hypertables · caggs · compression<br/>telemetry path — meter ticks")]
  end
  ST -- "write-through pkg calls" --> ORA
  ORA -- "hydrate on boot" --> ST
  ST --> OB["outbox (in-transaction)"]
  OB --> REL["apps/worker · 2 s · batch · ack-after-COMMIT<br/>idempotent replay ⇒ effectively-once"]
  REL --> TS
  REST --> MX["/metrics · /health/deep"] --> GF["Grafana"]
Loading

Mermaid renders natively on GitHub. Full 1-page version + ADR table: ARCHITECTURE.md.

What's actually here

Layer Evidence
Oracle OLTP 29-relation BCNF-honest schema, 7 PL/SQL packages, connector-state guard trigger, least-privilege role (no DELETE anywhere), MV + scheduler — db/oracle/V001–V006
Concurrency SELECT … FOR UPDATE / SKIP LOCKED bulk expiry, error bands -2050x…-209xx mapped to HTTP once (src/errors.js), race suites in CI on both engines — apps/api/test/race.js
OCPP 1.6J WebSocket gateway with HTTP Basic auth on upgrade (Security Profile 1), per-CP credentials, 10 msg/s limit, 7 core messages + Smart Charging (SetChargingProfile/Clear/GetCompositeSchedule) + 10 awaited management commands (Reset, UnlockConnector, ChangeAvailability, Get/ChangeConfiguration, TriggerMessage, ReserveNow, CancelReservation, ClearCache, DataTransfer) with per-command accepted-status sets and audit — apps/api/src/ocpp/, docs/ocpp-1.6-conformance.md
AI (advisory) 87-station pooled panel from three public datasets (Tennessee, Norway, Korea) · 754,032 station-hours · MLP MAE 1.906 kWh, MASE 0.640, 36.0 % skill vs seasonal-naive, conformal coverage 0.803 · model card, occlusion explanations, PSI/KS drift — apps/ai/, ADR-0008
CarbonPilot Deterministic model-v1 grid intensity (average for accounting, marginal for scheduling), 48 h forecasts, SHA-256 session receipts, site summaries, carbon-aware scheduling at weight 0 by default — apps/api/src/carbon/, apps/web/src/app/carbon/, ADR-0017
Control loop (durable) The loop is proven through the DB engine, not just the local store: test/e2e/control-plane.js drives certify → plan → actuate → typed outcome → durable audit over HTTP against the compose stack and refuses to run unless /health reports mode: oracle
Control loop FC-HCC, closed end-to-end: sense → certify (worst-case admission, taper-aware) → schedule (acceptance-envelope, feasibility-first then price-aware) → compile → actuate with CALLRESULT ack correlation → verify against metered kW. Optimizer-independent envelope rejects cap-violating pushes (-20903), pushes are idempotent per (cp, decision) and rate-governed 6/min/CP; per-site mode defaults OFF until receipts exist — apps/api/src/control/, ADR-0010/0011/0014/0015
Twin + experiments Deterministic generative fleet (arrivals, SoC, CC-CV taper, ToU, non-compliant chargers) with a shared scoring harness — apps/simulator/src/twin.js. Receipts are reproducible from their seeds alone (scenarios are anchored to a fixed UTC epoch). E1b: controller −46.7 cost units vs static-cap (95% CI [−48.3, −45.0], 200 paired seeds), 0 calibration shortfall on 1,687 admitted vehicles. E3: admission tracks load; and the taper-binding family fails (42.6% shortfall, no deadline advantage over static-cap at ρ=0.9) — published as-is. E6 repairs it without editing E3: acceptance-envelope scheduling cuts shortfall 42.6% → 2.36% and deadline miss 56.6% → 15.7%, with plan-honesty gap falling from 1,280 kWh → 0.011 kWh per 100-seed run and zero change below the knee — docs/perf.md, ADR-0015
DA3 pipeline Outbox → 2s relay → hypertables → caggs; crash-after-COPY replays safely — apps/worker/, db/timescale/
API 82 spec'd paths, OpenAPI 3.0 with a CI drift gate + a generated-snapshot check, Idempotency-Key replay, keyset pagination, request-ID error envelopes — apps/api/src/
Web 18 routes, "Grid Current" design system (Bricolage Grotesque / IBM Plex Sans + Plex Mono tabular numerals), installable PWA with offline page, zero chart dependencies, unified PageState + toasts + URL-as-state — apps/web/
CI 6 jobs: lint (Node 20/22 matrix) · security (npm audit gate, both lockfiles) · quality · coverage (c8 → Codecov) · db-tests (Oracle + Timescale service containers) · e2e (full compose). The control-loop properties, the AI advisory contract, the twin properties, the OpenAPI snapshot freshness and the durable control-plane journey (npm run test:e2e:control) all gate on every push — .github/workflows/ci.yml
Releases tag-driven GitHub Releases with notes extracted from a Keep-a-Changelog CHANGELOG.md; conventional commits throughout — .github/workflows/release.yml

Honest limits

  • The local profile (no Docker) runs an in-process store mirroring package semantics for fast iteration/tests; ORACLE_HOST/TS_HOST arms the durable two-engine path (ADR-0005).
  • Chargers are simulated (SteVe precedent — real hardware interop is out of scope); payments are a prepaid wallet (no card data, ever); single-VM Compose, not K8s (ADR-0001).
  • OCPP scope is the 1.6J core profile + Smart Charging + the ten management commands in ADR-0018; GetDiagnostics and firmware update remain out of scope (no file store/signature/rollback contract). Online authorization only — no local auth list. Migration path to OCPP 2.0.1 (IEC 63584): TransactionEvent replaces Start/Meter/Stop — the store surface is protocol-neutral by design. Full statement: docs/ocpp-1.6-conformance.md.
  • No performance numbers are claimed here — see docs/perf.md (methodology + 5 reproducible experiments; measured tables land from bench/ scripts only).

Quickstart

# 1) local, no Docker: API :4000 + web :3000
npm install
npm run dev:api &                      # seeded demo data, /api/v1/health
cd apps/web && npm install && npm run dev

# 2) full stack (Oracle 23ai + TimescaleDB containers) — works with no .env at all
#    If you created a root .env (see DEPLOY.md), pass it or it is silently ignored:
#      docker compose --env-file .env -f infra/docker-compose.yml up --build
docker compose -f infra/docker-compose.yml up --build

# 3) races + tests + coverage
npm run test -w apps/api && npm run test:race -w apps/api
npm run coverage                       # c8 line coverage over the unit-tier suites
node apps/simulator/src/index.js --scenario race   # expect exactly one 201 + one 409

Demo logins: arjun@volthub.in / Operator@123 · any seeded driver / Driver@123. The admin credential is not published — set ADMIN_BOOTSTRAP_PASSWORD before seeding (see DEPLOY.md §8).

Status & releases

  • Current release: v1.7.0 — tagged releases carry notes extracted from CHANGELOG.md (Keep-a-Changelog / SemVer).
  • Engineering history is fully auditable: conventional-commit breadcrumbs (fix(oracle): …, ci(e2e): …) — including the failures and their fixes.
  • Roadmap notes (OCPP 2.0.1 migration path, scale exits): ARCHITECTURE.md · docs/masterplan/18-da-plans-and-roadmap.md.

Docs map

What Where
Docs site (Pages) humoge7502.github.io/VoltHub-CSMS — 1-page visual tour of this repo
Masterplan (DA1→DA2→DA3) docs/masterplan/ + polished PDF docs/VoltHub-CSMS-Engineering-Masterplan.pdf
Architecture (1 page + diagram) ARCHITECTURE.md
Decision records docs/adr/0001 modular monolith · 0002 TimescaleDB (4.90/5) · 0003 outbox pipeline · 0004 plain-JS divergence · 0005 store adapter · 0006 connector FK-native · 0007 OCPP remote commands · 0008 AI advisory sidecar · 0009 multi-tenancy (deferred) · 0010 FC-HCC control loop · 0011 control-path integrity · 0012 twin profile compliance · 0013 benchmark methodology · 0014 control-loop closure · 0015 acceptance-envelope scheduling · 0016 wallet signature rail · 0017 carbon-aware charging · 0018 OCPP management commands
OCPP conformance docs/ocpp-1.6-conformance.md — implemented messages, per-command status sets, explicit gaps, test evidence
Wallet rail (signed authorisations) docs/wallet.md — EIP-191 personal_sign, single-use challenge, amount inside the signed text, server-side signer recovery, replay refused with 409 ALREADY_SETTLED; no card data, no keys, no gas (ADR-0016)
Security posture SECURITY.md
Contributing (the full bar) CONTRIBUTING.md — testing ladder, ground rules, ADR-first changes
Performance methodology docs/perf.md
REST contract (OpenAPI 3.0) docs/openapi.json — generated snapshot of the live /api/v1/docs spec (npm run openapi:gen); route coverage drift-gated and snapshot freshness byte-checked in CI
AI platform (advisory-only) docs/ai-platform.md — three-dataset forecasts, anomaly triage, smart-charging LP, model card, explanations, drift; measured CPU receipts; guardrails in code (ADR-0008)
CarbonPilot (advisory-only) docs/adr/0017-carbon-aware-charging.md — average vs marginal intensity, receipts and why carbon cannot block a session
Demo beats docs/demo-script.md
ER / architecture / race diagrams diagrams/*.mmd (Mermaid)
Verification receipts docs/verification.md (every claim names what actually ran)

License

MIT.


Also building Q-Trust — post-quantum cryptography migration & attestation: CBOM scanning, NIST/CNSA 2.0 scoring, GNN-ranked migration planning, attestations sealed on Base L2. The two projects share one discipline: trade-offs named, claims receipted, races proven in CI.

About

Two-engine EV charging platform — Oracle money path + TimescaleDB telemetry + OCPP 1.6J gateway. Race-tested in CI on both engines.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages