The Beancount backend (Logistiki.Ledger.Beancount) is the accounting oracle.
It uses beancount_ex as the executable
accounting specification. Beancount is the oracle, not the domain model —
Beancount-specific structs never escape the backend.
Responsibilities
- map virtual account codes to Beancount accounts (
BeancountMapper) - map journals to Beancount transaction directives
- map postings to Beancount postings (signed amounts: debit
+, credit-) - validate accounting behaviour with
Beancount.check/1 - compute balances, statements, trial balances (delegated to the projection engine so the Simulation and Beancount backends agree by construction)
- support regression comparison via
verify_ledger/1andoracle_balances/1
The mapper
Logistiki.Ledger.BeancountMapper converts at the boundary:
BeancountMapper.to_beancount_account(account) # %VirtualAccount{} -> "Assets:Cash:Usd:Nostro"
BeancountMapper.to_beancount_directive(journal, postings, accounts_by_code)
BeancountMapper.to_beancount_posting(posting, account)
BeancountMapper.to_beancount_opens(accounts)
BeancountMapper.from_beancount_balance(result)
BeancountMapper.from_beancount_entry(result)Account code mapping
Logistiki codes are colon-separated (LIABILITIES:CLIENT_DEPOSITS:USD:ACME:OPERATING).
The mapper:
- maps the account type to a Beancount root (
Assets,Liabilities,Equity,Income,Expenses); - drops the Logistiki root segment and CamelCases the rest (underscores removed).
The result is deterministic, stable, human-readable, and not based only on UUIDs. Each segment starts with an uppercase letter as Beancount requires.
Signed amounts
Beancount encodes direction as sign: debits are positive, credits are
negative, regardless of account type. Account balances then read naturally
(assets positive, liabilities/income negative) and match Logistiki's
net = debit - credit projection.
Backend configuration
config :logistiki, :ledger_backend, Logistiki.Ledger.BeancountAt runtime: Logistiki.put_ledger_backend(Logistiki.Ledger.Beancount).
The beancount_ex engine defaults to Beancount.Engine.Elixir (a pure-Elixir
booking engine) so the oracle works without a Python/bean-check install.
The real bean-check/bean-query CLI engine can be configured if desired.
Oracle verification
Logistiki.Ledger.Beancount.verify_ledger/1 renders all posted and reversed
journals to a Beancount ledger and runs Beancount.check/1.
oracle_balances/1 runs a BQL SELECT account, sum(position) GROUP BY account
and returns the oracle's balances for regression comparison.
Beancount as executable specification
Every future native backend must match Beancount behaviour for shared supported
features. The regression suite (see docs/testing_strategy.md) compares
Simulation-backend results against the Beancount oracle for generated valid
journals. Results must match.