Python 3.12+, pandas, DuckDB. No port and no inbound API. A batch pipeline that copies
trading history into a star schema, and a dashboard that reads it.
This service exists because two questions have incompatible access patterns. "What is my balance
right now" wants one row from a normalised table in a few milliseconds. "What did the desk trade last
quarter, by instrument, by week" wants a full scan and an aggregate, and it is allowed to take seconds.
Serving both from PostgreSQL means the second one can starve the first, and the first one is order
placement.
DuckDB is the analytical store. One file, no server, no account, no credentials. The star schema in
contracts/analytics-schema.sql is plain ANSI SQL and runs on it unchanged, which is what
makes the lesson portable: a graduate who can explain why fact_trades is loaded in batch
rather than read live can explain it on any warehouse.
Module names are the packages under analytics/src/analytics. Nothing in this
service listens on a port, and nothing in the platform calls it.
What lives in the DuckDB file
Table
Kind
Holds
fact_trades
fact
One row per executed trade, with foreign keys to the three dimensions and the measures the dashboard aggregates.
dim_account
dimension
Account attributes, with effective dates so a historical trade keeps the attributes it had at the time.
dim_instrument
dimension
Symbol, name, currency and sector.
dim_date
dimension
A calendar, so a query can group by week or quarter without date arithmetic in SQL.
etl_watermark
bookkeeping
How far the incremental load has read. This is what makes etl run resumable.
etl_quarantine
bookkeeping
Rows that failed validation, with the reason. A pipeline that silently drops bad rows hides a defect upstream.
kafka_processed_events
bookkeeping
eventId values already handled by the optional streaming load, so a redelivery does nothing.
stg_market_candles
staging
End-of-day candles pulled from the Fauxnance API, used for price context rather than for trade facts.
Batch and stream, and which one wins
The batch extract from PostgreSQL is authoritative for fact_trades. Consuming
trade-events is optional, and where a team builds it, the streamed rows reconcile against
the batch load rather than replacing it. Two writers to one fact table with no agreement about which is
correct produces a number nobody can defend in a review, which is worse than a number that is an hour
old.
Sprint 4 points the dashboard at PostgreSQL because the analytical store does not exist yet. Sprint 7
moves it. After that, pointing the dashboard back at PostgreSQL is a defect, not a shortcut.