Engine API Reference
This page is generated from docstrings via Sphinx autodoc. It is intended for contributors who want to understand or extend the engine.
Note
LedgerLoom is still in early versions. The engine API is intentionally small and conservative. We prefer adding new helpers over changing existing behavior.
Package exports
LedgerLoom Engine (v0.1).
This package contains reusable accounting primitives extracted from the chapter runners. Chapters remain responsible for writing artifacts.
Public v0.1 API (intentionally small):
- ledgerloom.engine.config.LedgerEngineConfig
- ledgerloom.engine.ledger.LedgerEngine
- ledgerloom.engine.coa.COASchema
- class ledgerloom.engine.Account(code: 'str', name: 'str', account_type: 'str', normal_side: 'str', statement: 'str', rollup_code: 'str', is_contra: 'bool', is_active: 'bool', track_department: 'bool', track_project: 'bool', description: 'str')[source]
- account_type: str
- code: str
- description: str
- is_active: bool
- is_contra: bool
- name: str
- normal_side: str
- rollup_code: str
- statement: str
- track_department: bool
- track_project: bool
- class ledgerloom.engine.COASchema(accounts: tuple[Account, ...], segment_dimensions: tuple[dict[str, str], ...], segment_values: tuple[SegmentValue, ...])[source]
A COA schema bundle suitable for joins + validation.
- segment_dimensions: tuple[dict[str, str], ...]
- segment_values: tuple[SegmentValue, ...]
- class ledgerloom.engine.Dimension(name: str, key: str, default: str = '', required: bool = False)[source]
A configurable segment (dimension) materialized into the postings fact table.
Dimensions are read from
Entry.metaand written as separate string columns on the postings table (e.g., department, project, location).This stays intentionally simple in v0.1: - Entry-level metadata only (no posting-level overrides). - Deterministic: dimension columns appear in the configured order.
- default: str = ''
- key: str
- name: str
- required: bool = False
- class ledgerloom.engine.LedgerEngine(cfg: LedgerEngineConfig | None = None, *, config: LedgerEngineConfig | None = None)[source]
The reusable ledger compute engine.
The LedgerLoom engine is the contract layer between accounting ideas and software engineering practice:
Chapters are free to focus on pedagogy and artifacts.
The engine provides a single, tested implementation of conventions (normal balances, posting IDs, deterministic math).
Tests and invariants make refactors safe and keep outputs reproducible.
Example
>>> from ledgerloom.engine import LedgerEngine >>> eng = LedgerEngine() # or LedgerEngine(cfg=...), LedgerEngine(config=...) >>> postings = eng.postings_fact_table(entries)
v0.1 minimal API surface (methods): postings_fact_table, balances_by_* , running_balance_by_posting, invariants, gl_schema_description.
- balances_by_account_as_of(postings: DataFrame, as_of: date | str) DataFrame[source]
Balances grouped by account, using postings up to
as_of.
- cfg: LedgerEngineConfig
- class ledgerloom.engine.LedgerEngineConfig(debit_normal_roots: FrozenSet[str] = <factory>, credit_normal_roots: FrozenSet[str] = <factory>, entry_id_key: str = 'entry_id', department_key: str = 'department', dimensions: tuple[~ledgerloom.engine.config.Dimension, ...] | None=None, strict_validation: bool = False, entry_id_policy: Literal['strict', 'generated']='strict')[source]
Configuration for
LedgerEngine.Roots are the first segment of an account path, e.g.
Assets:Cash->Assets.Normal-balance convention: - debit-normal: balances increase with debits (Assets, Expenses) - credit-normal: balances increase with credits (Liabilities, Equity, Revenue)
The engine treats unknown roots as debit-normal for computation, but invariants will report them.
- credit_normal_roots: FrozenSet[str]
- debit_normal_roots: FrozenSet[str]
- department_key: str = 'department'
- property effective_dimensions: tuple[Dimension, ...]
Return configured dimension specs in deterministic order.
If
dimensionsis None, fall back to a single department dimension usingdepartment_key(backward-compatible with earlier chapters/apps).
- entry_id_key: str = 'entry_id'
- entry_id_policy: Literal['strict', 'generated'] = 'strict'
- property recognized_roots: FrozenSet[str]
- strict_validation: bool = False
- class ledgerloom.engine.SegmentValue(dimension_code: 'str', value_code: 'str', value_name: 'str')[source]
- dimension_code: str
- value_code: str
- value_name: str
- ledgerloom.engine.closing_entries_from_adjusted_tb(tb_adj: DataFrame, *, period: str, close_date: date) list[Entry][source]
Generate closing entries from an adjusted trial balance.
Returns a list of
Entryobjects.Closing policy (workbook-friendly): - Revenue accounts close directly to Retained Earnings. - Expense accounts close directly to Retained Earnings. - Dividend/Draw accounts (identified by account name) close to Retained Earnings.
The helper is deterministic: - stable entry_ids - stable account ordering inside each entry
Ledger compilation + views
Ledger engine (v0.1).
The engine takes a list of ledgerloom.core.Entry objects and produces
canonical ledger tables:
postings: one row per posting line (fact table)
balance views: by account / period / segment
invariants: explicit constraints you can assert in tests
This module intentionally stays “boring”: it copies chapter logic into a reusable core, keeping byte-for-byte identical artifacts when chapters call it.
- class ledgerloom.engine.ledger.LedgerEngine(cfg: LedgerEngineConfig | None = None, *, config: LedgerEngineConfig | None = None)[source]
The reusable ledger compute engine.
The LedgerLoom engine is the contract layer between accounting ideas and software engineering practice:
Chapters are free to focus on pedagogy and artifacts.
The engine provides a single, tested implementation of conventions (normal balances, posting IDs, deterministic math).
Tests and invariants make refactors safe and keep outputs reproducible.
Example
>>> from ledgerloom.engine import LedgerEngine >>> eng = LedgerEngine() # or LedgerEngine(cfg=...), LedgerEngine(config=...) >>> postings = eng.postings_fact_table(entries)
v0.1 minimal API surface (methods): postings_fact_table, balances_by_* , running_balance_by_posting, invariants, gl_schema_description.
- balances_by_account_as_of(postings: DataFrame, as_of: date | str) DataFrame[source]
Balances grouped by account, using postings up to
as_of.
- cfg: LedgerEngineConfig
- ledgerloom.engine.ledger.account_root(account: str) str[source]
Return the root segment of a colon-path account.
- ledgerloom.engine.ledger.balances_by_account(postings: DataFrame, cfg: LedgerEngineConfig) DataFrame[source]
Materialized view: balances grouped by account.
- ledgerloom.engine.ledger.balances_by_department(postings: DataFrame) DataFrame[source]
Materialized view: balances grouped by department and root.
- ledgerloom.engine.ledger.balances_by_dimension(postings: DataFrame, dimension: str) DataFrame[source]
Materialized view: balances grouped by a dimension and root.
- ledgerloom.engine.ledger.balances_by_period(postings: DataFrame) DataFrame[source]
Materialized view: balances grouped by period (YYYY-MM) and account.
- ledgerloom.engine.ledger.entry_department(entry: Entry, cfg: LedgerEngineConfig) str[source]
Backward-compatible helper: the configured “department” dimension value.
- ledgerloom.engine.ledger.entry_dimensions(entry: Entry, cfg: LedgerEngineConfig) dict[str, str][source]
Return configured dimension values for this entry.
Dimensions are read from
Entry.metaand materialized as columns on the postings fact table. Missing keys fall back to each dimension’sdefault.
- ledgerloom.engine.ledger.entry_id(entry: Entry, cfg: LedgerEngineConfig) str[source]
Return the stable identifier for an entry.
By default, LedgerLoom treats
entry.meta[cfg.entry_id_key]as required. This is a pragmatic constraint for real systems: it makes matching, reconciliation, and traceability explicit.The behavior is controlled by
ledgerloom.engine.config.LedgerEngineConfig.entry_id_policy:"strict": raise if missing"generated": synthesize a deterministic id from entry content
- ledgerloom.engine.ledger.gl_schema_description(cfg: LedgerEngineConfig | None = None) dict[str, Any][source]
A tiny schema description for the GL tables (for docs/tooling).
- ledgerloom.engine.ledger.invariants(entries: list[Entry], postings: DataFrame, cfg: LedgerEngineConfig) dict[str, Any][source]
Compute core invariants for a balanced ledger.
- ledgerloom.engine.ledger.postings_as_of(postings: DataFrame, as_of: date | str) DataFrame[source]
Filter postings to rows with
date <= as_of.The postings table stores dates as ISO strings (YYYY-MM-DD), so lexical comparison is safe and deterministic.
- ledgerloom.engine.ledger.postings_fact_table(entries: list[Entry], cfg: LedgerEngineConfig) DataFrame[source]
Build the postings fact table (one row per posting line).
- ledgerloom.engine.ledger.running_balance_by_posting(postings: DataFrame, cfg: LedgerEngineConfig | None = None) DataFrame[source]
Window-function style running balances per account.
- ledgerloom.engine.ledger.signed_cents(cfg: LedgerEngineConfig, root: str, debit_cents: int, credit_cents: int) int[source]
Return balance delta in the account’s normal sign convention.
- ledgerloom.engine.ledger.validate_entries(entries: list[Entry], cfg: LedgerEngineConfig) None[source]
Optional strict validation for real-world usage (opt-in).
This function is intentionally separate from invariants: - invariants are reports you can assert in tests - strict validation is an exception-throwing gate you can enable in apps
Enabled by setting
LedgerEngineConfig.strict_validation = Trueor by callingledgerloom.engine.ledger.LedgerEngine.validate_entries().
Chart of Accounts schema
Chart of Accounts (COA) engine primitives.
Chapter 03 introduces a COA as a schema: - accounts as a dimension table (account master) - rollups (parent/child) for reporting - segments (department/project) as extra dimensions
The engine provides data structures + deterministic computations. Chapters can write these out to CSV/JSON however they like.
- class ledgerloom.engine.coa.Account(code: 'str', name: 'str', account_type: 'str', normal_side: 'str', statement: 'str', rollup_code: 'str', is_contra: 'bool', is_active: 'bool', track_department: 'bool', track_project: 'bool', description: 'str')[source]
- account_type: str
- code: str
- description: str
- is_active: bool
- is_contra: bool
- name: str
- normal_side: str
- rollup_code: str
- statement: str
- track_department: bool
- track_project: bool
- class ledgerloom.engine.coa.COASchema(accounts: tuple[Account, ...], segment_dimensions: tuple[dict[str, str], ...], segment_values: tuple[SegmentValue, ...])[source]
A COA schema bundle suitable for joins + validation.
- segment_dimensions: tuple[dict[str, str], ...]
- segment_values: tuple[SegmentValue, ...]
- class ledgerloom.engine.coa.SegmentValue(dimension_code: 'str', value_code: 'str', value_name: 'str')[source]
- dimension_code: str
- value_code: str
- value_name: str
- ledgerloom.engine.coa.build_account_master_rows(accounts: Sequence[Account]) list[dict[str, str]][source]
- ledgerloom.engine.coa.dec_str_2(x: Decimal) str[source]
Stable 2-decimal formatting; normalize -0.00 -> 0.00.
- ledgerloom.engine.coa.default_accounts() list[Account][source]
A tiny but realistic default COA used in Chapter 03.
- ledgerloom.engine.coa.default_segments() tuple[list[dict[str, str]], list[SegmentValue]][source]
Configuration
LedgerLoom Engine configuration.
The “engine” is the reusable core that chapters can build on.
v0.1 design constraints - Small surface area (a handful of types/functions). - Explicit accounting conventions (normal balances by root). - Deterministic math (integer cents / stable string formatting). - Engine is pure-compute; chapters own file I/O.
- class ledgerloom.engine.config.Dimension(name: str, key: str, default: str = '', required: bool = False)[source]
A configurable segment (dimension) materialized into the postings fact table.
Dimensions are read from
Entry.metaand written as separate string columns on the postings table (e.g., department, project, location).This stays intentionally simple in v0.1: - Entry-level metadata only (no posting-level overrides). - Deterministic: dimension columns appear in the configured order.
- default: str = ''
- key: str
- name: str
- required: bool = False
- class ledgerloom.engine.config.LedgerEngineConfig(debit_normal_roots: FrozenSet[str] = <factory>, credit_normal_roots: FrozenSet[str] = <factory>, entry_id_key: str = 'entry_id', department_key: str = 'department', dimensions: tuple[~ledgerloom.engine.config.Dimension, ...] | None=None, strict_validation: bool = False, entry_id_policy: Literal['strict', 'generated']='strict')[source]
Configuration for
LedgerEngine.Roots are the first segment of an account path, e.g.
Assets:Cash->Assets.Normal-balance convention: - debit-normal: balances increase with debits (Assets, Expenses) - credit-normal: balances increase with credits (Liabilities, Equity, Revenue)
The engine treats unknown roots as debit-normal for computation, but invariants will report them.
- credit_normal_roots: FrozenSet[str]
- debit_normal_roots: FrozenSet[str]
- department_key: str = 'department'
- property effective_dimensions: tuple[Dimension, ...]
Return configured dimension specs in deterministic order.
If
dimensionsis None, fall back to a single department dimension usingdepartment_key(backward-compatible with earlier chapters/apps).
- entry_id_key: str = 'entry_id'
- entry_id_policy: Literal['strict', 'generated'] = 'strict'
- property recognized_roots: FrozenSet[str]
- strict_validation: bool = False
Money helpers
Deterministic money helpers.
Chapters intentionally write monetary amounts as strings with 2 decimals. Internally, the engine computes in integer cents to avoid floating-point drift.
Why integer cents? - Avoids floating-point rounding drift. - Makes invariants testable (sums are exact integers). - Keeps output deterministic across platforms.
Why explicit rounding? Decimal.quantize can consult the ambient Decimal context if no rounding mode is specified. LedgerLoom makes rounding explicit so results are stable and the convention is documented.
For v0.1, LedgerLoom uses ROUND_HALF_UP (“5 rounds up”).
- ledgerloom.engine.money.cents_to_str(cents: int) str[source]
Format cents as a fixed 2-decimal string.
Examples
0 -> “0.00” 12 -> “0.12” -305 -> “-3.05”