DECCF · Cash Flow Modeler and Calculator

API overview

The package's JavaScript exports are defined in src/index.js; TypeScript declarations are in src/index.d.ts. Installed projects import from the package root. Examples in this repository can import from src/index.js. Node.js 20 or later is required.

Model and compilation

currency and timeline are required; the other maps are optional. currency is a Currency value. A run may select a different reporting denomination through run(model, { currency }); this does not convert numeric values. Map keys become declaration names in results, and their values are references created by API factories.

  • compileModel(model) validates the model and returns a plan with dates, references, periods, and dependency order.
  • run(modelOrPlan, options?) evaluates the model. Declarations are compiled automatically.
  • ModelError exposes a stable machine-readable code, optional path and period, and details.

result.deterministic.account_postings serializes account movements with period, account, type, category, side, amount, and flow metadata. It includes explicit booking rows and moves movements. Account-plan closing rows have flow_role: "period-close", allowing a report to distinguish closing entries using only the run output.

Values and inputs

  • Currencies: currency("SEK") creates a three-letter uppercase code. currencies includes USD, SEK, EUR, GBP, NOK, DKK, CHF, CAD, AUD, and JPY.
  • Money: money(amount, currency), scaleMoney, addMoney, subtractMoney, and convertMoney(value, target, unitsOfTargetPerSource).
  • Direction: inflow and outflow. Create categories for a model with category(path), such as category("sales.subscription"). An optional { label } gives the category a display name in deterministic.categories. The core has no predefined categories; domain packs can provide them.
  • The accountplan domain pack adds account codes and classifications. The Swedish income-tax pack is separate.
  • Inputs: input.decimal, rate, fraction, integer, str, id, enum, date, money, object, objectArray, textfile, sni, curve, normal, logNormal, uniform, and triangular. derived({ inputs, calculate }) declares a value derived from other references before simulation. input.textfile(defaultText?) is a string precondition; a run can replace it with the contents of a text file or any other string through deterministic.inputs. input.sni(mainGroup?) is a string precondition validated against the SNI 2025 two-digit main-group list. It carries dropdown choices in the model description and result metadata for UIs.

fraction must be between 0 and 1; integer must be a whole number. Numeric values must be finite and can be checked against a domain. A date input accepts an ISO year-month or date. Curves and distributions are covered in Valuation and uncertainty; run-time overrides are described in Run configuration.

Components, collections, and funding

  • input.object({ field: inputValue }) groups typed input fields into one value. input.objectArray(rows, { key: "id", item? }) declares editable, keyed rows. Use a stable ID field and an explicit item schema when the initial list is empty. Calculator editors render object arrays as add/remove cards from this same schema.
  • component({ entities, accounts, flows, calculations, components, outputs }) composes ordinary declarations and exposes selected outputs. repeat(source, template) creates one component per object-array row. Inside the template, source.item.data exposes row inputs and source.item.outputs exposes component outputs. .where({ role: "adult" }) selects rows; .column("salary") gives a keyed column for allocation.
  • allocation({ amount, method, limits?, redistribute?, signed?, unit?, schedule?, phase? }) reports shares from proportional({ weights, overrides?, zeroBasis? }) or priority({ order, requests? }). Allocation has no cash or account side effects.
  • transfer({ from, to, amount, schedule, posting? }) makes a balanced movement. Cross-owner transfers need a posting policy. funding({ from, to, amount, method, posting, schedule, limits?, shortfall? }) uses the same allocation methods and records real transfers. Its calculation outputs are not counted again as cashflow.

See the declarative collections and funding guide for complete examples and the Household pack guide for a domain-specific composition.

Time

  • periods.daily/weekly/monthly/quarterly/annual({ from, count, projection? })
  • phase({ from, to }); frequencies.day/week/month/quarter/year
  • schedule.once(date, options?), .onceAfterNoOfDays(days), .every(frequency, options?), .daily, .weekly, .monthly, .quarterly, .annual, .yearEnd, .phaseStart, .phaseEnter, and .phaseEnd

Schedule options include from, to, interval, placement, except, also, dayOfMonth, and endOfMonth. See Time, phases, and schedules.

Entities and calculations

  • entity(options?) and the .asset, .party, .bundle, and .reference factories. Optional properties include owner, parent, fields, attributes, and lifecycle. owner links an entity to another entity and includes it in owner-scoped grouping; parent defines the entity tree. attributes holds static facts such as a person's date of birth. The Household pack exports household() for household entities.
  • field({ initial, next? }) stores a numeric value per period. next(previous, ctx) can calculate the next value.
  • cashflow({ schedule?, amount?, direction?, owner?, agreement?, category?, currency?, active?, dependsOn?, readsInflowsFrom?, readsCashflowsFrom?, readsAccountsFrom?, readsHistoricalAccountsFrom? }). A basic cashflow normally declares a direction. A posting cashflow can omit owner; it inherits the owner if all booked accounts share one. direction can be omitted when the cash account is selected or inferred, or when postings do not represent cash. A grouped flow can omit settings supplied by its group; compilation reports an error if a required setting is still missing. Flows follow model declaration order; dependsOn adds explicit dependencies. readsCashflowsFrom orders a flow after the owner's cashflows, and readsAccountsFrom orders it after flows that post to selected accounts, excluding period-closing flows. readsHistoricalAccountsFrom declares accounts a flow reads only from earlier periods, without adding same-period ordering edges.
  • flowGroup({ flows, preconditions?, ...defaults }) groups flows and nested groups, applies shared settings such as owner, schedule, direction, or dependencies, and can own named run inputs. A nested input like preconditions: { openingAssets: { remainingLifeYears: input.integer(5) } } is discovered by defineModel() and receives a stable dotted path based on its flow-group names. Values on an individual flow override the group. Put a group in the model's flows map, or use flows(...items) to flatten groups and flows into an array for defineModel({ flows }).
  • statement({ name?, owner?, schedule, balances, balancingAccount?, dependsOn? }) creates a flow group for importing absolute account values. Each { account, value } entry targets an account balance. A stdaccount reference is registered automatically; ordinary account() references must still be declared in accounts. One-time schedules apply values once; recurring schedules spread the remaining adjustment over the remaining occurrences. Income values are positive, expense values are negative, and asset, liability, or equity values use positive normal balances; the engine converts them to signed postings. Untyped and transfer accounts use signed ledger values. Set balancingAccount when the supplied entries do not balance; otherwise the statement requires a balanced set of account adjustments. See Import a historical statement.
  • account({ owner?, side?: "owed" | "due", type?, initial?, category? }) creates a balance. type can be asset, liability, equity, income, expense, or transfer. Posting rows inherit category from their target account. Debits increase the signed balance and credits decrease it. For income and expense categories, reporting signs are reversed so income is positive and expenses negative. Other account categories follow signed movements. side applies to moves cashflows.
  • stdaccount({ code, owner, initial? }) creates or reuses a plan-backed account. The code is always a string, including numeric business codes such as "1930" and groups such as "19". It selects the account type and category from the Business or Personal account plan; person, household, and company entities can be owners. The compiler automatically registers accounts referenced by flows, statements, and supported domain packs. A matching explicit account keeps its name and opening balance. Declare an account in accounts to choose its result name, retain an otherwise-unused opening balance, or register a reference used only inside a callback. Creating a parent while a more specific same-owner subaccount already exists is an error.
  • cashflow({ debit, credit, schedule, amount, cashAccount? }) is shorthand for two equal posting rows. Use bookings: [{ debit: account, amount }, { credit: account, amount }] for multiple rows. Each row sets exactly one side; amounts must be finite, non-negative, and balanced for each occurrence. If a row omits amount, it inherits the cashflow's base amount or, for a triggered occurrence, the source amount. Each row inherits the account category; do not set category on a posting cashflow. cashAccount must be one of the booked accounts and its signed movement determines the flow's net cashflow. If exactly one booked account is an asset, it is inferred. Without a cash account or direction, a posting flow has zero net cashflow. ctx.amount exposes the base amount in booking callbacks.
  • trigger(name?) creates a trigger reference. cashflow({ triggers }) emits it for each occurrence. A subscriber uses schedule.onceAfterNoOfDays(days) and may specify triggeredBy. When only one trigger type is emitted in the model, it can be inferred. ctx.source contains the source flow, model name, optional trigger name, amount, currency, date, and period. Each received trigger creates an occurrence.
  • state(label) and lifecycle({ states, initial, transitions?, onEnter? }) define state. Actions include actions.setState(entity, state), .setField(field, value), .activate(flow), and .deactivate(flow).
  • event({ schedule?, when?, actions? }) requires a schedule or condition.
  • agreement({ subject, parties?, from?, to?, terms?, onStart?, onEnd? }) attaches to an entity. parties maps roles to party entities. Use agreementTerm(value) for terms; declare cashflows separately.
  • option({ subject?, direction?, currency?, schedule?, maxExercises?, when, payoff }) creates a separate payoff series. Default direction is inflow and the default limit is one exercise. The condition must become true after being false; further exercises require another false-to-true transition.
  • metric({ calculate, label?, unit?, periodLength?, frequency?, annualAggregation?, owner?, fiscalYearStartMonth? }) declares a model metric. The optional label and unit are copied to its result series for presentation. The window length and frequency are counts of model periods. A metric callback receives ctx.metricPeriod with the inclusive period indexes and dates; annual results are rolled up with sum or last. When an owner is set, its fiscalYearStartMonth entity attribute supplies the default year boundary.

Calculation callbacks are synchronous and receive a calculation context with period data, reference readers, state access, and optional draws. ctx.sumAccount(account, from, to, options?) totals the account's debit-minus-credit postings over inclusive dates; set excludePeriodClosing: true to ignore internal account-closing entries. A flow must list current-period dependencies in readsAccountsFrom; historical-only readers can use readsHistoricalAccountsFrom to avoid same-period dependency edges.

Runs and packs

  • runMonteCarlo(model, { trials, seed, ...runOptions }) summarizes numeric metrics across draws and returns per-trial and overall runtime timings.
  • runScenarios(model, scenarios, runOptions?) returns { name, result } entries, each with its own result timings. The array also exposes a timings property with per-scenario measurements and totals. Each scenario has a name and optional overrides map keyed by InputReference; map values accept scalar inputs and curve series. Put scenario-specific inputs in the maps; shared run options apply across scenarios. Pass trials with seed in run options to execute Monte Carlo simulations for every scenario.
  • definePack({ name, version, extract }) creates a reusable pack; extractPacks(model, packs) adds its declarations to the model.

The Household pack requires the Personal account plan. It exports household() and householdBudget({ household, members, funding? }). Enabling the module makes the declarations available; the model author explicitly creates a budget component for each selected household. Missing budget expense and cash accounts are resolved from the Personal plan during compilation. See the Household pack guide. The SIE import pack requires the Business account plan and exposes sieStatement({ owner, sieContent }) to apply the latest imported account balances at the end of their SIE period.

Period order and actions

Within a period, the engine opens period state, updates fields, applies lifecycle transitions and agreement boundaries, evaluates events, calculates cashflows in dependency order and posts movements, then evaluates options and period-end calculations. Actions cannot change periods already calculated. ctx.previous(accountRef) reads the account's opening balance; closing balances are available from result.account(accountRef).

One current typing mismatch: runtime expects lifecycle onEnter entries as { state, actions }, while TypeScript currently declares Action[]. See Errors and limits.