Describe entities, fields, and states
Entities group related declarations. The core families are asset, party, bundle, and reference; the Household pack adds a household() constructor. owner can link an entity to another entity, while parent controls the entity tree. Household members use entity.party({ owner: household }).
import { currencies, defineModel, entity, field, periods } from "deccf";
const property = entity.asset({
fields: {
occupancy: field({ initial: 0.9, next: (previous) => previous }),
},
});
const investor = entity.party({ parent: property });
const model = defineModel({
name: "property-ownership",
currency: currencies.SEK,
timeline: periods.annual({ from: "2026-01", count: 1 }),
entities: { property, investor },
});
return model;An entity can have an owner, a parent, fields, and a lifecycle. Bind references to names later in the model's entities map. Owner links and parent links are included in owner-scoped result grouping and flow-distributor calculations; the parent relation also defines the entity tree.
A field stores a numeric value per period. initial sets its first value; next(previous, ctx) can calculate each later value. ctx.get(fieldRef) reads the current period's value and ctx.previous(fieldRef) reads the prior value. A field must belong to an entity declared in the model.
Accounts and postings
An Account is a balance, separate from its owning entity and the cashflow that posts to it. Declare accounts in the model's accounts map. owner groups an account under a person or business, but the engine does not require a particular owner or built-in account name. A posting cashflow can omit owner; when all its posted accounts have the same owner, it inherits that owner for grouping and reporting. direction can be omitted when the cash account is explicit or unambiguous.
type classifies an account as asset, liability, equity, income, expense, or transfer. category is the account's reporting category. A posting row inherits the category of its target account, so one transaction can produce rows under both a balance-sheet category and an income or expense category.
import { account, cashflow, category, currencies, defineModel, entity, periods, schedule } from "deccf";
const anna = entity.party();
const bank = account({ owner: anna, type: "asset", category: category("anna.assets.cash.bank") });
const salaryIncome = account({ owner: anna, type: "income", category: category("anna.income.salary") });
const salary = cashflow({
debit: bank,
credit: salaryIncome,
schedule: schedule.monthly(),
amount: 52_000,
});
const model = defineModel({
name: "anna-salary",
currency: currencies.SEK,
timeline: periods.monthly({ from: "2026-01", count: 12 }),
entities: { anna },
accounts: { bank, salaryIncome },
flows: { salary },
});
return model;Each occurrence must balance: total debit amounts equal total credit amounts. Posting amounts are zero or positive. A debit increases the signed account balance and a credit decreases it; account type does not change that rule. A credit balance for debt or income is therefore usually negative. For reporting, income and expense categories use economic signs: income is positive and expenses are negative. Asset, liability, and equity categories follow the signed debit/credit movement.
The account category records posting movements, not the account's ending balance. Read balances with result.account(bank) and category movements with result.category(categoryRef); category reads include descendant categories.
cashAccount identifies which posted account represents this cashflow's cash movement. If exactly one posted account has type: "asset", the engine selects it automatically. If a booking includes multiple asset accounts, such as cash and receivables, specify cashAccount to distinguish the cash entry. A debit/credit cashflow usually needs neither cashAccount nor direction when it has one asset account.
For simpler cashflows that use moves: account, side: "owed" | "due" controls the legacy balance adjustment. Debit/credit postings always use debit-positive and credit-negative signed movements.
See Post cashflows to accounts for split bookings and delayed settlement.
States and lifecycles
States and lifecycles describe an entity's current status and allowed transitions:
import { currencies, defineModel, entity, lifecycle, periods, state } from "deccf";
const active = state("active");
const closed = state("closed");
const life = lifecycle({
states: [active, closed],
initial: active,
transitions: [{ from: active, to: closed, when: (ctx) => ctx.time.index === 11 }],
});
const asset = entity.asset({ lifecycle: life });
const model = defineModel({
name: "asset-lifecycle",
currency: currencies.SEK,
timeline: periods.monthly({ from: "2026-01", count: 12 }),
entities: { asset },
});
return model;Lifecycles are evaluated at the start of a period, before cashflows. A conditional cashflow can query ctx.isInState(asset, active) or ctx.currentState(asset) to read that period's status. ctx.enteredAt(entity, state) returns the period when the entity entered the state, or null.