Skip to content

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.md in 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


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 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 → Verdict

Each step is narrated: what went in, what came out, what was filtered, what was assumed, and what could change the result.

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:

  1. Source: 2,375,126 billing events extracted from the source system’s E3-E4 exports (OPALE, Feb 2026 delivery)
  2. Bronze: structural ingestion, 14 columns preserved
  3. Silver: validated — 2,371,842 rows passed (99.86%), 3,284 flagged invalid (missing case_id, date out of range)
  4. Gold: filtered to is_valid=true, joined with material master
  5. Gold I/O: transfer qty vs usage qty per material × month → net_exposure_chf per material
  6. 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)

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:

  1. Billing price from gold_billing_prices (derived from E3-E4 billing events, averaged by material × month)
  2. Internal price from gold_article_price_palette rule default (E1 catalogue, latest valid entry)
  3. Margin = billing − internal = CHF −5.80 per unit × 847 events

Assumptions:

  • Price palette uses the default rule (highest-confidence catalogue entry)
  • Billing events include all statuses (POSTED, PENDING, REVERSED)

A dedicated tenant page at /{tenant}/ledger (or /{tenant}/how) that walks through the pipeline top-down:

  1. Sources — what was delivered, when, by whom, how many rows, SHA-256 verified? Reads from pipeline.yml + lineage_pipeline_metrics.

  2. Layers — Bronze → Silver → Gold with row counts, validity rates, filter rationale. Reads from lineage_pipeline_metrics + silver_quality_metrics + gold_quality_metrics.

  3. Entities — for each Gold entity, what it represents, how many rows, key columns, what was joined/derived. Reads from entity_registry + entity YAML definitions.

  4. Signals — what patterns were detected, how many findings, what severity distribution. Reads from signal_registry + finding counts.

  5. Theses & Verdicts — what questions were asked, what was confirmed, what root causes identified. Reads from thesis_verdicts + verdict_findings.

  6. 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.

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.

AssetWhat it provides for the Ledger
lineage/*.yamlData flow declarations (Bronze → Silver → Gold)
lineage_pipeline_metricsPer-layer row counts, valid/invalid splits
silver_quality_metricsInvalid row counts + reasons
gold_quality_metricsSilver-to-Gold row loss
entity_registry + entities/*.yamlEntity descriptions + column semantics
signal_registrySignal descriptions + finding counts
thesis_verdicts + interpretationsThesis status + human-readable explanation
pipeline.ymlSource declarations with SHA-256 + delivery metadata
gold_net_exposureThe I/O-based financial truth
notereportrender.pyPDF rendering engine (metric + table + narrative + chart cells)
  1. Narrative templates — nobody stitches lineage + metrics + entity descriptions into a readable story for a specific number
  2. Entry point — no “explain this” gesture from any JinDesk page
  3. Tenant-specific rendering — the narrative must reflect THIS tenant’s source system (OPALE, not SAP), THIS delivery date, THIS row counts
  4. Assumption transparency — what filters, what time window, what was excluded and why
  5. “What could change” section — the most trust-building part: telling the user what would make this number move
  • 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.

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.

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.


  1. Name: “Ledger” vs “Provenance” vs “How” vs “Trail” vs “Receipt”? The name should be intuitive for hospital administrators, not just developers.

  2. 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.

  3. 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?

  4. 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.

  5. 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.

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.

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.

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:

  1. surfaces is 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.
  2. 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 notebook and ledger looks identical content-wise; only the chrome differs.

If we hold those two lines, surfaces V1 ships now and Canvas absorbs it cleanly when ready.

A new optional top-level field on note YAMLs:

note_id: nb_ledger_net_exposure
kind: report
surfaces:
- notebook
- ledger
# ... rest of the note unchanged
PropertyDetail
TypeList of strings
Default[notebook] — every note browseable in the standard notebook view
RequiredNo — absence equals default
Empty listWarning at compile time, treated as default
Unknown surfaceHard error at compile time (notecheck.py)
DuplicatesSilently deduplicated

Two values supported:

SurfaceMeaningRoute
notebookStandard browseable note. Default for every note./<tenant>/notebook?note=<id>
ledgerA 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.

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.

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, tags
FROM <schema>.notes
WHERE list_contains(surfaces, 'ledger')
ORDER BY modified_at DESC

Render: 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.

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.

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.

CheckSeverity
surfaces: present but not a listError
surfaces: contains a value outside V1 vocabularyError
surfaces: is empty listWarning
surfaces: contains notebook onlyAllowed (verbose default)
surfaces: absentAllowed (silently defaults)
FileChange
scripts/notecheck.pyNew VALID_SURFACES = {'notebook', 'ledger'} constant; new validator call inside the per-note loop.
scripts/notecompile.pyIf 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.svelteMount SurfaceChip next to the note title.
explorer/src/lib/i18n/locales/{de,en,fr,it}.jsonAdd surfaces.ledger.label and one-liner intro for the Ledger index page.
tests/test_notecheck.pyNew cases for valid / unknown / empty / absent.
jinflow-pack-numetrix/notebook/nb_ledger_net_exposure.yamlAdd surfaces: [notebook, ledger]. First adopter.
docs/design/sister18_sister42_conversations.mdMark the surfaces question resolved 2026-06-04 once code lands.
  • A new pack ships a dashboard: surface — its notes opt in with surfaces: [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 /ledger route migrates from a SvelteKit route to a Canvas page declaration. Filters on surfaces contains ledger. The note YAMLs are unchanged.
  • 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 ledger requires a kind: 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 narrative cell. Wait for a second trace before inventing a new cell kind.

A pack author can add surfaces: [ledger] to any note in <pack>/notebook/, run jin afs update --do-it && jin make, and see:

  1. The note appears on /<tenant>/ledger
  2. The note’s title in the notebook view carries a Ledger chip
  3. 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

jazzisnow jinflow is a jazzisnow product
v0.64.7 · built 2026-09-20 19:48 UTC