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: |
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]]inwealthbraid.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
¶
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 ( |
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)
¶
parse_assignments(raw)
¶
Parse assignments supplied as JSON.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
Sequence[Mapping[str, Any]]
|
Objects with |
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 |
list[dict[str, Any]]
|
|
list[dict[str, Any]]
|
|
list[dict[str, Any]]
|
|
list[dict[str, Any]]
|
|
list[dict[str, Any]]
|
|
list[dict[str, Any]]
|
data: |
unmatched_lines_for(state, account, date)
¶
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 |
Raises:
| Type | Description |
|---|---|
NotFoundError
|
If no opened account lies in the subtree. |
ValidationError
|
If |
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)
¶
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]
|
|
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)
¶
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)
¶
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: |
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.