# AS0572397 — Regulatory Reporting (CBSL) · Python + React

A distributed system forward-engineered from the semantic network of the
**AS0572397** EUC, a regulatory submission workbook for the
**Central Bank of Sri Lanka**.

Primary input: `asset_kg_AS0572397.json` (2,976 nodes / 9,728 relationships).
Seed values: `hsbc_as0572397.cells.json` (660,087 cells).
**The `.xlsx` file was never read** — everything comes from the Neo4j graph.

---

## Running it

Requires **Python 3.11+** and **Node 20+**. The seed ships ready in the
repository — `make seed` is only needed after a new crawl.

`make install` creates a **local venv** in `.venv/`. On macOS with Homebrew
Python this is not a preference: `pip install --user` is refused (PEP 668), so
the venv is the only path that leaves the system Python alone.

```bash
make install               # creates .venv/ + fastapi, uvicorn, httpx, pytest + npm install
make test                  # 152 tests: domain, API, oracle, traceability

# in two terminals:
make api                   # gateway on http://127.0.0.1:8080  (docs at /docs)
make web                   # frontend on http://127.0.0.1:5173

bash tools/demo_state.sh    # populates period 2025-12 up to CALCULATED
```

Then open `http://127.0.0.1:5173` and click **open** on period 2025-12.

**On Windows** there is no `make` and the venv lives in `.venv\Scripts\` — the
command-by-command walkthrough is in [`RUN-WINDOWS.md`](RUN-WINDOWS.md).

Other targets:

```bash
make oracle     # recompute and check against the workbook's COMPUTED cells
make matrix     # regenerate TRACEABILITY.md from the live registries
make up         # Compose: 10 services + Postgres + RabbitMQ + MinIO
```

---

## The nine services

The reference prompt fixes six services inherited from AS0558844
(Workflow · Source · Risk · Controls · Reporting · Audit). **They do not fit
here.** Each of the 11 Asset 5 epics declares its owning service in
`implementation_footprint.candidate_services`. There are nine:

| service | bounded context | epics | rules |
|---|---|---|---|
| `source` | Source Acquisition and Reference Mapping | EPIC-01 | BR-001…005 |
| `classification` | Key Construction and Regulatory Classification | EPIC-02 | BR-006…012 |
| `currency` | Currency Treatment | EPIC-03 | BR-013…016 |
| `statement` | P&L + Balance Sheet Consolidation | EPIC-04, 05 | BR-017…024 |
| `intercompany` | Intercompany Analysis | EPIC-06 | BR-025…026 |
| `controls` | Adjustment and Reconciliation | EPIC-07 | BR-027…035 |
| `returns` | Regulatory Return Preparation | EPIC-08 | BR-036…038 |
| `submission` | Submission Preparation and Validation | EPIC-09 | BR-039…040 |
| `governance` | Documentation and Change Control + Presentation | EPIC-10, 11 | BR-041…044 |

`risk` disappears — there is no exposure, RWA or capital in this workbook.
`workflow` is absorbed by the gateway — there is no orchestrating macro, the
sequence is the cross-sheet formula dependency.

---

## Language

The application is **English throughout**. The semantic network is not: the nouns
(Tier-1, Tier-2, logical element names, subject areas, data groups) came out in
English, the prose (Tier-3, epic titles, the 44 rule names, entry points, flows,
definitions) came out in Portuguese.

The registry ships **in English on disk**, not as a Portuguese file with a
runtime layer over it — a reviewer who opens
`registry/semantic_registry.json` finds English.

| file | what it is |
|---|---|
| `registry/semantic_registry.pt.json` | the verbatim generation from `asset_kg_AS0572397.json`, never edited |
| `registry/translations.en.json` | the PT→EN map, keyed by the same stable IDs |
| `registry/semantic_registry.json` | what the services load — produced by `make registry` |

Provenance holds: the `.pt.json` is byte-identical to what the Asset KG Builder
produced, so any claim traced back to the asset still checks out, and the
translation is reviewable side by side. `REGISTRY_LANG=pt` reads the original.

`make registry` refuses to write a half-English registry: it aborts if the map
has a gap, and again if any Portuguese survives into the output. A test asserts
the same thing — a missing key would otherwise ship a Portuguese label into an
English UI.

---

## The decisions holding up the design

### First-class `Unresolved`

`shared/as0572397_shared/values.py`. Three states, never conflated:

| state | when | UI |
|---|---|---|
| `not_applicable` | the rule does not bear on the line | `—` |
| `not_found` | lookup with no match (BR-012) | `NOT FOUND` |
| `unresolved` | an input is missing upstream | `UNRESOLVED` |

The arithmetic propagates absence. **Never zero-for-unknown.** BR-012 has 7,256
occurrences of IFERROR/ISNA/IFNA in the workbook, existing precisely to hide the
lookup that failed.

### One writer per record

Nine rules write into an element owned by another service — BR-004, BR-011,
BR-015, BR-016, BR-028, BR-030, BR-032, BR-034 and BR-035. That is a fact of the
graph: the spreadsheet has no boundary. The domain function lives in the service
of its epic; the write leaves by event and the **element's owner is the only
writer**. `tests/test_semantic_coverage.py` asserts exactly that set of nine.

### The LE060 cycle

F3 writes LE060 as the operator's manual adjustment; F7 writes it again, fed back
by the regulator confirmation (38 formulas on the `CBSL Confirmation` sheet).
LE060 is `AppendOnly`. Without the `adjustment_source` discriminator the sum
counts it twice. Manual posting is locked once the confirmation arrives.

### The state guard lives in the gateway

`DRAFT → OPEN → CALCULATED → SUBMITTED → CONFIRMED`. In AS0558844 the guard only
covered the workflow service's own commands, and variance ended up rebuilt
against a period that was never frozen.

### Exactly one observed error code

`INTERCOMPANY_COUNTERPARTY_NOT_IN_LIST` — flow F5, decision D1, backed by the
workbook's 11 `ExcelDataValidation`. The other seven codes are **modernisation
recommendations** and carry `evidence: "recommended"` in the API response and in
the UI.

### Seven editable fields out of 88

Asset 2 marks 30 elements `Authoritative`, but authoritative ≠ editable:
**7 operator input · 21 ingested · 47 computed · 13 reference**.
LE069 is the only closed-list field in the whole system.

### The period is a 20-dimension POV

The workbook's "period" is not a date. The `Links` sheet holds an HFM-style POV —
`Scenario#IFRS · Year#2025 · Period#Dec · View#YTD · Entity#4225 · …` — built by
concatenating label and value, the same mechanic as BR-006. The comparative
changes one dimension: the **previous month**, not the previous year. The Phase 1
inference said previous year; the equivalence oracle brought it down.

---

## Three defects found during generation

**1. Column offset between Asset 2 and the cells.** Asset 2's `column_ordinal` is
1-based; the crawler's `ExcelCell.column` is 0-based. The first extract read every
column one to the right: `Sr_no_` became `Br`, and 1,346 `#N/A` were attributed to
`GRCA_REF_Final`. It surfaced in a screenshot — every composite key came out as
`HSBLA`. Fixed in `tools/build_seed.py`, checked against each sheet's row-0 header.

**2. BR-008 was altering the key.** The first implementation prepended the
exception prefix. That stopped 682 of the 1,410 rows resolving in the BSP-GRCA
table. The evidence supports the branch existing (a `LEFT(...,1)` comparison
against a literal), not the branch changing the key: `a/l + BSP2` reproduces
`ALBSP_Final` on **1,410 of 1,410 rows**.

**3. The root `tests/` was never collected.** `testpaths = services tools` in
`pytest.ini`. The file asserting coverage of the 44 rules was not being run. With
it running came a correction: the cross-boundary writes are **nine**, not the six
Phase 2 counted.

---

## Named gap

Cell column 12 of the `SS616` sheet **has no header** and Asset 2 does not map it
(ordinal 13 is missing from the `columnMap`). It carries `#N/A` on **1,346 of the
1,410 rows** — a lookup that fails in the workbook itself. It is recorded and
visible on the classification screen, not silenced.

---

## Layout

```
shared/as0572397_shared/   values · errors · rulekit · traceability · runstate · events · store · i18n
                          registry/semantic_registry.json   (generated from the Asset KG)
                          registry/translations.en.json     (PT->EN, reviewable side by side)
services/<svc>/<svc>_svc/  domain.py — one function per rule, decorated with @reg.rule
services/gateway/          app.py · wiring.py · pipeline.py  (BFF + 13 F1 stages + SSE)
frontend/src/              16 modules · components/Value.tsx is the heart of the UI
seed/workbook_seed.json    the workbook's original values
tools/                     build_seed.py · equivalence_oracle.py · generate_traceability_matrix.py
```

## Tests

152, all green. `TC-BR-0NN-B1` / `-B2` are Asset 6's 88 cases (two branches per
rule); the rest is registry completeness, element ownership, translation
completeness, the oracle gate and the API contract.

```
services/*/tests/test_*_rules.py       the two branches of each rule
services/*/tests/test_*_registry.py    registry.missing() == set()
services/gateway/tests/                state guard, rejection, F1 pipeline
tests/test_semantic_coverage.py        44 rules bound · 88 elements uniquely owned
```
