Skip to content
Last updated: Sep 25, 2026

FinHub — The CFO Aggregation View ​

OwnerClassificationVersionEffectiveNext reviewStatus
Platform EngineeringInternal1.02026-09-152026-12-15Draft

Purpose ​

FinHub is the CFO's cross-module aggregation surface — one payload and one supervisor agent that read across the six finance modules (Cash Application, Collections, CloseHub, Reconciliation, InvoiceHub, Journal) so the executive home shows a single, connected view.

FinHub is not a module. It has no schema, no workflows, and no engine of its own. Every number it shows is produced by a module's own aggregate service — FinHub composes those outputs, it never recomputes them. Concretely it is two pieces: (1) a server-side aggregation controller (cfo-summary), and (2) a FinHub Finni supervisor agent that classifies a CFO question and dispatches it to the right module agent or cross-domain composite. Everything downstream reads FinHub so the map, the grid, the per-app pages, and Finni all agree on the same figures.


1. What FinHub is — and is not ​

FinHubA module (e.g. CloseHub, InvoiceHub)
Owns a DB schema◯ No✅ Yes
Owns workflows / an engine◯ No✅ Yes
Computes its own numbers⚠️ No — reads module aggregates✅ Yes
Surface typeAggregation view + supervisor agentFull CRUD + intelligence triplet
Degrades gracefully✅ Per-module (one failure → null card)n/a
  • ✅ FinHub is a read/compose layer over module aggregate services and a routing layer over module Finni agents.
  • ⚠️ FinHub is not a module triplet (no controller-a / workflow / agent trio of its own). Collections likewise has no schema — it is the AR view of the Cash module's companyledger / collectionqueue tables.
  • ◯ FinHub holds no persisted state. There is no finhub schema.

Source: libs/appsloader/src/finhub/index.ts, libs/appsloader/src/finhub/cfo.aggregates.service.ts.

2. The cfo-summary aggregation ​

One endpoint assembles the CFO home payload server-side, so the client does not fan out to six module endpoints.

PropertyValue
EndpointGET /api/o/finance/finhub/v1/cfo-summary
AuthMounted inside the authenticated guard; tenantInfo / tenantEnabled already on context
Controllerlibs/appsloader/src/finhub/index.ts
ServiceCfoSummaryService.getSummary(ctx) in cfo.aggregates.service.ts

How it assembles (cfo.aggregates.service.ts):

  1. ✅ Resolves each module's tenant-scoped Prisma client from the shared global cache (globalThis.__tenantPrismaCache) the tenant-resolver plugin already populated — reusing the exact instances the module routes created.
  2. ✅ Runs each module's own aggregate service in parallel — e.g. MasterBankItemsAggregatesService (cashapp), TaskAggregatesService + CDGService (closehub), JvProcessingAggregatesService (journal), InvoiceProcessingDataAggregatesService (invoicehub), ReconTrialBalanceAggregatesService (recon). Because each card comes from the same service behind that module's own dashboard, FinHub cannot drift from the module dashboards.
  3. ✅ Wraps every module call in safe(...) — one module failing degrades that card to null and is listed in meta.degraded, rather than failing the whole payload.
  4. ⚠️ For journal / invoice / recon it consumes the persisted, frozen materiality/anomaly fields the modules already wrote (e.g. getJournalIntelligence, getCloseReadinessSummary) — read-only, no recompute.

Payload shape returned: { financialPosition, modules, whatNeedsYou, keyInsights, meta }.

3. cfo.signals — the pure-function curation layer ​

cfo.signals.ts turns the raw module cards into the CFO surface. It is the load-bearing honesty point of this page:

⚠️ cfo.signals are PURE FUNCTIONS over the already-assembled cards. No DB access, no recompute of module numbers, no persisted read-model. Every field is live-computed on each request from cards that were themselves just fetched. There is no finhub table and no materialized cfo_summary — the "read-model" in older design docs is not shipped.

FunctionProducesNotes
deriveNodeSignals(modules)A uniform { health, insight, materiality, anomaly } per nodeOne shape for the map overlay, the grid, per-app pages, and Finni
deriveWhatNeedsYou(modules)The ACT listOnly 4 reason kinds pass the gate: above-doa, close-blocking, material-anomalous, deadline. Ranked by reason weight then $-severity; bounded to ~top-5 on the landing
deriveKeyInsights(modules)The KNOW listOnly CONNECTED (≥2-module) "so what" a point tool can't see — e.g. close-at-risk because AP unposted + AR past-due
  • ✅ fmtReasons(...) surfaces the real persisted reason JSON the engines logged at assess time — the badge authors nothing; it reads detail/message/reason/code/kind.
  • ⚠️ Health thresholds (e.g. cash rate ≥ 85 → green), the ACT membership rules, and the financialPosition.atRisk roll-up (unapplied + past-due AR + aged-90 AP) are defined in this file — they are curation, computed live, not stored.
  • ◯ Invoice per-node anomaly is a stubbed { count: 0, exposure: 0 } — a TODO(fraud) marks the fraud-score rollup as not-yet-wired.

Source: libs/appsloader/src/finhub/cfo.signals.ts.

4. The FinHub Finni supervisor ​

FinHub Finni is not a fixed-tool agent — it is a supervisor. It LLM-classifies a CFO question, then either runs a small composite graph (for genuine cross-sub-ledger synthesis) or dispatches to a single module's agent (which owns the full focused toolset, including drill-downs). This preserves the "few-tools-per-agent" principle and fixes drill-down dead-ends.

Source: packages/finance/financeagents/src/agents/finni-agent/index.ts (finniFinhubGraph, classifyFinhubRoute, FINHUB_DOMAIN_GRAPHS), finhub-classify-prompt.md, finhub-composite-systemprompt.md, finhub-composite-tools.ts.

Routing (classifyFinhubRoute → 12 routes total):

KindRoutesHandled by
Cross-domain composite (6)cashflow, workingcapital, closereadiness, cashposition, attention, approvalsThe single composite graph (finni-finhub-composite); its LLM picks the matching composite tool
Single-domain (6)cash, collections, payables, reconciliation, journal, closeDispatched to that domain's own Finni graph

✅ 6 domain graphs are wired (FINHUB_DOMAIN_GRAPHS): cash → finniCashAppGraph, collections → finniCollectionsGraph, payables → finniInvoiceGraph, reconciliation → finniReconciliationGraph, journal → finniJournalGraph, close → finniClosehubGraph.

Composite tools (finhubCompositeTools): recommendCashflowActions + getCloseReadiness, getCashPosition, getCfoAttention, getWorkingCapital, getWorkingCapitalTrend, getApprovals.

Dispatch mechanics:

  • ✅ Classification uses the structured-output model (DEFAULT_FINNI_MODEL, default qwen3) against FINHUB_CLASSIFY_PROMPT; on any error it falls back to cash.
  • ✅ Context-inheriting follow-ups: a follow-up chip round-trips its parent answer's route as config.configurable.finniRoute; if valid, classification is skipped so the follow-up stays in its parent's domain.
  • ✅ Single-domain answers are stamped with their route (stampFinhubRoute); composite answers are left unstamped so their gate-drill follow-ups re-classify to whichever domain they name.
  • ✅ On domain dispatch, Finni elevates role: "controller" (portfolio view) and resolves the current close periodId.

5. Relationship to FIG (mission-control lens) ​

The "mission control" map is the CFO landing lens: nodes = modules, coloured by health, with the ACT/KNOW lists beside them.

Today (shipped)FIG (future substrate)
Data pathApps call their own aggregate services via cfo-summaryA per-tenant graph (fig.entity + fig.relationship) that FinHub and apps read as lenses
Graph substrate◯ None — no fig.* schema exists◯ Design only — not yet built
Cross-module "so what"Derived in cfo.signals (deriveKeyInsights)Graph traversal
  • ✅ The mission-control lens works TODAY without any FIG graph substrate. The connected numbers come from cfo-summary + cfo.signals, not from a graph.
  • ◯ FIG is the future substrate that would replace the per-app fan-out with a single traversable graph. No FIG code exists yet — see FIG — Design.
  • ⚠️ The front-end map component (FinConnectedMap.vue) is NOT in this repo — it lives in the separate blitz-ui repo. Only code comments here reference it (e.g. the recon trial-balance aggregate powers "the FinConnectedMap recon node"). This page documents the backend surface it reads.

Revision history ​

VersionDateAuthorChange
1.02026-09-15Platform EngineeringInitial draft — cfo-summary aggregation, cfo.signals pure-function curation, FinHub Finni supervisor (12 routes → 6 composite + 6 domain graphs), FIG relationship.

Finaisse Internal — Confidential. Access-restricted; not for external distribution.