FinHub — The CFO Aggregation View
| Owner | Classification | Version | Effective | Next review | Status |
|---|---|---|---|---|---|
| Platform Engineering | Internal | 1.0 | 2026-09-15 | 2026-12-15 | Draft |
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
| FinHub | A 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 type | Aggregation view + supervisor agent | Full 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/collectionqueuetables. - ◯ FinHub holds no persisted state. There is no
finhubschema.
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.
| Property | Value |
|---|---|
| Endpoint | GET /api/o/finance/finhub/v1/cfo-summary |
| Auth | Mounted inside the authenticated guard; tenantInfo / tenantEnabled already on context |
| Controller | libs/appsloader/src/finhub/index.ts |
| Service | CfoSummaryService.getSummary(ctx) in cfo.aggregates.service.ts |
How it assembles (cfo.aggregates.service.ts):
- ✅ 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. - ✅ 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. - ✅ Wraps every module call in
safe(...)— one module failing degrades that card tonulland is listed inmeta.degraded, rather than failing the whole payload. - ⚠️ 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.signalsare 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 nofinhubtable and no materializedcfo_summary— the "read-model" in older design docs is not shipped.
| Function | Produces | Notes |
|---|---|---|
deriveNodeSignals(modules) | A uniform { health, insight, materiality, anomaly } per node | One shape for the map overlay, the grid, per-app pages, and Finni |
deriveWhatNeedsYou(modules) | The ACT list | Only 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 list | Only 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 readsdetail/message/reason/code/kind. - ⚠️ Health thresholds (e.g. cash
rate ≥ 85 → green), the ACT membership rules, and thefinancialPosition.atRiskroll-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 }— aTODO(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):
| Kind | Routes | Handled by |
|---|---|---|
| Cross-domain composite (6) | cashflow, workingcapital, closereadiness, cashposition, attention, approvals | The single composite graph (finni-finhub-composite); its LLM picks the matching composite tool |
| Single-domain (6) | cash, collections, payables, reconciliation, journal, close | Dispatched 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, defaultqwen3) againstFINHUB_CLASSIFY_PROMPT; on any error it falls back tocash. - ✅ 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 closeperiodId.
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 path | Apps call their own aggregate services via cfo-summary | A 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 separateblitz-uirepo. 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.
Related pages
- Finni Architecture — Runtime & CFO Co-Pilot
- FIG — Finaisse Intelligence Graph (Design)
- Platform Architecture
Revision history
| Version | Date | Author | Change |
|---|---|---|---|
| 1.0 | 2026-09-15 | Platform Engineering | Initial draft — cfo-summary aggregation, cfo.signals pure-function curation, FinHub Finni supervisor (12 routes → 6 composite + 6 domain graphs), FIG relationship. |