Package statements turns a chart of accounts into classified financial statements, where the section, the sign and every total are functions of each account's kind and none of them is supplied by the caller.
A realm holds the chart — p/journal keeps one, and a realm wrapping it publishes one — and asks this package what the statements say. Nothing here reads the chain, holds state, or decides what an account is: it takes journal.Account rows and returns figures.
Example
1rows := []journal.Account{ /* ascending by Code */ }
2st, err := Derive(rows)
3// st.Assets, st.Claims, st.CumulativeNet, st.Abnormal
The three-bucket partition
journal partitions the five kinds in two, by normal balance. A statement partitions them in three, by where the figure is presented:
Example
1SectionAssets : {Asset} balance sheet, debit side
2SectionClaims : {Liability, Equity} balance sheet, credit side
3SectionPerformance : {Revenue, Expense} income statement
SectionOf is the whole mapping and it is fail-closed: a Kind outside 1..5 is ErrInvalidKind, never a bucket. The zero Kind matters here because journal.Kind.IsDebitNormal reports it as credit-normal, so a silent fallthrough would not drop the row — it would file it under claims and produce a statement that balances and is wrong.
There is no Balances method, and the reason is the product's own arithmetic
Write A, L, Q, R and X for the natural-sign totals of the five kinds, each the sum of journal.Account.Balance over its kind, and NI = R - X. For any single account, Balance() is RawDelta() for a debit-normal kind and its negation otherwise, so
Example
1sum over every account of RawDelta = A - L - Q - R + X
and in a balanced book that sum is totalDebits - totalCredits = 0. Hence
Example
1A = L + Q + NI
which is the balance sheet identity. The consequence is uncomfortable and is stated here rather than discovered by a reader: **over a chart drawn from a journal.Book the identity is a constant true**, algebraically identical to `totalDebits == totalCredits`, which the Book enforces on every Post. It is true of a chart whose every account is classified backwards. A `Balances()` returning that comparison would be a check that cannot fail, which this project has shipped before and does not ship again — see DISCOVERY.md § 4.
The identity is NOT vacuous over this package's own input domain, because rows may come from anywhere and a hand-built set need not sum to zero. That scope is the whole content of the claim, and dropping a scope is the documentation defect this project has recorded most often. So:
- [Statement.Assets] and [Statement.Claims] are both reported, as figures.
- Their equality is asserted in the tests, in both directions: held over a Book-derived chart, and shown to survive a chart miscoded on purpose, which is what makes it vacuous there rather than reassuring.
- Nothing in the API offers that comparison as a verdict.
What ships instead: the abnormality census
journal.Account.Balance is natural-sign and journal's own doc says a negative result is "a real signal (a contra position)". What did not exist is the roll-up: finding an abnormal account meant already knowing to look at it. [Statement.Abnormal] lists every row whose balance runs against its kind, and Statement.AbnormalIn counts them per section. It survives a balanced book, which the identity does not.
It is the only partial answer in this portfolio to journal's obligation 7 — "a Kind set wrongly at Open time is undetectable here and undetectable anywhere else in this package" — and it is sound but incomplete, with the incompleteness exactly characterisable:
- It CATCHES a misclassification that crosses the debit/credit-normal boundary and whose account has a non-zero balance. A payables account coded as an asset accumulates credits and reads negative.
- It CANNOT CATCH a misclassification that stays on the same side: Liability <-> Equity <-> Revenue, or Asset <-> Expense. Both subtotals move, no balance goes negative, and nothing anywhere detects it. Obligation 7 is narrowed, not closed.
- It has a FALSE-POSITIVE MODE that is not a defect: a genuine contra account — accumulated depreciation under assets, a sales return under revenue — is legitimately abnormal-signed. The census is a finding to read, never a verdict.
The income statement is cumulative and can be nothing else
journal.Book.Close is a backdating seal: it refuses entries dated at or before a cutoff and does not sweep nominal accounts into equity. There is no retained-earnings account and no closing entry, so revenue and expense accumulate from the book's first posting forever. The field names carry the word — [Statement.CumulativeRevenue], [Statement.CumulativeExpenses], [Statement.CumulativeNet] — because a reader who takes them for a period figure misreads every statement this package produces.
[Statement.Claims] therefore includes CumulativeNet as an explicit reconciling line. A reader who checks Liabilities + Equity against Assets and finds it short by exactly CumulativeNet is not looking at a bug. A period income statement needs entry-level data and is out of scope; so is a cash-flow statement, which needs a cash-vs-accrual classification journal.Posting does not carry.
What this package does not claim
- **Nothing about money.** A statement is a report about a book, and a book is a set of claims by its owner with no custody behind it. This package never sees a coin. A realm publishing its output must say so loudly: a published statement adds apparent authority to a self-report, and that is the one way this product can mislead someone.
- **No audit.** Every figure here is derived from rows the caller supplied. Deriving them faithfully is the guarantee; that they describe anything real is not.
- **Two statements and a census**, not a statement set. No statement of changes in equity, no notes, no comparatives.
p/journal: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal