Sense 18: The Ledger — Computational Provenance as Narrative
Sense 18 · In bloom · Last touched 2026-08-20
- last_verified: 2026-06-05
Synced from
docs/design/sense_18_the_ledger.mdin the engine repo — that’s the source; this page is a build-time mirror.
Every number in jinflow has a story. The Ledger tells it.
Status: proposed Date: 2026-04-16
The question
Section titled “The question”A user sees CHF −5,095,660 on the overview tile. A material is flagged high severity. A thesis says confirmed. The natural question:
“How do you know?”
Not “how does jinflow work” (that’s the What is this? page). Not “what’s the DAG” (that’s dbt docs). The question is specific:
How did THIS number come from MY data, through which steps, with what assumptions?
Today jinflow can tell you what it found. It cannot tell a non-technical user how it got there.
The concept
Section titled “The concept”The Ledger is computational provenance rendered as narrative — specific to this tenant, this data, this moment. Like a financial ledger that traces every number back to its source transactions, the analytical Ledger traces every finding back through the pipeline:
Source delivery → Extraction → Bronze → Silver → Gold → Signal → Thesis → VerdictEach step is narrated: what went in, what came out, what was filtered, what was assumed, and what could change the result.
What it looks like
Section titled “What it looks like”Example: Net Exposure headline
Section titled “Example: Net Exposure headline”Net Exposure: CHF −5,095,660
This number is the sum of all material-level net exposures where the polarity is “leakage” (delivered but not billed). It comes from
gold_net_exposure, which computes the arithmetic difference between warehouse deliveries and patient billing per material over the trailing 12 months.Data path:
- Source: 2,375,126 billing events extracted from the source system’s E3-E4 exports (OPALE, Feb 2026 delivery)
- Bronze: structural ingestion, 14 columns preserved
- Silver: validated — 2,371,842 rows passed (99.86%), 3,284 flagged invalid (missing case_id, date out of range)
- Gold: filtered to is_valid=true, joined with material master
- Gold I/O: transfer qty vs usage qty per material × month → net_exposure_chf per material
- Aggregation: sum of abs(net_exposure_chf) WHERE polarity = ‘leakage’
What could change this number:
- A new data delivery (current: Feb 2026)
- Fixing 3,284 invalid billing rows (currently excluded)
- Changing the time window (currently trailing 12 months)
Example: Signal finding
Section titled “Example: Signal finding”signal_negative_margin flagged material 620260 at high severity
Why: billing price (CHF 12.50) is below internal price (CHF 18.30) for 847 billing events in the trailing 12 months.
Data path:
- Billing price from
gold_billing_prices(derived from E3-E4 billing events, averaged by material × month)- Internal price from
gold_article_price_paletteruledefault(E1 catalogue, latest valid entry)- Margin = billing − internal = CHF −5.80 per unit × 847 events
Assumptions:
- Price palette uses the
defaultrule (highest-confidence catalogue entry)- Billing events include all statuses (POSTED, PENDING, REVERSED)
Two phases
Section titled “Two phases”Phase A: The Ledger page
Section titled “Phase A: The Ledger page”A dedicated tenant page at /{tenant}/ledger (or /{tenant}/how) that
walks through the pipeline top-down:
-
Sources — what was delivered, when, by whom, how many rows, SHA-256 verified? Reads from
pipeline.yml+lineage_pipeline_metrics. -
Layers — Bronze → Silver → Gold with row counts, validity rates, filter rationale. Reads from
lineage_pipeline_metrics+silver_quality_metrics+gold_quality_metrics. -
Entities — for each Gold entity, what it represents, how many rows, key columns, what was joined/derived. Reads from
entity_registry+ entity YAML definitions. -
Signals — what patterns were detected, how many findings, what severity distribution. Reads from
signal_registry+ finding counts. -
Theses & Verdicts — what questions were asked, what was confirmed, what root causes identified. Reads from
thesis_verdicts+verdict_findings. -
Net Exposure — the headline financial number, fully traced.
The page is generated at jin make time from data already in the KLS:
lineage tables, quality metrics, registries, entity definitions. No new
computation needed — just narrative assembly.
This is essentially a report (kind: report notebook) with a fixed structure that every tenant gets. The report engine (Step 6) can render it to PDF. JinDesk renders it as a scrollable page.
Phase B: Contextual “explain” panels
Section titled “Phase B: Contextual “explain” panels”From any number on any JinDesk page, click to expand an inline explanation. A small icon (ℹ or ?) next to values that have provenance. Clicking opens a panel that shows the computation path for that specific value.
Phase B is harder because it requires:
- Knowing which query produced the displayed value
- Tracing the query back through the model DAG
- Rendering the explanation in context (not a separate page)
Phase B grows naturally from Phase A — once the narrative templates exist (from the Ledger page), they can be reused in contextual panels.
What already exists
Section titled “What already exists”| Asset | What it provides for the Ledger |
|---|---|
lineage/*.yaml | Data flow declarations (Bronze → Silver → Gold) |
lineage_pipeline_metrics | Per-layer row counts, valid/invalid splits |
silver_quality_metrics | Invalid row counts + reasons |
gold_quality_metrics | Silver-to-Gold row loss |
entity_registry + entities/*.yaml | Entity descriptions + column semantics |
signal_registry | Signal descriptions + finding counts |
thesis_verdicts + interpretations | Thesis status + human-readable explanation |
pipeline.yml | Source declarations with SHA-256 + delivery metadata |
gold_net_exposure | The I/O-based financial truth |
notereportrender.py | PDF rendering engine (metric + table + narrative + chart cells) |
What’s missing
Section titled “What’s missing”- Narrative templates — nobody stitches lineage + metrics + entity descriptions into a readable story for a specific number
- Entry point — no “explain this” gesture from any JinDesk page
- Tenant-specific rendering — the narrative must reflect THIS tenant’s source system (OPALE, not SAP), THIS delivery date, THIS row counts
- Assumption transparency — what filters, what time window, what was excluded and why
- “What could change” section — the most trust-building part: telling the user what would make this number move
Connection to other Senses
Section titled “Connection to other Senses”- Sense 14 (The Signal): signals are the primary “claims” that need provenance. The Ledger explains how each signal reached its conclusion.
- Sense 15 (Observations & Explanations): Explanations are the strategic layer above Verdicts. The Ledger is the operational layer below — it explains the machinery, not the meaning.
- Sense 13 (The Canvas): the Ledger page could be a Canvas page definition (YAML-declared, panel-composed) rather than a hardcoded route.
- Sense 42 (The Landscape): the system surface as a vantage — the Garden (structural view) and the River (temporal view) live inside it. The Ledger is the prose companion to the Garden — same story, different medium.
- Three inputs to Understanding: the Ledger is firmly a Data-stream artefact — it accretes what analytical instruments have said over time. Knowledge (Dossiers, Statements) might annotate the Ledger but doesn’t drive it; Observations cite Ledger entries as evidence through Contributing Factors.
The deeper principle
Section titled “The deeper principle”Dashboards show answers. jinflow shows work.
The Ledger is what makes jinflow auditable. It’s the difference between “trust me, the number is −5M” and “here’s exactly how we got −5M, here’s what we assumed, and here’s what would change it.” In a hospital procurement context, that transparency is not a feature — it’s a requirement.
How this manifests in jinflow
Section titled “How this manifests in jinflow”The Ledger answers “where did this number come from?” for every result. The first running surface is the per-finding provenance panel and the dedicated Ledger page in JinDesk; the deeper computational-narrative form (every dbt model, every join, every signal call that produced a single value) is forming.
-
In the app: Ledger · per-finding provenance on the Findings detail pages
-
Pairs with: Sense 36 The Lineage — Lineage carries pack-to-tenant origin; the Ledger carries within-tenant computation
Status: in bloom. Basic provenance views are live; the full narrative chain (every transform, every reference) is the next layer.
Open questions
Section titled “Open questions”-
Name: “Ledger” vs “Provenance” vs “How” vs “Trail” vs “Receipt”? The name should be intuitive for hospital administrators, not just developers.
-
Granularity: does the Ledger explain every Gold column, or just the headline metrics (net exposure, finding counts, thesis verdicts)? Start narrow, expand based on user feedback.
-
Staleness: the Ledger reflects the last
jin make. If the user changes data and hasn’t rebuilt, the Ledger is stale. Should it show a warning? Should it timestamp visibly? -
Interactivity: is the Ledger a static document (generated once, read many) or does it allow drill-down (click a layer to see the actual rows)? Phase A = static, Phase B = interactive.
-
Multi-tenant: does the platform layer need its own Ledger? Or is provenance always per-tenant?Resolved 2026-06-02 — the platform layer was retired (“the future will not be KLS with multiple tenants”). Provenance is per-tenant, period.
Implementation spec — Surfaces V1 (2026-06-04)
Section titled “Implementation spec — Surfaces V1 (2026-06-04)”Status: spec, awaiting review. Code follows in a separate commit per the CLAUDE.md spec before code for substantive engine work rule.
The Ledger spike on 2026-06-03/04 converged on a clear shape: traces
are kind: report notes. The next question — surfaced by Mig during
the spike review — was “how does the user see that a note is a
Ledger entry and not just any note?”
This spec answers that question with a mechanism wider than the Ledger. It’s structured around the Ledger’s V1 needs but the machinery is general: a note declares which surfaces render it.
What pulled us in
Section titled “What pulled us in”Mig’s framing during the comparison between the hardcoded spike and the note-rendered version:
“Note is missing certain information and disclaimers. Typographically note is more pleasing, and I truly think that we should go the note way. Currently there are no labels set, right? In the future we will mark clearly all sources that choose to use the notebook as an output channel.”
The pre-spike phrasing of this question — “tags for Ledger entries” — was too narrow. The honest framing is broader: a note is content; the notebook view is one surface that renders it; the Ledger is another surface; future surfaces (a dashboard, a weekly digest, a PDF series) are more. Where a note appears should be declared on the note, not on the surface.
The principle
Section titled “The principle”A note is content. A surface is a presentation context — a route, a list, a digest, a PDF series. The note declares which surfaces it belongs to. Each surface decides how to render the notes that named it. Authoring stays in YAML; JinDesk reads the declaration and routes accordingly.
Relationship to Sense 13 — The Canvas
Section titled “Relationship to Sense 13 — The Canvas”The Canvas is about interior composition: a page is built from declarative panels/cells. Surfaces is about exterior routing: an artifact declares which presentation contexts it belongs to. Same Form-Follows-Data conviction, different sides of the handshake.
Each surface (Ledger, Dashboard, …) is conceptually a Canvas page. V1 ships them as SvelteKit routes — predictable, fast. When Canvas matures into “any page from YAML declaration”, surfaces becomes one of the filters a Canvas page reads. Two design constraints flow from this and apply to V1:
surfacesis a flat list of identifier strings, not a richer object. Future Canvas pages can filter on the field (filter: { surfaces: contains 'ledger' }); they shouldn’t need to parse semantic structure inside each entry.- The note never carries surface-specific rendering. It
declares which surfaces it belongs to; the surface decides how
to render it. The same note appearing on
notebookandledgerlooks identical content-wise; only the chrome differs.
If we hold those two lines, surfaces V1 ships now and Canvas absorbs it cleanly when ready.
Schema
Section titled “Schema”A new optional top-level field on note YAMLs:
note_id: nb_ledger_net_exposurekind: reportsurfaces: - notebook - ledger# ... rest of the note unchanged| Property | Detail |
|---|---|
| Type | List of strings |
| Default | [notebook] — every note browseable in the standard notebook view |
| Required | No — absence equals default |
| Empty list | Warning at compile time, treated as default |
| Unknown surface | Hard error at compile time (notecheck.py) |
| Duplicates | Silently deduplicated |
V1 surface vocabulary
Section titled “V1 surface vocabulary”Two values supported:
| Surface | Meaning | Route |
|---|---|---|
notebook | Standard browseable note. Default for every note. | /<tenant>/notebook?note=<id> |
ledger | A trace — a Ledger entry. Appears in the Ledger collection. | /<tenant>/ledger (index), still readable via notebook URL |
Future surfaces (dashboard, digest, etc.) land one at a time when
a real driver arrives. Per the existing
“don’t pre-emptively migrate” discipline, the vocabulary doesn’t
grow until forced.
Where the vocabulary lives
Section titled “Where the vocabulary lives”For V1, hardcoded in two places (mirror to the
sister 42 Q2 conversation
about jinflow_name):
- Python:
scripts/notecheck.py— validator constant - TypeScript:
explorer/src/lib/notes/surfaces.ts(new) — chip rendering + index page filter
When a third consumer arrives, extract to a shared catalog
(jinflow/jinflow_surfaces.py + TS mirror). Don’t refactor
preemptively — same pattern as jinflow_name.
Index page — the Ledger
Section titled “Index page — the Ledger”A new SvelteKit route /[tenant]/ledger/+page.svelte (and
+page.server.ts).
Server load:
SELECT note_id, title_de, title_en, title_fr, title_it, modified_at, kind, surfaces, tagsFROM <schema>.notesWHERE list_contains(surfaces, 'ledger')ORDER BY modified_at DESCRender: a list of cards. Each card shows the note title (in active
locale), modified date, the first metric or table label as a
preview, and the Ledger chip. Click → routes to
/<tenant>/notebook?note=<id>.
The page itself carries a one-line introduction: “Every number in the Ledger has a story. Click a trace to read where it came from, what assumes it, and what could change it.”
When the Ledger collection is empty for a tenant (no notes have
surfaces: [ledger]), render the empty-state with a hint that
pack authors can promote a note by adding the surface declaration.
Chip on note headers
Section titled “Chip on note headers”A small visible badge on the note’s header — one per surface that
isn’t notebook (the default). On the notebook view of a
surfaces: [notebook, ledger] note, a Ledger chip appears next
to the title. Clicking the chip routes to the surface’s index page.
Visual treatment matches existing chips (PostureChip etc.) — small,
unobtrusive, semantic colour. The notebook default surface gets
no chip (it’s the universal context, doesn’t deserve a marker).
Chip label is i18n-keyed (surfaces.ledger.label, etc.) so locales
follow.
Migration story — zero
Section titled “Migration story — zero”All existing notes default to surfaces: [notebook]. Their behaviour
is byte-identical to before. The very first promoted note —
nb_ledger_net_exposure.yaml in numetrix — is the migration:
add the field, watch the chip appear, watch the Ledger index page
populate.
Validation rules (in notecheck.py)
Section titled “Validation rules (in notecheck.py)”| Check | Severity |
|---|---|
surfaces: present but not a list | Error |
surfaces: contains a value outside V1 vocabulary | Error |
surfaces: is empty list | Warning |
surfaces: contains notebook only | Allowed (verbose default) |
surfaces: absent | Allowed (silently defaults) |
Implementation surface (file-by-file)
Section titled “Implementation surface (file-by-file)”| File | Change |
|---|---|
scripts/notecheck.py | New VALID_SURFACES = {'notebook', 'ledger'} constant; new validator call inside the per-note loop. |
scripts/notecompile.py | If notes get baked into a table (verify), include surfaces column. Otherwise no change. |
explorer/src/lib/notes/surfaces.ts (new) | TypeScript mirror of the vocabulary + chip metadata (label-key, route). |
explorer/src/lib/components/notes/SurfaceChip.svelte (new) | Renders a clickable chip per surface. |
explorer/src/routes/[tenant]/ledger/+page.server.ts (new) | Server load queries notes filtered by surface. |
explorer/src/routes/[tenant]/ledger/+page.svelte (new) | Index page. |
explorer/src/routes/[tenant]/notebook/+page.svelte | Mount SurfaceChip next to the note title. |
explorer/src/lib/i18n/locales/{de,en,fr,it}.json | Add surfaces.ledger.label and one-liner intro for the Ledger index page. |
tests/test_notecheck.py | New cases for valid / unknown / empty / absent. |
jinflow-pack-numetrix/notebook/nb_ledger_net_exposure.yaml | Add surfaces: [notebook, ledger]. First adopter. |
docs/design/sister18_sister42_conversations.md | Mark the surfaces question resolved 2026-06-04 once code lands. |
Outlook — what V1 makes possible later
Section titled “Outlook — what V1 makes possible later”- A new pack ships a
dashboard:surface — its notes opt in withsurfaces: [dashboard]. Future Dashboard route reads the same field; no new mechanism. - Per-identity visibility (per the Sense 43 Outlook section)
could be added by introducing
surfaces:overlays — e.g., the surface list resolves through identity/role overlays before filtering. Not V1. - Canvas-rendered surface index — when Canvas
matures, the
/ledgerroute migrates from a SvelteKit route to a Canvas page declaration. Filters onsurfacescontainsledger. The note YAMLs are unchanged.
Out of scope (deliberate deferrals)
Section titled “Out of scope (deliberate deferrals)”- A third surface (dashboard, digest, etc.). One forcing case at a time. The Ledger forces V1; the next pack with a real second-surface driver forces V2.
- Surface-specific rendering hints in the note YAML (e.g., “on the Ledger surface, render only cells 1, 4, 6”). Notes stay content- shaped; surfaces stay form-shaped.
- Per-surface validation rules beyond vocabulary check (e.g.,
“surface
ledgerrequires akind: report”). Not needed for the Ledger spike; revisit when a surface emerges that has real pre-conditions. - The “what could change this number” cell type promotion. Still a
V1
narrativecell. Wait for a second trace before inventing a new cell kind.
Exit criterion
Section titled “Exit criterion”A pack author can add surfaces: [ledger] to any note in
<pack>/notebook/, run jin afs update --do-it && jin make, and
see:
- The note appears on
/<tenant>/ledger - The note’s title in the notebook view carries a Ledger chip
- Removing the field reverts both signals — the note vanishes from the Ledger collection and the chip disappears
with zero Svelte edits and zero pack-internal SQL.
Numerical neighbors: ← Sense 17: The House · Sense 19: The Simulation →