Skip to content

SPEC 006 Transaction Ledger

Rafael Queiroz edited this page Sep 18, 2026 · 4 revisions

SPEC-006 — Transaction Ledger

Status Draft
PRD source F3 (FR-3.1–3.13), §7.1, DM-1, DM-5
Depends on SPEC-003
Required by SPEC-005, SPEC-007, SPEC-010, all report specs
Milestone M2

Description

The transaction ledger — the single source of truth for everything the product reports.

Two principles govern it. The ledger is authoritative and everything else is derived: positions, valuations, wallet allocations and every report figure are computed from transactions and can be rebuilt from them alone. If a cached derivation disagrees with a recomputation, the ledger wins. Every row is traceable: each transaction records whether it came from an import batch or manual entry, so any number in any report can be explained back to its source.

Also covers manual transaction management — essential both for assets outside B3 custody (a CDB the extract never showed) and for correcting import gaps.

Business rules

The ledger

ID Rule
BR-006-01 Transaction is the single source of truth. Position, DailyValuationSnapshot and all report figures are derived and rebuildable from it (DM-4).
BR-006-02 Every transaction records its provenance: the originating ImportBatch, or a manual-entry marker. Provenance is visible in the UI, not merely stored.
BR-006-03 Transaction.status ∈ {active, unclassified, superseded}. Only active rows enter calculations.
BR-006-04 Transaction.natural_key is unique per user, enforced by a database constraint (DM-1).
BR-006-05 Supported types: buy, sell, dividend, JCP, rendimento, amortization, split, grupamento, bonificação, subscription, transfer in, transfer out, adjustment, leilão de frações (the cash for a bonificação fraction sold at auction — a provento, SPEC-014 BR-014-01), fração de bonificação (a bonificação fraction B3 removed — quantity leaves at unchanged total cost, SPEC-007 BR-007-05a), conversion out and conversion in. Conversion legs share a required group identifier; conversion_in carries an exact cost-basis amount, and both types have zero cash value. They are created, edited and deleted only as one complete group. (Amended 2026-09-18, #113, #121.)
BR-006-06 Monetary values are stored as exact decimals — never floating point — at 2 places for currency and 8 for quantities (CR-2).

History & search

ID Rule
BR-006-07 A chronological, paginated list across all assets and institutions.
BR-006-08 Filterable by date range, asset, asset class, transaction type, institution and wallet.
BR-006-09 Full-text search on asset code and name.
BR-006-10 Exportable to CSV honouring the active filters, with formula-injection neutralisation (SPEC-003, BR-003-13).

Manual management

ID Rule
BR-006-11 A transaction can be created manually with type, asset, date, quantity, unit price, fees and institution.
BR-006-12 Any transaction can be edited, whether manual or imported.
BR-006-13 Any transaction can be deleted, with a confirmation stating what will be recalculated.
BR-006-14 Editing or deleting triggers recalculation of every derived figure from that date forward, and affected reports refresh.
BR-006-15 Validation prevents impossible states — most importantly selling more than the quantity held at that date — with an explanation of why, not a silent rejection.
BR-006-16 An edited imported transaction is flagged is_user_modified; a later re-import must not overwrite the correction.
BR-006-17 Bulk operations: delete multiple, assign multiple to a wallet.
BR-006-18 A backdated transaction is accepted and triggers recalculation forward from its date, not merely from today.

Acceptance Criteria

  • Transaction list paginates correctly over 10,000+ rows and loads within the SPEC performance budget.
  • Every filter works independently and in combination.
  • Search finds assets by code and by name.
  • Each row displays its provenance — import batch or manual — in the UI.
  • CSV export reflects active filters and neutralises formula-injection cells.
  • A CDB absent from every B3 extract can be added manually, appears in Portfolio Value, and can be allocated to a wallet.
  • All seventeen transaction types can be created, edited and deleted; conversion legs are managed atomically as one group.
  • Editing a transaction's quantity recalculates positions, average cost and every dependent report from that date forward.
  • Deleting a transaction shows what will be recalculated before confirming.
  • Attempting to sell more than held at that date is refused with an explanation naming the held quantity.
  • Editing an imported transaction flags it modified; re-importing the same extract does not revert the change.
  • A backdated transaction inserted between existing ones recalculates everything after it, and average cost is correct at every subsequent date.
  • Bulk delete and bulk wallet assignment operate on a multi-selection.
  • Position rebuilt from the ledger alone matches the incrementally-maintained Position exactly — asserted by an automated rebuild test (DM-4).
  • No monetary value is stored or computed in floating point, verified by schema and code inspection.

Out of Scope

  • Cost basis and average price computation (SPEC-007) — this spec stores transactions, that one interprets them.
  • Import parsing and reconciliation (SPEC-005).
  • Wallet allocation mechanics (SPEC-010) — bulk assignment here delegates to it.
  • Valuation and pricing (SPEC-008, SPEC-009).
  • Transaction-level tax treatment or IR classification.
  • A full audit trail of edits (who changed what, when) beyond the LGPD audit log in SPEC-004 — versioned transaction history is not in v1.
  • Undo/redo of edits.
  • Multi-currency transactions — BRL only.

Decision Logs

# Decision Alternatives considered Rationale
DL-006-01 The ledger is the single source of truth; everything else is derived and rebuildable. Maintain positions as authoritative state updated in place Derived-and-rebuildable means any calculation bug is fixable by correcting the logic and replaying — no data migration, no permanently corrupted state. Authoritative positions would make every past bug a data problem. The automated rebuild test (DM-4) keeps this honest.
DL-006-02 Imported transactions are editable. Lock imported rows as immutable B3 extracts have gaps — missing history before the export range, unmapped types, assets outside custody. If the user cannot correct them, their numbers stay wrong and they leave. The is_user_modified flag protects corrections from being reverted by re-import.
DL-006-03 Edits recalculate forward from the transaction date, not from today. Recalculate everything; recalculate only current state Recalculating only current state leaves historical charts wrong — the Portfolio Value line would silently disagree with the transactions behind it. Full recalculation is correct but wasteful on a long history. Forward-from-date is both correct and bounded.
DL-006-04 Deletion is permitted, with recalculation disclosed beforehand. Soft-delete only; forbid deletion of imported rows Users make genuine mistakes — duplicate manual entries, wrong asset. Forbidding deletion pushes them to workarounds that corrupt the ledger worse. Disclosing the recalculation before confirming makes the consequence visible.
DL-006-05 Exact decimals, never floating point. Floating point for simplicity Floating point silently loses precision in accumulation — average cost over hundreds of transactions drifts, and the number stops matching the broker's. Unfixable after the fact without a full rebuild.
DL-006-06 unclassified rows are stored but excluded from calculations. Reject them at import; include with a best-guess type Storing keeps reconciliation honest (the row exists and is visible) while excluding keeps calculations correct. Including a guess would corrupt figures invisibly, which the PRD's "honest over impressive" principle forbids.

Clone this wiki locally