Projections¶
Tier: Read · Requirement module: Analytics and BI · Status: ⬜ Not started
Purpose¶
Projections serves every question that is not "what is the operative record right now". Operational dashboards, late-filing trends, sector heatmaps, capital adequacy summaries, AML/CFT trends, complaint statistics, inspection planning, and export packs are all built from denormalised read models fed by change events.
This makes the seventh law true. Quarter-end is when every firm files and every supervisor runs reports; those two activities must not contend for the same rows.
Scope¶
| Capability | Status | Notes |
|---|---|---|
| Operational dashboards | ✅ | Compliance rate, overdue count, high-risk firms, pending reviews |
| Late filing trends | ⬜ | By period, licence category, return type |
| Sector heatmaps | ⬜ | Risk band by category and return, drillable to firm |
| Capital adequacy summaries | ⬜ | Sector-wide early warning distribution |
| AML/CFT trends | ⬜ | STR volumes, PEP exposure, screening gaps (R07–R10) |
| Complaint statistics | ⬜ | Volume, resolution time, root cause (R18) |
| Inspection plans | ⬜ | Risk-ranked candidate lists with the drivers that ranked them |
| Export packs | ⬜ | Board and ministerial reporting bundles |
The pipeline¶
engine state change
│
▼
outbox table ← same transaction as the change
│
▼
Kafka
│
▼
projection consumer ← idempotent, replayable
│
▼
reporting schema ← typed columns, read-only credentials
Two properties this buys:
A projection can never claim something the record does not contain. The outbox is written in the same transaction as the state change, so there is no window in which an event exists without its cause, or a cause without its event.
Projections are rebuildable. Consumers are idempotent and events are retained, so a projection with a bug is fixed by correcting the consumer and replaying. This is the reason projections may be denormalised aggressively — they are derived, and derived data can be thrown away.
Flattening the template shape¶
Submissions are stored as jsonb keyed by field id, which is right for the write path and wrong for
analytics. Projections flatten the fields analysts actually query into typed columns:
| Column | Source | Type |
|---|---|---|
total_assets |
values ->> 'R02-X01' |
numeric(20,2) |
early_warning_ratio |
values ->> 'R02-X08' |
numeric(6,3) |
breaches |
values ->> 'R02-F08' |
integer |
risk_score |
Prism score | smallint |
days_late |
Cadence | integer |
So reporting gets real types, real indexes, and query plans a DBA can reason about, while the write path keeps the flexibility a configurable template demands. The projection definition is generated from the template, so a new field becomes a new column without hand-written DDL.
Staleness, stated¶
Projections are eventually consistent — typically sub-second, but not guaranteed.
Every screen makes an explicit choice, recorded in its specification:
| Screen | Source | Why |
|---|---|---|
| Returns triage register | Projection | Hundreds of rows, seconds of staleness is immaterial |
| Return detail under review | Record | The reviewer is deciding on it now; staleness is unacceptable |
| Sector heatmap | Projection | Aggregate by nature |
| Audit trail on a return | Record | Evidence is read from Seal directly |
A screen that shows projected data displays the projection's lag when it exceeds a threshold, rather than silently presenting stale figures as current.
Owns¶
- Read model schemas and consumers
- Aggregates, trends, and heatmap data
- Export pack generation
Does not own¶
- Any authoritative fact. Every value here is derived and can be rebuilt from the engines.
Open questions¶
- Warehouse boundary. Projection tables in PostgreSQL are sufficient for supervisory scale. Whether a supervisor eventually wants an external warehouse for cross-year analytics is a procurement question, not an architectural one — the events are already the integration point.
- Firm-facing analytics. Should a firm see its own position against anonymised sector benchmarks? Valuable for compliance behaviour, and a disclosure question for the supervisor.
Related¶
- Architecture doctrine — reading never blocks writing
- Stack — the
jsonbargument this section depends on - Prism — scores feeding heatmaps