Skip to content

accounting

Accounting models: T-accounts, journal entries, and ledger.

This package provides the core double-entry bookkeeping primitives. Bank-specific accounts are in :mod:brms.core.models.accounting.bank_accounts.

Modules:

  • accounts

    Generic T-account types and account classifications for double-entry bookkeeping.

  • bank_accounts

    Pre-configured chart of accounts for a commercial bank.

  • chart_of_accounts

    Chart of accounts: organizes all accounts by category.

  • journal

    Journal entries and journal for recording accounting transactions.

  • ledger

    General ledger: posts journal entries to accounts and closes the books at period end.

Classes:

AccountBalances

Bases: UserDict['TAccount', float]

A dictionary-like class to hold account balances.

Methods:

  • from_accounts

    Create an AccountBalances instance from a list of accounts.

from_accounts classmethod

from_accounts(accounts: list[TAccount]) -> AccountBalances

Create an AccountBalances instance from a list of accounts.

AccountNormalBalance

Bases: Enum

The normal balance of an account or the preferred type of net balance that it should have.

In double-entry bookkeeping, every account has a "normal" side:

  • DEBIT_NORMAL: Asset and Expense accounts normally carry debit balances. Debits increase these accounts; credits decrease them.
  • CREDIT_NORMAL: Liability, Equity, and Income accounts normally carry credit balances. Credits increase these accounts; debits decrease them.

AccountType

Bases: Enum

Types of accounts in the accounting equation.

The five fundamental account types:

  • Asset -- resources owned (debit-normal)
  • Liability -- obligations owed (credit-normal)
  • Equity -- residual interest (credit-normal)
  • Income -- revenue earned (credit-normal, temporary)
  • Expense -- costs incurred (debit-normal, temporary)

Accounting equation: Assets = Liabilities + Equity

Methods:

get_normal_balance staticmethod

get_normal_balance(
    account_type: AccountType,
    *,
    contra_account: bool = False,
) -> AccountNormalBalance

Return the normal balance for a given account type.

A contra account has the opposite normal balance of its parent account type. For example, a contra-asset account has a credit-normal balance.

ChartOfAccounts dataclass

ChartOfAccounts(
    assets: list[TAccount] = list(),
    equities: list[TAccount] = list(),
    liabilities: list[TAccount] = list(),
    income: list[TAccount] = list(),
    expenses: list[TAccount] = list(),
    income_summary_account: IncomeSummaryAccount = IncomeSummaryAccount(),
    retained_earnings_account: RetainedEarningsAccount = RetainedEarningsAccount(),
)

A chart of accounts organizes all accounts by category.

::

+----------------------------------------------+
|              Chart of Accounts                |
+----------------------------------------------+
| Assets        | What the entity owns          |
| Liabilities   | What the entity owes          |
| Equity        | Owner's residual interest     |
| Income        | Revenue earned (temporary)    |
| Expenses      | Costs incurred (temporary)    |
+----------------------------------------------+
| Accounting equation:                          |
|   Assets = Liabilities + Equity               |
|                                               |
| At period end, Income and Expenses close to   |
| Retained Earnings (Equity) via Income Summary |
+----------------------------------------------+

Methods:

  • all_accounts

    Yield every account including nested sub-accounts (depth-first).

all_accounts

all_accounts() -> Generator[TAccount, None, None]

Yield every account including nested sub-accounts (depth-first).

Unlike __iter__, this expands composite accounts so that both the composite and all of its descendants are yielded. Useful for lookups by name when the target may be a sub-account.

ChartOfAccountsBuilder

ChartOfAccountsBuilder()

Builder for creating a ChartOfAccounts instance.

Usage::

coa = (
    ChartOfAccountsBuilder()
    .add_asset_account(cash)
    .add_liability_account(deposits)
    .add_equity_account(equity)
    .add_income_account(interest_income)
    .add_expense_account(interest_expense)
    .build()
)

Methods:

add_asset_account

add_asset_account(
    account: TAccount,
) -> ChartOfAccountsBuilder

Add an asset account to the builder.

add_equity_account

add_equity_account(
    account: TAccount,
) -> ChartOfAccountsBuilder

Add an equity account to the builder.

add_expense_account

add_expense_account(
    account: TAccount,
) -> ChartOfAccountsBuilder

Add an expense account to the builder.

add_income_account

add_income_account(
    account: TAccount,
) -> ChartOfAccountsBuilder

Add an income account to the builder.

add_liability_account

add_liability_account(
    account: TAccount,
) -> ChartOfAccountsBuilder

Add a liability account to the builder.

build

build() -> ChartOfAccounts

Build and return a ChartOfAccounts instance.

CompositeTAccount

CompositeTAccount(
    name: str,
    account_type: AccountType,
    contra_accounts: list[TAccount] | None = None,
    parent: TAccount | None = None,
    debit: float = 0.0,
    credit: float = 0.0,
    *,
    is_contra_account: bool = False,
)

Bases: TAccount

Composite T-account that can hold multiple sub T-accounts.

A composite account aggregates the balances of its children. Debiting or crediting a child automatically recalculates the parent's totals.

Example -- Investment Securities (composite)::

Investment Securities  [composite, debit-normal]
  +-- Investment HTM       debit: 5,000
  +-- Investment FVOCI     debit: 3,000
  = total debit: 8,000

Methods:

  • add_sub_account

    Add a child account to this composite.

  • balance

    Return the net balance of this account.

  • credit

    Add credit amount to account.

  • debit

    Add debit amount to account.

  • has_contra_account

    Check if this account has any contra accounts attached.

  • posting_accounts

    Recursively yield the lowest-level accounts that accept direct postings.

  • remove_sub_account

    Remove a child account from this composite.

Attributes:

credit_value property writable

credit_value: float

Return the credit value of the account.

debit_value property writable

debit_value: float

Return the debit value of the account.

parent property writable

parent: TAccount | None

Get the parent account.

sub_accounts property

sub_accounts: Generator[TAccount, None, None]

Yield direct child accounts.

add_sub_account

add_sub_account(account: TAccount) -> None

Add a child account to this composite.

The child's parent is set to this account, and balances are recalculated.

balance

balance() -> float

Return the net balance of this account.

Debit-normal accounts: balance = debits - credits. Credit-normal accounts: balance = credits - debits.

credit

credit(amount: float) -> None

Add credit amount to account.

debit

debit(amount: float) -> None

Add debit amount to account.

has_contra_account

has_contra_account() -> bool

Check if this account has any contra accounts attached.

posting_accounts

posting_accounts() -> Generator[TAccount, None, None]

Recursively yield the lowest-level accounts that accept direct postings.

If this composite has no children, yields itself. Otherwise, recurses into each child's posting_accounts.

remove_sub_account

remove_sub_account(account: TAccount) -> None

Remove a child account from this composite.

CompoundEntry dataclass

CompoundEntry(
    debit_accounts: dict[TAccount, float],
    credit_accounts: dict[TAccount, float],
    date: date | None = None,
    description: str = "",
)

Bases: JournalEntry

A compound journal entry that can affect multiple accounts.

Used when a transaction touches more than two accounts. The total debits must equal total credits (validated on creation).

Example: Closing a trading income account with sub-accounts::

+----------------------------------------------+
| Description: Closing Trading Income          |
+----------------------------------------------+
| Debit:  Unrealized Gain ......... $5,000     |
| Debit:  Realized Gain ........... $3,000     |
| Credit: Income Summary .......... $6,000     |
| Credit: Unrealized Loss ......... $2,000     |
+----------------------------------------------+

Methods:

  • credit_account_value_pairs

    Return an iterator over credit account and value pairs.

  • debit_account_value_pairs

    Return an iterator over debit account and value pairs.

  • involves_account

    Check if the journal entry involves a specific account.

  • is_balanced

    Check if the compound entry is balanced.

  • reversed

    Return a new CompoundEntry with debit and credit accounts swapped.

  • total_credits

    Calculate the total credits for the compound entry.

  • total_debits

    Calculate the total debits for the compound entry.

  • validate

    Assert sum of debit entries equals sum of credit entries.

credit_account_value_pairs

credit_account_value_pairs() -> (
    Iterator[tuple[TAccount, float]]
)

Return an iterator over credit account and value pairs.

debit_account_value_pairs

debit_account_value_pairs() -> (
    Iterator[tuple[TAccount, float]]
)

Return an iterator over debit account and value pairs.

involves_account

involves_account(account: TAccount) -> bool

Check if the journal entry involves a specific account.

is_balanced

is_balanced() -> bool

Check if the compound entry is balanced.

Using 1e-6 as a tolerance level to account for floating-point inaccuracies.

reversed

reversed() -> CompoundEntry

Return a new CompoundEntry with debit and credit accounts swapped.

total_credits

total_credits() -> float

Calculate the total credits for the compound entry.

total_debits

total_debits() -> float

Calculate the total debits for the compound entry.

validate

validate() -> None

Assert sum of debit entries equals sum of credit entries.

IncomeSummaryAccount

IncomeSummaryAccount()

Bases: TAccount

Represent the income summary account.

A temporary account used during period-end closing. All income and expense account balances are transferred here, and the net result is then closed to Retained Earnings.

Methods:

  • balance

    Return the net balance of this account.

  • credit

    Add credit amount to account.

  • debit

    Add debit amount to account.

  • has_contra_account

    Check if this account has any contra accounts attached.

  • posting_accounts

    Yield all accounts that can directly receive debit/credit postings.

Attributes:

  • credit_value (float) –

    Return the credit value of the account.

  • debit_value (float) –

    Return the debit value of the account.

  • parent (TAccount | None) –

    Get the parent account.

  • sub_accounts (Generator[TAccount, None, None]) –

    Yield direct child accounts. Empty for simple accounts.

credit_value property writable

credit_value: float

Return the credit value of the account.

debit_value property writable

debit_value: float

Return the debit value of the account.

parent property writable

parent: TAccount | None

Get the parent account.

sub_accounts property

sub_accounts: Generator[TAccount, None, None]

Yield direct child accounts. Empty for simple accounts.

balance

balance() -> float

Return the net balance of this account.

Debit-normal accounts: balance = debits - credits. Credit-normal accounts: balance = credits - debits.

credit

credit(amount: float) -> None

Add credit amount to account.

debit

debit(amount: float) -> None

Add debit amount to account.

has_contra_account

has_contra_account() -> bool

Check if this account has any contra accounts attached.

posting_accounts

posting_accounts() -> Generator[TAccount, None, None]

Yield all accounts that can directly receive debit/credit postings.

For a simple account, yields itself. For a composite account, recursively yields the lowest-level accounts in the hierarchy that are not themselves composites with children.

The ledger uses this when building closing entries to ensure postings go to accounts that accept direct value changes.

Journal dataclass

Journal(entries: list[JournalEntry] = list())

Class to record all journal entries.

Methods:

add_entry

add_entry(entry: JournalEntry) -> None

Add a journal entry to the journal.

get_entries_by_account

get_entries_by_account(
    account: TAccount,
) -> list[JournalEntry]

Get all journal entries involving a specific account.

get_entries_by_date

get_entries_by_date(date: date) -> list[JournalEntry]

Get all journal entries for a specific date.

get_entries_within_date_range

get_entries_within_date_range(
    start_date: date, end_date: date
) -> list[JournalEntry]

Get all journal entries within a specific date range.

JournalEntry

Bases: ABC

Abstract base class for a journal entry.

Methods:

credit_account_value_pairs

credit_account_value_pairs() -> (
    Iterator[tuple[TAccount, float]]
)

Return an iterator over credit account and value pairs.

debit_account_value_pairs

debit_account_value_pairs() -> (
    Iterator[tuple[TAccount, float]]
)

Return an iterator over debit account and value pairs.

involves_account abstractmethod

involves_account(account: TAccount) -> bool

Check if the journal entry involves a specific account.

reversed abstractmethod

reversed() -> JournalEntry

Return a new entry with debits and credits swapped (reversing entry).

Ledger dataclass

Ledger(
    chart_of_accounts: ChartOfAccounts,
    journal: Journal = Journal(),
    date_closed: date | None = None,
)

The general ledger.

Attributes:

  • chart_of_accounts (ChartOfAccounts) –

    The chart of accounts containing all T-accounts.

  • journal (Journal) –

    The journal recording every posted entry.

  • date_closed (date | None) –

    The date of the most recent period-end close, or None.

Methods:

close_contra_accounts

close_contra_accounts(date: date) -> None

Net contra account balances against their parent income/expense accounts.

For an income account with contra expenses (e.g., Trading Income with Unrealized Trading Loss), this posts an entry that zeroes the contra balances and reduces the parent's balance by the same amount.

close_income_and_expense_accounts

close_income_and_expense_accounts(date: date) -> None

Transfer all income and expense balances to Income Summary.

Skips contra accounts (already closed) and the Income Summary account itself.

close_income_summary_account

close_income_summary_account(date: date) -> None

Transfer the Income Summary balance to Retained Earnings.

close_ledger

close_ledger(date: date) -> None

Run the full period-end closing sequence.

  1. Net contra accounts against their parents.
  2. Transfer income/expense balances to Income Summary.
  3. Transfer Income Summary to Retained Earnings.

get_account_balances

get_account_balances() -> AccountBalances

Return a snapshot of all account balances.

get_accounts_by_type

get_accounts_by_type(
    account_type: AccountType,
) -> list[TAccount]

Return all accounts matching the given type.

post

post(entry: JournalEntry) -> None

Post a journal entry: record it and update account balances.

For each debit leg, the corresponding account's debit side increases. For each credit leg, the corresponding account's credit side increases. The entry is appended to the journal for audit purposes.

set_account_balances

set_account_balances(balances: AccountBalances) -> None

Set opening balances directly on accounts.

This bypasses the journal (no entry is recorded) and should only be used for initial setup, never during normal operation.

Each balance is applied to the account's normal side: debit-normal accounts get a debit, credit-normal accounts get a credit.

RetainedEarningsAccount

RetainedEarningsAccount()

Bases: TAccount

Represent the retained earnings account.

Accumulated net income that has not been distributed. During period-end closing, the Income Summary balance is transferred here.

Methods:

  • balance

    Return the net balance of this account.

  • credit

    Add credit amount to account.

  • debit

    Add debit amount to account.

  • has_contra_account

    Check if this account has any contra accounts attached.

  • posting_accounts

    Yield all accounts that can directly receive debit/credit postings.

Attributes:

  • credit_value (float) –

    Return the credit value of the account.

  • debit_value (float) –

    Return the debit value of the account.

  • parent (TAccount | None) –

    Get the parent account.

  • sub_accounts (Generator[TAccount, None, None]) –

    Yield direct child accounts. Empty for simple accounts.

credit_value property writable

credit_value: float

Return the credit value of the account.

debit_value property writable

debit_value: float

Return the debit value of the account.

parent property writable

parent: TAccount | None

Get the parent account.

sub_accounts property

sub_accounts: Generator[TAccount, None, None]

Yield direct child accounts. Empty for simple accounts.

balance

balance() -> float

Return the net balance of this account.

Debit-normal accounts: balance = debits - credits. Credit-normal accounts: balance = credits - debits.

credit

credit(amount: float) -> None

Add credit amount to account.

debit

debit(amount: float) -> None

Add debit amount to account.

has_contra_account

has_contra_account() -> bool

Check if this account has any contra accounts attached.

posting_accounts

posting_accounts() -> Generator[TAccount, None, None]

Yield all accounts that can directly receive debit/credit postings.

For a simple account, yields itself. For a composite account, recursively yields the lowest-level accounts in the hierarchy that are not themselves composites with children.

The ledger uses this when building closing entries to ensure postings go to accounts that accept direct value changes.

SimpleEntry dataclass

SimpleEntry(
    debit_account: TAccount,
    credit_account: TAccount,
    value: float,
    date: date | None = None,
    description: str = "",
)

Bases: JournalEntry

A simple journal entry affecting exactly two accounts.

Example: Receiving a $10,000 deposit::

+----------------------------------------------+
| Date: 2024-01-15                             |
| Description: Customer deposit received       |
+----------------------------------------------+
| Debit:  Cash ..................... $10,000    |
| Credit: Deposits ................ $10,000    |
+----------------------------------------------+

Methods:

credit_account_value_pairs

credit_account_value_pairs() -> (
    Iterator[tuple[TAccount, float]]
)

Return an iterator over credit account and value pairs.

debit_account_value_pairs

debit_account_value_pairs() -> (
    Iterator[tuple[TAccount, float]]
)

Return an iterator over debit account and value pairs.

involves_account

involves_account(account: TAccount) -> bool

Check if the journal entry involves a specific account.

reversed

reversed() -> SimpleEntry

Return a new SimpleEntry with debit and credit accounts swapped.

TAccount

TAccount(
    name: str,
    account_type: AccountType,
    contra_accounts: list[TAccount] | None = None,
    parent: TAccount | None = None,
    debit: float = 0.0,
    credit: float = 0.0,
    description: str = "",
    *,
    is_contra_account: bool = False,
    is_temporary_account: bool = False,
)

A T-account in double-entry bookkeeping.

A T-account has two sides: debit (left) and credit (right). The normal balance determines which side increases the account value.

Asset and Expense accounts have DEBIT normal balance::

+----------------------------------+
|         Cash (Asset)             |
+-----------------+----------------+
|     Debit       |    Credit      |
|   (increase)    |   (decrease)   |
+-----------------+----------------+
|    1,000        |      200       |
|      500        |                |
+-----------------+----------------+
|   Balance: 1,300                 |
+----------------------------------+

Liability, Equity, and Income accounts have CREDIT normal balance::

+----------------------------------+
|       Deposits (Liability)       |
+-----------------+----------------+
|     Debit       |    Credit      |
|   (decrease)    |   (increase)   |
+-----------------+----------------+
|                 |    5,000       |
|      200        |    1,000       |
+-----------------+----------------+
|              Balance: 5,800      |
+----------------------------------+

Double-entry principle: every transaction debits one account and credits another for the same amount, keeping the books balanced.

Example: Receiving a $10,000 deposit::

Cash.debit(10_000)      -- Cash increases
Deposits.credit(10_000) -- Deposits increases

Methods:

  • balance

    Return the net balance of this account.

  • credit

    Add credit amount to account.

  • debit

    Add debit amount to account.

  • has_contra_account

    Check if this account has any contra accounts attached.

  • posting_accounts

    Yield all accounts that can directly receive debit/credit postings.

Attributes:

  • credit_value (float) –

    Return the credit value of the account.

  • debit_value (float) –

    Return the debit value of the account.

  • parent (TAccount | None) –

    Get the parent account.

  • sub_accounts (Generator[TAccount, None, None]) –

    Yield direct child accounts. Empty for simple accounts.

credit_value property writable

credit_value: float

Return the credit value of the account.

debit_value property writable

debit_value: float

Return the debit value of the account.

parent property writable

parent: TAccount | None

Get the parent account.

sub_accounts property

sub_accounts: Generator[TAccount, None, None]

Yield direct child accounts. Empty for simple accounts.

balance

balance() -> float

Return the net balance of this account.

Debit-normal accounts: balance = debits - credits. Credit-normal accounts: balance = credits - debits.

credit

credit(amount: float) -> None

Add credit amount to account.

debit

debit(amount: float) -> None

Add debit amount to account.

has_contra_account

has_contra_account() -> bool

Check if this account has any contra accounts attached.

posting_accounts

posting_accounts() -> Generator[TAccount, None, None]

Yield all accounts that can directly receive debit/credit postings.

For a simple account, yields itself. For a composite account, recursively yields the lowest-level accounts in the hierarchy that are not themselves composites with children.

The ledger uses this when building closing entries to ensure postings go to accounts that accept direct value changes.