Skip to content

Services

wealthbraid.services.importing

CSV statement import: evidence in, statement lines out.

Importing never creates accounting entries. It stores the file as evidence and proposes one line record per row: an observation of what the bank reported. Lines decide what can enter the book (a recorded fingerprint blocks the same row from being imported again), so the proposal waits for human approval like any other sensitive change. Turning lines into entries is the job of categorization.

Each line carries a fingerprint so re-importing the same (or an overlapping) statement skips rows already recorded or already proposed. The fingerprint uses the account, date, amount, commodity, and normalised description, plus the row's occurrence number among identical rows in the same file, so two genuine identical purchases on one day are both kept.

ImportResult dataclass

The outcome of an import.

Attributes:

Name Type Description
evidence str

The evidence record id of the imported file.

rows int

Rows parsed from the file.

new_lines int

Rows proposed as new statement lines.

duplicates list[int]

Rows skipped because they were already recorded.

operation OperationState | None

The line-import operation, if any rows were new.

ParsedRow dataclass

One statement row parsed from a CSV file.

fingerprint_rows(rows, *, account, commodity)

Compute a deduplication fingerprint for every row.

The fingerprint covers what identifies a transaction regardless of export format: account, date, amount, commodity, and normalised description, plus the row's occurrence number among identical rows in the same file. Bank references are deliberately left out: many banks reuse them across statements, and the same transaction exported with and without a reference column must still be recognised.

Parameters:

Name Type Description Default
rows list[ParsedRow]

The parsed rows in file order.

required
account str

The statement account.

required
commodity str

The statement commodity.

required

Returns:

Type Description
list[str]

One hex fingerprint per row.

import_csv(book, path, *, actor, profile, account=None, commodity=None, source=None)

Import a CSV statement as evidence plus proposed statement lines.

Parameters:

Name Type Description Default
book Book

The book to import into.

required
path Path

The CSV file.

required
actor str

Who is importing.

required
profile ImportProfile

The column mapping.

required
account str | None

The statement account (overrides the profile's).

None
commodity str | None

The statement commodity (overrides the profile's; defaults to the book currency).

None
source str | None

Where the statement came from.

None

Returns:

Name Type Description
The ImportResult

class:ImportResult.

Raises:

Type Description
NotFoundError

If the file does not exist.

UsageError

If no statement account is given.

parse_csv(content, profile)

Parse a CSV statement with a column mapping.

Parameters:

Name Type Description Default
content bytes

The raw file bytes (UTF-8, optionally with a BOM).

required
profile ImportProfile

The column mapping.

required

Returns:

Type Description
list[ParsedRow]

The parsed rows in file order.

Raises:

Type Description
ValidationError

If the file cannot be decoded, a column is missing, or a cell is malformed.

UsageError

If the profile names neither an amount column nor debit/credit columns.

wealthbraid.services.categorize

Categorization: proposing entries for unmatched statement lines.

Each unmatched statement line becomes a proposed two-posting entry: the line's amount on the statement account, balanced by a category (counter) account. The category comes from one of two sources, and both produce an operation that waits for human approval:

  • the book's ordered rules ([[rules]] in wealthbraid.toml), or
  • explicit assignments supplied by an agent or human, each with an optional rationale and confidence.

The operation records the rules or assignments used, the evidence behind every line, and the lowest per-line confidence as its overall confidence.

Assignment dataclass

A category chosen for one statement line.

Attributes:

Name Type Description
line str

The statement line id.

account str

The counter account.

confidence float

Confidence in this assignment.

rationale str

Why this account was chosen.

categorize(book, *, actor, assignments=None, reasoning=None, limit=None)

Propose entries for unmatched statement lines.

Rule-based categorization skips lines a pending proposal already covers, so running it twice does not queue duplicates. Explicit assignments may still cover such lines, for example to offer the reviewer an alternative.

Parameters:

Name Type Description Default
book Book

The book.

required
actor str

The proposer.

required
assignments Sequence[Assignment] | None

Explicit assignments; when omitted, the book's rules are used.

None
reasoning str | None

The proposer's reasoning summary (required with explicit assignments).

None
limit int | None

Categorize at most this many lines.

None

Returns:

Type Description
OperationState | None

The proposed operation (None if nothing matched) and the unmatched line ids that are

list[str]

neither in this proposal nor in another pending proposal.

Raises:

Type Description
UsageError

If explicit assignments come without reasoning or name duplicate lines.

ValidationError

If an assignment names a line that is unknown or already matched.

entry_for(line_id, line, account)

Build the entry payload that accounts for a statement line.

Parameters:

Name Type Description Default
line_id str

The statement line id.

required
line LineData

The statement line.

required
account str

The counter account.

required

Returns:

Type Description
dict[str, Any]

An entry payload matching the line.

line_text(line)

Return the text rules match against.

Parameters:

Name Type Description Default
line LineData

The statement line.

required

Returns:

Type Description
str

Payee and description joined by a space.

parse_assignments(raw)

Parse assignments supplied as JSON.

Parameters:

Name Type Description Default
raw Sequence[Mapping[str, Any]]

Objects with line, account, and optional confidence and rationale.

required

Returns:

Type Description
list[Assignment]

The parsed assignments.

Raises:

Type Description
UsageError

If an item is malformed.

suggest_by_rules(state, rules, lines)

Match statement lines against rules; the first matching rule wins.

Parameters:

Name Type Description Default
state BookState

The book state.

required
rules Sequence[CategorizeRule]

The ordered rules.

required
lines Sequence[str]

Candidate line ids.

required

Returns:

Type Description
list[Assignment]

Assignments for the lines some rule matched.

wealthbraid.services.reconcile

Reconciliation: checking a statement balance against the ledger.

reconcile computes the ledger balance of an account (and its sub-accounts) as of the statement date, compares it with the balance the statement reports, lists the statement lines up to that date that no entry accounts for yet, and proposes a reconciliation record for human approval.

An approved reconciliation is a checkpoint. reconciliation_status re-evaluates every checkpoint against the book as it is now, so a later correction that changes a reconciled period shows up as an exception instead of silently invalidating the reconciliation.

ledger_balance(state, account, date, commodity)

Return the balance of an account subtree in one commodity as of a date.

Parameters:

Name Type Description Default
state BookState

The book state.

required
account str

The account.

required
date date

The as-of date, inclusive.

required
commodity str

The commodity.

required

Returns:

Type Description
Decimal

The balance.

reconcile(book, *, actor, account, date, statement_balance, commodity=None, evidence=(), note=None)

Compare a statement balance with the ledger and propose a reconciliation.

Parameters:

Name Type Description Default
book Book

The book.

required
actor str

The proposer.

required
account str

The statement account.

required
date date

The statement date.

required
statement_balance str

The balance printed on the statement.

required
commodity str | None

The statement commodity (defaults to the book currency).

None
evidence Sequence[str]

Evidence ids for the statement.

()
note str | None

A free-text note.

None

Returns:

Type Description
tuple[dict[str, Any], OperationState]

The comparison and the proposed operation.

Raises:

Type Description
ValidationError

If the account is not open or the balance is not a decimal.

reconciliation_status(state)

Re-check every applied reconciliation against the current book.

Parameters:

Name Type Description Default
state BookState

The book state.

required

Returns:

Type Description
list[dict[str, Any]]

One row per reconciliation with status:

list[dict[str, Any]]
  • superseded: a later reconciliation covers the same account, date, and commodity;
list[dict[str, Any]]
  • new_lines: statement lines dated inside the period arrived after it was recorded and are not yet accounted for;
list[dict[str, Any]]
  • balanced: the ledger now agrees with the statement;
list[dict[str, Any]]
  • changed: it agreed when recorded, but later changes moved the ledger balance;
list[dict[str, Any]]
  • discrepancy: it never agreed and still does not.
list[dict[str, Any]]

data:RESOLVED_STATUSES need no attention.

unmatched_lines_for(state, account, date)

Return unmatched statement lines of an account subtree up to a date.

Parameters:

Name Type Description Default
state BookState

The book state.

required
account str

The account.

required
date date

The as-of date, inclusive.

required

Returns:

Type Description
list[str]

Line ids in date order.

wealthbraid.services.explain

Explanations: why a balance changed, and where a record came from.

explain_change decomposes the change in an account's balance over a period into contributions from counter accounts. Within each entry, the postings to the explained account subtree are balanced by the entry's other postings, so the contributions sum exactly to the balance change. The result carries that check explicitly (reconciles) rather than assuming it.

trace walks provenance links in both directions: from a record to the operation, decision, evidence, and statement lines behind it, and to the corrections, matches, and notes that came after it.

explain_change(state, *, account, start, end)

Decompose an account subtree's balance change over a period.

Parameters:

Name Type Description Default
state BookState

The book state.

required
account str

The account (subtree root) to explain.

required
start date

First day of the period, inclusive.

required
end date

Last day of the period, inclusive.

required

Returns:

Type Description
dict[str, Any]

Opening and closing balances, the change, contributions per counter

dict[str, Any]

account (largest first), the entries involved, and a reconciles flag.

Raises:

Type Description
NotFoundError

If no opened account lies in the subtree.

ValidationError

If start is after end.

trace(state, record_id)

Collect the provenance of a record.

Parameters:

Name Type Description Default
state BookState

The book state.

required
record_id str

Any record id.

required

Returns:

Type Description
dict[str, Any]

The record, the operation behind it, and related records upstream

dict[str, Any]

(evidence, statement lines, earlier versions) and downstream

dict[str, Any]

(corrections, matching entries, notes, derived lines).

Raises:

Type Description
NotFoundError

If the record does not exist.

wealthbraid.services.reports

Financial summaries derived from book state.

Every report is a plain, JSON-compatible dictionary with amounts as decimal strings and a basis block naming the record the report was computed from (as_of_record). Re-running a report with --at <record> against the same record log reproduces it exactly.

amounts(inventory)

Render an inventory as {commodity: quantity}.

Parameters:

Name Type Description Default
inventory Inventory

The inventory.

required

Returns:

Type Description
dict[str, str]

A commodity-sorted mapping of decimal strings.

balances(state, *, as_of=None, account=None)

Return the balance of every account, with roll-ups per account type.

Parameters:

Name Type Description Default
state BookState

The book state.

required
as_of date | None

Include entries up to and including this date.

None
account str | None

Only include this account and its descendants.

None

Returns:

Type Description
dict[str, Any]

accounts (non-empty leaf balances) and totals by account type.

basis(state, **parameters)

Describe what a derived view was computed from.

Parameters:

Name Type Description Default
state BookState

The book state.

required
**parameters Any

The report parameters (dates are converted to ISO strings).

{}

Returns:

Type Description
dict[str, Any]

The head record id, record count, counts of issues (records excluded

dict[str, Any]

from the ledger) and integrity issues (records whose content or chain

dict[str, Any]

link is broken), and the parameters.

cashflow(state, *, start, end, currency)

Return monthly income, spending, savings, and savings rate in one commodity.

Only postings denominated in currency are counted; this report does not convert, so it never silently mixes currencies.

Parameters:

Name Type Description Default
state BookState

The book state.

required
start date

First day of the range.

required
end date

Last day of the range.

required
currency str

The commodity to summarise.

required

Returns:

Type Description
dict[str, Any]

One row per calendar month plus range totals.

income_statement(state, *, start, end)

Return income and expenses over a period.

Income is shown as a positive number (credit-normal accounts are sign-flipped).

Parameters:

Name Type Description Default
state BookState

The book state.

required
start date | None

First day of the period, inclusive.

required
end date | None

Last day of the period, inclusive.

required

Returns:

Type Description
dict[str, Any]

Income rows, expense rows, their totals, and net income per commodity.

net_worth(state, *, as_of, currency)

Return assets, liabilities, and net worth valued in one currency.

Converted amounts are rounded to cents; native amounts are kept as recorded. The report lists every recorded price it relied on, with its date and age, and warns when a pair has direct and inverse prices that disagree by more than one percent. Holdings without a price path to currency are reported per side in unvalued_assets and unvalued_liabilities rather than dropped or netted.

Parameters:

Name Type Description Default
state BookState

The book state.

required
as_of date

The valuation date (entries and prices up to this date).

required
currency str

The reporting currency.

required

Returns:

Type Description
dict[str, Any]

Valued totals, unvalued remainders, the prices used, price warnings, and

dict[str, Any]

the per-account breakdown.

price_warnings(latest)

Flag pairs whose direct and inverse prices disagree by more than one percent.

Parameters:

Name Type Description Default
latest dict[tuple[Commodity, Commodity], Price]

The latest price per directed pair.

required

Returns:

Type Description
list[str]

One human-readable warning per inconsistent pair.

wealthbraid.services.scenarios

Deterministic wealth scenarios.

A scenario projects a starting amount forward month by month with contributions, withdrawals, a nominal return, and inflation. Everything is Decimal arithmetic with fixed rules, so the same inputs and the same book record always give the same numbers.

Monthly rates are the geometric equivalents of the annual rates ((1 + r) ** (1/12) - 1), so twelve months of growth compound to exactly the stated annual rate. Contributions are added at the end of each month (after that month's growth); withdrawals likewise. Each projected year decomposes as

end = start + contributions - withdrawals + growth

All figures are reported in cents. The reported growth is the balancing figure of the reported cents, so the identity holds exactly for the numbers as printed; growth_exact is the unrounded growth, and reconciles checks that the two differ only by rounding (at most two cents).

ScenarioAssumptions

Bases: BaseModel

The tunable assumptions of a scenario. Rates are annual fractions ("0.05" = 5%).

ScenarioSpec

Bases: BaseModel

A scenario file: base assumptions plus named variants that override some of them.

monthly_rate(annual)

Convert an annual rate to its geometric monthly equivalent.

Parameters:

Name Type Description Default
annual Decimal

The annual rate as a fraction.

required

Returns:

Type Description
Decimal

The monthly rate.

Raises:

Type Description
ValidationError

If the rate is -100% or lower.

parse_spec(raw)

Validate a scenario specification.

Parameters:

Name Type Description Default
raw Any

The decoded TOML or JSON document.

required

Returns:

Type Description
ScenarioSpec

The parsed :class:ScenarioSpec.

Raises:

Type Description
ValidationError

If the document is malformed.

project(assumptions, *, start_amount, years)

Project a balance year by year.

Parameters:

Name Type Description Default
assumptions ScenarioAssumptions

The scenario assumptions.

required
start_amount Decimal

The starting balance.

required
years int

The horizon in years.

required

Returns:

Type Description
list[dict[str, Any]]

One row per year with the start, contributions, withdrawals, growth, end

list[dict[str, Any]]

(nominal), end in start-date money (real), and the decomposition check.

run_scenario(state, spec, *, default_currency)

Run a scenario and each of its variants.

Parameters:

Name Type Description Default
state BookState

The book state (used for the starting net worth when not given).

required
spec ScenarioSpec

The scenario specification.

required
default_currency str

The currency when the spec names none.

required

Returns:

Type Description
dict[str, Any]

Per-variant yearly projections and summaries, the starting point, the

dict[str, Any]

inputs digest, and the record basis.

wealthbraid.services.review

The human review queue: everything that needs a person's attention.

The queue gathers, in one place:

  • pending operations, lowest confidence first;
  • statement lines no entry accounts for yet, with any pending proposals covering them;
  • reconciliations that never balanced or that later changes invalidated;
  • integrity issues raised while projecting the record log.

review_queue(state)

Build the review queue.

Parameters:

Name Type Description Default
state BookState

The book state.

required

Returns:

Type Description
dict[str, Any]

Pending operations, unmatched lines, reconciliation exceptions, issues, and counts.