Cosmic Bull

cashflow

gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow on onyx-1 testnet

pure packageprimitiveaccountinglive on onyx-1

Understand where money comes from and where it goes.

Splits a period's cash movement into operating, investing and financing activity, and says so plainly when the split cannot be determined.

In detail curated

A pure DIRECT-METHOD statement of cash flows: how cash moved over one p/periods Window, split into operating/investing/financing activity, with an explicit refusal where the split is not determinate. The third primary financial statement -- p/statements derives the other two at an instant and p/periods derives one window's movement, and all three of those packages carry a doc note saying the INDIRECT method needs a cash-vs-accrual classification journal.Posting does not have. That note is TRUE and does not block this package: the DIRECT method needs something smaller and caller-suppliable -- a declaration of which chart codes hold cash -- so nothing about what a posting is had to change. THE CENTRAL CORRECTNESS DECISION IS THAT ATTRIBUTION IS PER ENTRY, NEVER PER LEG, and the natural per-leg design is wrong in a way that still BALANCES: on a part-paid purchase (debit Inventory 100 / credit Cash 60 / credit Payables 40) per-leg attribution reports 100 of outflow against the 60 that actually moved, inventing a 40 cash flow that never happened while the statement adds up. So an entry's net movement is computed from its CASH legs alone and that whole figure goes to one category. DETERMINACY IS A REFUSAL, NOT AN APPORTIONMENT: an entry is determinate iff it has at least one non-cash leg and all of them classify alike; otherwise it is named in Report.Unresolved with one of THREE reasons and its delta is carried in Report.Unattributed, so NetChange and Closing stay true even when the split is incomplete. ReasonUnbalanced exists separately because a ReasonConflict carrying an EMPTY Categories set is indistinguishable from a bug in the package -- found by writing the conflict test, not by reading the code. CLASSIFICATION IS PER CODE AND DELIBERATELY NOT PER journal.Kind: a kind-based mapping needs no caller declaration and is the first design anyone reaches for, and it puts the ENTIRE working-capital cycle in the wrong section, because receivables and inventory are assets whose movement is OPERATING. There is no default bucket of any sort; an undeclared code makes its entry unresolved, since silence is not assent. Cash must be a journal.Asset (ErrCashAccountNotAsset) or a debit to cash would stop meaning an inflow and nothing downstream could tell; the stated cost is that a bank overdraft carried as a liability cannot be declared as cash. Imports stdlib errors plus p/journal and p/periods; MaxAccounts/MaxEntries are reached THROUGH periods rather than restated. 18 sentinel errors; Cash, Rules and the chart are all strictly ascending because membership is a binary search in Derive's inner loop.

Use it

A pure package has no state and no transactions: you use it by importing it.

Import it

import "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow"

The path is the address: there is no package manager, nothing to add to a manifest and no version to pin, so the package you compile against is the one the chain runs. It differs per chain because the namespace in it is the deploying account's.

Called from real code

Not an example written for this page. These lines are quoted from cashflow, a deployed package in this portfolio that imports this one as cf, out of the committed file whose sha256 onyx-1 testnet attested. They compile because they are compiled.

if _, err := cf.Derive(
	chartOf(owner), nil,
	periods.Window{From: 0, Until: 1},
	cf.Policy{Cash: c, Rules: r},
	0,
); err != nil {
onyx/r/cashflow/cashflow.gno lines 661–666, onyx-1 testnet
rep, err := cf.Derive(chart, entries, w, cf.Policy{
	Cash:  copyCodes(pol.cash),
	Rules: copyRules(pol.rules),
}, opening)
onyx/r/cashflow/cashflow.gno lines 796–799, onyx-1 testnet
rep, err := cf.Derive(chartOf(owner), es, r.rep.Window, cf.Policy{
	Cash:  copyCodes(r.pol.cash),
	Rules: copyRules(r.pol.rules),
}, r.rep.Opening)
onyx/r/cashflow/cashflow.gno lines 1136–1139, onyx-1 testnet

Deployments

onyx-1 testnet onyx-1

RenderedSourceCall builder

chain-attested
Import pathgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow
Namespaceg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Deployed at height375,382
Deploy transactione601754869a8a69a33ae598aa4eae806d88cedd51ec077f3913f588773c67763 look it up on the RPC
Deployerg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Gas used45,099,881
Storagenot attested per package on this chain — see the limitations below
Files on chaincashflow.gno gnomod.toml
Deployed bytescashflow.gno — 33,331 bytes
sha25677dfe5927a4b026e029b991a6a9125a0f683c9d6b177529545e86ebc1859b226
Realm addressnone — A /p/ package is not a realm: it holds no state, custodies no coins and is never a transaction sender. The pkgPath derivation would still produce a value; recording one would name nothing.
Used byr/cashflow

Do not take the hash above on trust. $download returns the bytes onyx-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:

curl -sS 'https://onyx.testnets.gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow$download&file=cashflow.gno' | shasum -a 256

Expected: 77dfe5927a4b026e029b991a6a9125a0f683c9d6b177529545e86ebc1859b226 — 33,331 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.

API

chain-derived 1 exported function, 7 types, 8 methods.

This is a /p/ package: you import it, you do not call it in a transaction. gnoweb's $help shows only exported top-level functions, so the types and methods below do not appear there at all — which is why this reference exists.

Overview

Package cashflow derives a DIRECT-METHOD statement of cash flows from a double-entry book: how cash moved over one window, split into operating, investing and financing activity, with an explicit refusal where the split is not determinate.

It is the third primary financial statement. p/statements derives the other two from a chart at an instant, and p/periods derives one window's movement; all three of those packages carry a note saying they do not do this one, and all three say the INDIRECT method needs a cash-vs-accrual classification journal.Posting does not carry. That is true. The direct method needs something different and smaller: a declaration of which chart codes hold cash. A declaration is something a caller can supply, which is what makes this package possible without changing what a posting is.

The sign convention, and why cash must be an asset

A debit to a cash account is an INFLOW and a credit is an OUTFLOW. That follows from cash being debit-normal, so Policy requires every declared cash code to be a journal.Asset and refuses anything else (ErrCashAccountNotAsset). Declaring a credit-normal account as cash would invert every figure in the report and nothing downstream could tell. The cost of the rule is real and is stated rather than hidden: a bank overdraft carried as a liability cannot be declared as cash, and a caller holding one must carry it as a contra-asset or leave it out.

Attribution is per ENTRY, never per leg

This is the package's central correctness decision, and the natural design is wrong in a way that produces numbers which balance. Take one ordinary part-paid purchase:

debit Inventory 100 credit Cash 60 credit AccountsPayable 40

Classifying each non-cash leg and attributing its own amount reports 100 of inventory-related outflow against the 60 of cash that actually moved. It invents a cash flow of 40 that did not happen, and the statement still adds up. So the unit of attribution is the entry:

The determinacy rule, stated as a refusal

An entry touching cash is determinate iff it has at least one non-cash leg and all of its non-cash legs classify to the SAME category, in which case the entry's whole net cash delta goes there. Otherwise the entry is named in [Report.Unresolved] with the reason, and its cash delta is carried in [Report.Unattributed].

The "at least one" clause is not pedantry. An entry with nonzero net cash and no non-cash leg at all cannot come out of a balanced book, and it gets its own ReasonUnbalanced rather than being reported as a conflict with nothing to conflict — see that constant.

Under that rule the purchase above is determinate when Inventory and AccountsPayable both classify operating: 60 of operating outflow, which is right. An entry that settles a loan and buys equipment at once — one leg financing, one investing — is NOT determinate, and no apportionment is invented for it. Splitting it proportionally would be a guess wearing a number's clothes.

Why classification is per CODE and not per journal.Kind

A mapping from journal.Kind to a category would need no caller declaration at all, and it is the first design anyone reaches for. It is wrong on the most common accounts in any book: receivables and inventory are assets whose movement is OPERATING, not investing, and a kind-based mapping puts the entire working-capital cycle in the wrong section. So there is no kind-based default and no default bucket of any sort. A non-cash account with no declared category makes its entry unresolved (ReasonUndeclared) — silence is not assent.

What this package does not do

p/journal: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal p/periods: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods p/statements: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements

Imports

Constants and variables

MaxAccounts bounds the chart Derive will accept. It is periods' bound, which is statements' bound, which is journal's own account cap — one number, declared once, so a chart that clears one of these packages clears all three.

const MaxAccounts = periods.MaxAccounts

MaxEntries bounds the entry slice Derive will walk. It is periods' bound for the same reason: a caller feeding the same window to both packages must not have one of them refuse what the other accepted.

const MaxEntries = periods.MaxEntries

NumCategories is how many categories a statement of cash flows has, and so the length [Report.Lines] always has. It is a constant of the standard, not a configuration.

const NumCategories = 3
var (
	// ErrInvalidWindow is a window failing [periods.Window.Valid].
	ErrInvalidWindow = errors.New("cashflow: invalid window")

	// ErrTooManyAccounts is a chart longer than [MaxAccounts].
	ErrTooManyAccounts = errors.New("cashflow: chart length exceeds MaxAccounts")

	// ErrChartNotSorted is a chart whose codes are not strictly ascending.
	// Membership here is a binary search, so the ordering is load-bearing
	// rather than cosmetic.
	ErrChartNotSorted = errors.New("cashflow: chart codes not strictly ascending")

	// ErrInvalidKind is a chart row whose Kind is outside journal's five.
	ErrInvalidKind = errors.New("cashflow: invalid account kind")

	// ErrTooManyEntries is an entry slice longer than [MaxEntries].
	ErrTooManyEntries = errors.New("cashflow: entry count exceeds MaxEntries")

	// ErrEntriesNotSorted is an entry slice not strictly ascending by ID.
	ErrEntriesNotSorted = errors.New("cashflow: entry IDs not strictly ascending")

	// ErrTooManyPostings is an entry with more than
	// [journal.MaxPostingsPerEntry] postings. journal cannot produce one, and
	// re-checking is what makes [MaxEntries]'s cost bound a proven ceiling
	// rather than a hope.
	ErrTooManyPostings = errors.New("cashflow: entry exceeds journal.MaxPostingsPerEntry")

	// ErrInvalidPosting is an in-window posting with an invalid Side or a
	// non-positive Amount. journal carries direction in Side and amounts are
	// always positive, so a signed amount here is a posting from somewhere
	// else.
	ErrInvalidPosting = errors.New("cashflow: invalid posting")

	// ErrUnknownAccount is an in-window posting naming a code absent from the
	// chart. It is scoped to the window because no figure in the report
	// depends on an entry outside it.
	ErrUnknownAccount = errors.New("cashflow: posting names an account absent from the chart")

	// ErrNoCashAccounts is an empty [Policy.Cash]. A statement of cash flows
	// over a book with no declared cash is all zeroes and looks exactly like
	// a quiet period, so the empty declaration is refused rather than
	// reported.
	ErrNoCashAccounts = errors.New("cashflow: policy declares no cash accounts")

	// ErrCashNotSorted is a [Policy.Cash] not strictly ascending.
	ErrCashNotSorted = errors.New("cashflow: policy cash codes not strictly ascending")

	// ErrUnknownCashAccount is a declared cash code absent from the chart.
	ErrUnknownCashAccount = errors.New("cashflow: policy declares a cash account absent from the chart")

	// ErrCashAccountNotAsset is a declared cash code whose chart Kind is not
	// [journal.Asset]. See the package doc on the sign convention: this is
	// what keeps a debit to cash meaning an inflow.
	ErrCashAccountNotAsset = errors.New("cashflow: policy declares a non-asset account as cash")

	// ErrRulesNotSorted is a [Policy.Rules] not strictly ascending by Code.
	ErrRulesNotSorted = errors.New("cashflow: policy rule codes not strictly ascending")

	// ErrInvalidCategory is a category outside the declared three — in a
	// policy rule, or passed to [Report.Line].
	ErrInvalidCategory = errors.New("cashflow: unknown category")

	// ErrUnknownRuleAccount is a policy rule naming a code absent from the
	// chart.
	ErrUnknownRuleAccount = errors.New("cashflow: policy rule names an account absent from the chart")

	// ErrCashAccountClassified is a code declared both as cash and as a
	// classified account. The two roles are exclusive: a cash account is what
	// MOVES, and a classified account is what the movement is FOR. Accepting
	// both would make the same code answer two different questions in one
	// entry.
	ErrCashAccountClassified = errors.New("cashflow: code is declared as cash and also classified")

	// ErrOverflow is an int64 overflow in any accumulation, including the
	// entry-level cash total and the closing balance.
	ErrOverflow = errors.New("cashflow: int64 overflow")
)

The three activity categories of a statement of cash flows.

const (
	CategoryOperating Category = 1 // trading: the working-capital cycle
	CategoryInvesting Category = 2 // long-lived assets and investments
	CategoryFinancing Category = 3 // capital and borrowings
)

The three reasons an entry is unresolved.

const (
	// ReasonConflict is an entry whose non-cash legs classify to more than
	// one category. [Unresolved.Categories] names them, and names at least
	// two.
	ReasonConflict Reason = 1

	// ReasonUndeclared is an entry with a non-cash leg whose account has no
	// policy rule. [Unresolved.Codes] names them. An undeclared account is
	// named rather than defaulted, because a default bucket turns a missing
	// declaration into a figure nobody questions.
	//
	// It takes precedence over [ReasonConflict] when an entry has both an
	// undeclared code and two declared categories, because the undeclared
	// code is the actionable half: declaring it may resolve the entry, and
	// reporting a conflict while concealing a missing declaration would send
	// the reader to re-examine a policy that is not yet complete.
	ReasonUndeclared Reason = 2

	// ReasonUnbalanced is an entry all of whose legs are declared cash
	// accounts and whose cash legs do not net to zero.
	//
	// [journal.Book] cannot produce one: a balanced entry whose every leg is
	// cash has equal debits and credits over cash and therefore nets to zero,
	// which makes it a [Report.NetZero] item instead. So this reason is
	// reachable only from a hand-built entry slice, the same class
	// [p/periods] documents for its own hand-built Report.
	//
	// It exists rather than being folded into [ReasonConflict] because a
	// conflict carrying an EMPTY [Unresolved.Categories] is indistinguishable
	// from a bug in this package, and a census item a reader cannot tell from
	// a defect is not a census item. Found by writing the test for the
	// conflict case, not by reading the code.
	ReasonUnbalanced Reason = 3
)

Types

type Category

type Category uint8

Category is which activity a cash flow belongs to. The zero value is deliberately invalid, so a forgotten field is an error rather than operating activity — which is the bucket a defaulting implementation would pick and the one that would hide the most.

The constants carry a Category prefix for the same reason [statements.Section]'s do: a bare Operating would read as a field name.

Category.String

func (c Category) String() string

String returns the category's name, or "invalid" for the zero value and any unrecognised code.

Category.Valid

func (c Category) Valid() bool

Valid reports whether c is one of the three categories.

type Classification

type Classification struct {
	Code     string
	Category Category
}

Classification is one account code's activity category, declared by the caller. See the package doc on why this is per code rather than per journal.Kind.

type Line

type Line struct {
	Category Category

	// Inflows is the gross cash received, non-negative.
	Inflows int64

	// Outflows is the gross cash paid, non-negative.
	Outflows int64

	// Entries is how many in-window entries were attributed here.
	Entries int
}

Line is one category's gross flows over the window.

Inflows and Outflows are both non-negative AS PRODUCED BY Derive, and gross rather than netted, because a category with 1,000 in and 1,000 out is not the same business as one with nothing happening and a net of zero is all a netted figure can say about either.

Exported fieldTypeDoc
CategoryCategory
Inflowsint64Inflows is the gross cash received, non-negative.
Outflowsint64Outflows is the gross cash paid, non-negative.
EntriesintEntries is how many in-window entries were attributed here.

Line.Net

func (l Line) Net() int64

Net returns Inflows minus Outflows.

The subtraction is unchecked, and that is safe for any Line Derive produces: both columns are non-negative, so the difference is in range for every pair of int64 values they can hold. A hand-built Line with a negative column can wrap, and that is the only way it can.

The wrapped value does not stay local, which is the part worth stating: Report.Reconciles sums Net() over the lines, and a wrapped Net() can make it agree over a report whose columns say something else entirely. So Reconciles refuses a negative column outright rather than summing it — see the guard there. Net() itself stays unchecked because it has no way to report a refusal and a Line is a figure, not a claim.

This note previously compared the blast radius to periods.Activity.Movement and said that one was wider because a negative column there also inverts a sign convention. That comparative was wrong, and an audit of this package found it: both wrap a figure that then passes every downstream guard, and the real asymmetry is in where the wrapped value GOES, not in sign inversion. The note is kept, corrected, because the equivalent note in periods was also once wrong about its own blast radius and also had to be found by an audit — twice is a pattern, and the pattern is that a comparative claim about another package's hazard is the sentence nothing checks.

type Policy

type Policy struct {
	// Cash is the codes that hold cash, strictly ascending. Must be
	// non-empty, and every code must be a [journal.Asset] in the chart.
	Cash []string

	// Rules is the activity category for non-cash codes, strictly ascending
	// by Code. A code absent from here is NOT defaulted; an entry touching it
	// is unresolved with [ReasonUndeclared].
	Rules []Classification
}

Policy is the caller's whole declaration: which chart codes hold cash, and what each other code's movement is for.

Both slices must be strictly ascending by code, because membership is a binary search in Derive's inner loop. The two sets must be disjoint (ErrCashAccountClassified). Neither is verified against anything but the chart — this package cannot know whether a code called "petty cash" holds cash, and says so in its doc rather than pretending the name is evidence.

Exported fieldTypeDoc
Cash[]stringCash is the codes that hold cash, strictly ascending. Must be non-empty, and every code must be a [journal.Asset] in the chart.
Rules[]ClassificationRules is the activity category for non-cash codes, strictly ascending by Code. A code absent from here is NOT defaulted; an entry touching it is unresolved with [ReasonUndeclared].

type Reason

type Reason uint8

Reason is why Derive would not classify an entry. The zero value is deliberately invalid.

Reason.String

func (r Reason) String() string

String returns the reason's name, or "invalid".

Reason.Valid

func (r Reason) Valid() bool

Valid reports whether r is one of the three reasons.

type Report

type Report struct {
	// Window is the window the figures are for, echoed back so a stored
	// report cannot be read without it.
	Window periods.Window

	// Lines is the three categories, ascending by Category, always present
	// and always of length [NumCategories] — a category with no activity is a
	// zero line, not an absent one, because a statement of cash flows with a
	// missing section is not a statement of cash flows. Use [Report.Line] to
	// reach one by category rather than indexing by arithmetic.
	Lines []Line

	// Opening is the caller-supplied opening cash position, echoed back
	// unchanged. This package cannot verify it; see the package doc. A
	// negative value is accepted — an overdrawn position is a real one — so
	// the sign is not constrained here.
	Opening int64

	// NetChange is the window's total net cash movement, attributed and
	// unattributed together. It is the book's figure, not the statement's, so
	// it is true even when the split is not complete.
	NetChange int64

	// Unattributed is the net cash movement of the entries in Unresolved.
	// Zero exactly when every entry that moved cash was classified.
	Unattributed int64

	// Closing is Opening plus NetChange.
	Closing int64

	// Entries is how many entries fell in the window, including the ones that
	// moved no cash. Zero is a legitimate report of a quiet period, not an
	// error.
	Entries int

	// CashEntries is how many in-window entries touched a declared cash
	// account at all, including the ones in NetZero and Unresolved.
	CashEntries int

	// NetZero is the IDs of in-window entries that touched cash but moved
	// none on net — a transfer between two declared cash accounts, possibly
	// alongside non-cash legs. Classification is deliberately not attempted:
	// there is nothing to attribute, so demanding determinacy of them would
	// manufacture unresolved entries out of non-events.
	NetZero []uint64

	// Unresolved is the entries whose category was not determinate.
	Unresolved []Unresolved
}

Report is one window's statement of cash flows. Every slice is freshly allocated by Derive on every call and is never nil, so the caller owns it and an empty result is an empty slice rather than a nil to be distinguished from one.

The invariant that makes the report readable when the split is incomplete:

sum(Lines[i].Net()) + Unattributed == NetChange Opening + NetChange == Closing

Both hold on every Report Derive returns, including one with unresolved entries. Report.Reconciles asserts the first.

Exported fieldTypeDoc
Windowperiods.WindowWindow is the window the figures are for, echoed back so a stored report cannot be read without it.
Lines[]LineLines is the three categories, ascending by Category, always present and always of length [NumCategories] — a category with no activity is a zero line, not an absent one, because a statement of cash flows with a missing section is not a statement of cash flows. Use [Report.Line] to reach one by category rather than indexing by arithmetic.
Openingint64Opening is the caller-supplied opening cash position, echoed back unchanged. This package cannot verify it; see the package doc. A negative value is accepted — an overdrawn position is a real one — so the sign is not constrained here.
NetChangeint64NetChange is the window's total net cash movement, attributed and unattributed together. It is the book's figure, not the statement's, so it is true even when the split is not complete.
Unattributedint64Unattributed is the net cash movement of the entries in Unresolved. Zero exactly when every entry that moved cash was classified.
Closingint64Closing is Opening plus NetChange.
EntriesintEntries is how many entries fell in the window, including the ones that moved no cash. Zero is a legitimate report of a quiet period, not an error.
CashEntriesintCashEntries is how many in-window entries touched a declared cash account at all, including the ones in NetZero and Unresolved.
NetZero[]uint64NetZero is the IDs of in-window entries that touched cash but moved none on net — a transfer between two declared cash accounts, possibly alongside non-cash legs. Classification is deliberately not attempted: there is nothing to attribute, so demanding determinacy of them would manufacture unresolved entries out of non-events.
Unresolved[]UnresolvedUnresolved is the entries whose category was not determinate.

Report.Complete

func (r Report) Complete() bool

Complete reports whether every in-window entry that moved cash was attributed to a category.

A complete report is not a correct one: completeness is a property of the POLICY's coverage, and a policy that classifies every account wrongly is complete. See the package doc.

Report.Line

func (r Report) Line(c Category) (Line, error)

Line returns the line for c. An invalid category is ErrInvalidCategory; there is no default line.

This is the accessor to use. [Report.Lines] is ordered by Category and a consumer could index it as Lines[c-1], but that arithmetic is a second copy of the ordering invariant and the kind of thing that survives a reordering silently.

Report.Reconciles

func (r Report) Reconciles() bool

Reconciles reports whether the attributed lines plus the unattributed residual equal NetChange.

It is offered as API rather than asserted internally because a Report can be built by hand, and a consumer that stores one should be able to check it on the way back out. On a Report from Derive it is always true.

False means one of three things and does not distinguish them: the lines do not sum to NetChange, the sum overflows, or a line carries a negative column (which Line.Net would wrap). All three are "do not trust this report", which is the whole question a bool can answer.

type Unresolved

type Unresolved struct {
	// EntryID is the entry's journal ID.
	EntryID uint64

	// Reason is why it could not be classified.
	Reason Reason

	// Net is the entry's net cash movement: positive for an inflow. Reported
	// so a reader can see the size of what is unattributed rather than only
	// that something is.
	Net int64

	// Categories is the conflicting set, ascending and of length at least
	// two, when Reason is [ReasonConflict]; empty otherwise. Freshly
	// allocated and never nil.
	//
	// Ascending rather than in posting order, so that two records of the same
	// conflict compare equal whichever leg was posted first.
	Categories []Category

	// Codes is the undeclared account codes, in posting order and
	// deduplicated, when Reason is [ReasonUndeclared]; empty otherwise.
	// Freshly allocated and never nil.
	//
	// Posting order rather than ascending, because these are a worklist for
	// whoever extends the policy and the order a reader meets them in the
	// entry is the more useful one. The two slices differ on this
	// deliberately: a conflict set is compared, a code list is worked
	// through.
	Codes []string
}

Unresolved is one in-window entry Derive would not classify. It is a census item, not an error: the entry's cash movement is real and is carried in [Report.Unattributed], so [Report.NetChange] and [Report.Closing] stay true even when the split is incomplete.

Whether an entry here SHOULD have been classifiable is an accounting judgement made outside this package. What is reported is that this policy could not decide it, and exactly what was missing or in conflict.

Exported fieldTypeDoc
EntryIDuint64EntryID is the entry's journal ID.
ReasonReasonReason is why it could not be classified.
Netint64Net is the entry's net cash movement: positive for an inflow. Reported so a reader can see the size of what is unattributed rather than only that something is.
Categories[]CategoryCategories is the conflicting set, ascending and of length at least two, when Reason is [ReasonConflict]; empty otherwise. Freshly allocated and never nil. Ascending rather than in posting order, so that two records of the same conflict compare equal whichever leg was posted first.
Codes[]stringCodes is the undeclared account codes, in posting order and deduplicated, when Reason is [ReasonUndeclared]; empty otherwise. Freshly allocated and never nil. Posting order rather than ascending, because these are a worklist for whoever extends the policy and the order a reader meets them in the entry is the more useful one. The two slices differ on this deliberately: a conflict set is compared, a code list is worked through.

Functions

Derive

func Derive(chart []journal.Account, entries []journal.Entry, w periods.Window, p Policy, opening int64) (Report, error)

Derive builds the statement for one window.

Preconditions, all refused rather than assumed: w must be valid; chart must be strictly ascending by Code, no longer than MaxAccounts, with every Kind one of journal's five; entries must be strictly ascending by ID and no longer than MaxEntries, with at most journal.MaxPostingsPerEntry postings each; and p must satisfy the rules on Policy.

Entries outside w are skipped, not refused — a caller passing the whole book is the expected case — and their postings are therefore not content-checked, because no figure in the report depends on them. ErrInvalidPosting and ErrUnknownAccount are scoped to the window for that reason; the postings-per-entry cap is not, since it is what makes MaxEntries's cost ceiling a bound rather than a hope.


Doc text is reproduced as vm/qdoc returns it. The node markdown-escapes doc comments, so a bracket or angle bracket may carry a backslash the committed source does not have. The source itself is at source and in this repository.

Dependencies

chain-attested
Importserrors, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods
First-party dependenciesgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods

Known limitations

Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.

curated

Source

Source fileonyx/p/cashflow/cashflow.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository)
Matches the deployed bytesyes — byte-identical