Skip to main content
CFDL

Statements

A statement is the artifact a practitioner recognizes: an operating pro forma, a remittance report, a free cash flow build-up. CFDL produces one per period from the model you already wrote — you declare how it is organized, or take one from a pack or the default, and never maintain its numbers alongside the model.

Every run's results carry a statements section — in the playground it is the Statement tab. A statement comes from one of three places: the model declares one, the active pack ships one, or the engine renders the entity hierarchy as a default, marked default. The default is assembled when results are rendered, never enters the compiled document, and yields to any declared statement. A statement the model declares replaces the pack's statement of the same name.

Declaring one

A declared statement is generated or authored, never both and never neither (E1369).

A generated statement names an existing hierarchy — structure entity (the part of tree) or structure category (the dotted path) — and cuts it at a depth. It enumerates no rows: a node whose children are shown is a subtotal, a node whose children are cut off is a line carrying all of its descendants' cash, so the lines partition the cash at every depth. It may carry a label, a grain, a slice filter (orthogonal to the structure), and metrics naming declared metrics to publish beside it.

statement portfolio {
  label     "Portfolio by property"
  structure entity
  depth     2
}

An authored statement states its own rows — line, subtotal, ratio, spacer — each drawing from a category, stream, slice or entity, from a contract type and its line by role, or presenting a published series (series "domain.cre.noi"). A subtotal folds rows stated elsewhere and claims nothing; a series row is a fold OF the ledger rather than cash in it, so it claims no streams, stays out of the bottom line, and refuses a claim clause beside it (E1370); a ratio divides two operands, each a declared slice or a pack subtotal, and publishes null where the denominator is zero. This is the form for a pro forma: curated labels, expenses shown positive under "Less:", a coverage ratio that is a node of no hierarchy.

What makes a generated statement work

Three things, and only the first is something you write.

A category says what a stream is, economically. It is a dotted path whose first segment is operating, investing or financing — the sections of a statement of cash flows. A pack's contracts classify the streams they emit, so in most models you never write one. A hand-written stream can declare its own:

stream tower.parking on entity asset.tower inflow currency USD {
  schedule every month from 2026-01 to 2035-12
  category operating.revenue.other
  amount = 4500
}

A subtotal aggregates categories, every period — net operating income is everything classified operating.*. Packs declare these; you read them as domain.<pack>.<name> in the results.

A statement puts them in order with labels and indentation.

Reading one

Every line is signed as it is stored. An expense is negative. A row that a reader expects to see positive — "Less: vacancy", debt service — carries a display sign that flips it for rendering only. The published value stays signed, so anything consuming the JSON still adds up correctly while a rendered statement reads the way a pro forma should.

A ratio is recomputed at the grain it is shown at. An annual coverage ratio is annual NOI over annual debt service — never the average of twelve monthly ratios, which is a different and wrong number. Where the denominator is zero the value is null rather than zero: a period with no debt service has no coverage ratio, and averaging a zero in would understate the deal.

The bottom line is checked, not assumed. Every statement publishes a reconciliation — its own total, the total of the universe it reports, and the residual between them. That universe is the model, with one exception: a statement filtered by a slice reconciles against the slice's total, because reporting the filter itself as a shortfall would make a warning fire on a correct model. If a stream carries no category it appears in a visible Unclassified row rather than quietly vanishing, because a statement that is short by an unnoticed line looks entirely plausible.

A slice itself publishes no reconciliation, by design: it is partial on purpose, and must be seen to be — a partial number never dresses as a complete one.

Grain

Grain belongs to the output, not the run. One model can publish a monthly pro forma and an annual summary of the same cash, and both are in the same results document. Each statement carries its own column labels, because an annual view of a monthly model has ten columns where the model has 120.

Several views of one domain

A pack can ship more than one layout over the same categories, because one asset class has more than one reporting convention. The same loan pool reads as a remittance report — principal split scheduled and unscheduled, because that is what a prepayment speed acts on — or as a statement of operations reporting total and net investment income.

Each view is checked for completeness when the pack loads: every category the pack declares appears in exactly one line row. A view cannot quietly omit or double-count while looking plausible.

What each pack ships

PackStatementReported at
energyProject operating statement (default)model grid
creOperating statement (default)model grid
creSources and uses of capitalmodel grid
creditCollections statement (default)model grid
creditRemittance reportmodel grid
creditStatement of operationsmodel grid
opcoFree cash flow (default)model grid
opcoSponsor cash flowmodel grid
opcoStatement of cash flowsmodel grid