calternal_money::budget
Exact zero-based envelope maths: Ready to Assign, Assigned, Activity, Available, rollover, funding states and card payment reserves.
Port of calculate in spikes/money-format/budget_math.py (the #404
oracle, rounds 1-3 plus two independent breakage reviews). The rules
(DESIGN §48, funding states):
- Assigning moves money from Ready to Assign into a category’s Available.
- Income and cash opening balances add to Ready to Assign.
- Positive Available rolls into the next month. Cash overspending (Available below zero from cash spending) is deducted from next month’s Ready to Assign; credit overspending becomes card debt instead.
- A card purchase moves the funded part of the category’s money into the card’s payment category (the reserve). Payments consume funded purchases oldest first by date (file order breaks ties); a refund releases only its own category’s unpaid reserve.
- Cash has first claim on a category that both cash and a card spend from in one month, independent of file order.
- A zero-value import reconciliation row can adjust Ready to Assign alone; it keeps Account and Category totals unchanged (#462, DESIGN §48).
Every value is an exact Minor in the budget currency; every sum is
checked, so an overflow is an error, never a wrapped number. Nothing here
is stored: the caller derives it from the files each time (F5).
Source: crates/calternal-money/src/budget.rs
Structs
Section titled “Structs”BillCoverage
Section titled “BillCoverage”pub struct BillCoverageA card bill compared with its funded reserve (never clamped, DESIGN §48).
Fields
pub bill: Minor: The full bill.pub reserve: Minor: The reserve, floored at zero.pub underfunded: Minor:max(0, bill - reserve).pub overfunded: Minor:max(0, reserve - bill): stays for the next cycle.pub status: CoverageStatus:Underfunded,OverfundedorFunded.
Implements: Clone, Copy, Debug, Eq, PartialEq, Serialize
Source: crates/calternal-money/src/budget.rs:156
CategoryMonth
Section titled “CategoryMonth”pub struct CategoryMonthOne category’s derived numbers for one month.
Fields
pub category_id: String: Category ID.pub assigned: Minor: Money assigned in this month.pub activity: Minor: Posted activity in this month (spending negative, inflow positive).pub available: Minor: Month-end Available after rollover in and this month’s activity.pub state: FundingState: Funding state.pub overspend: Option<OverspendKind>: Which account kind overspent it this month, if any.pub target: Option<Minor>: Monthly target.pub months_funded: Option<i64>: Full target months covered.pub target_remainder: Option<Minor>: Available beyond full target months.
Implements: Clone, Debug, Serialize
Source: crates/calternal-money/src/budget.rs:219
ImportReplay
Section titled “ImportReplay”pub struct ImportReplay<'a>Incremental Money calculation for an importer that projects one month at a time. The metadata Ledger must stay alive for the replay; each call accepts transactions from one parsed month and releases them when it returns. This keeps large import validation from retaining every month of transaction objects (#462, DESIGN §48).
ImportReplay::new
Section titled “ImportReplay::new”pub fn new(metadata: &'a Ledger) -> Result<Self>Start a replay from validated Budget and Account metadata. The Ledger may have no month files; it must contain the assignments and identities used by all streamed months (#462, DESIGN §48).
ImportReplay::month_with_transactions
Section titled “ImportReplay::month_with_transactions”pub fn month_with_transactions( &mut self, month: &str, transactions: &[Transaction], mut check: impl FnMut() -> Result<()>, ) -> Result<MonthTotals>Calculate one month from its temporary transaction rows, then release those rows while keeping balances and card purchase queues for the next month (#462, DESIGN §48).
ImportReplay::adjust_ready_to_assign
Section titled “ImportReplay::adjust_ready_to_assign”pub fn adjust_ready_to_assign(&mut self, amount: Minor) -> Result<()>Apply an import-only Ready to Assign reconciliation after a month. It changes no Account or Category total and carries into later months (#462, DESIGN §48).
Source: crates/calternal-money/src/budget.rs:421
MonthTotals
Section titled “MonthTotals”pub struct MonthTotalsOne derived month. Maps are keyed by ID and sorted.
Fields
pub month: String:YYYY-MM.pub ready_to_assign: Minor: Ready to Assign at the end of this month.pub categories: Vec<CategoryMonth>: Categories sorted by ID.pub card_debt: BTreeMap<String: Card account balances (negative is debt).pub card_payment_balance: BTreeMap<String: Card ID -> Available in its payment category (the reserve).pub debt_not_covered: BTreeMap<String: Card ID -> debt the reserve does not cover.pub account_balances: BTreeMap<String: Every account balance in budget minor units.
Implements: Clone, Debug, Serialize
MonthTotals::category
Section titled “MonthTotals::category”pub fn category(&self, id: &str) -> Option<&CategoryMonth>Find one category’s numbers.
Source: crates/calternal-money/src/budget.rs:242
CoverageStatus
Section titled “CoverageStatus”pub enum CoverageStatusThe coverage label for a bill.
Variants
Funded: Reserve equals the bill.Underfunded: Reserve below the bill.Overfunded: Reserve above the bill.
Implements: Clone, Copy, Debug, Eq, PartialEq, Serialize
CoverageStatus::label
Section titled “CoverageStatus::label”pub const fn label(self) -> &'static strThe oracle’s label.
Source: crates/calternal-money/src/budget.rs:171
FundingState
Section titled “FundingState”pub enum FundingStateA category’s derived funding state for one month.
Variants
Available: Available is above zero (no target, or less than one target month).Funded: Available is exactly zero.Overspent: Available is below zero from cash spending.Underfunded: Available is below zero from card spending, or a payment category is below zero.FundedAhead(i64): Available covers this many full target months (>= 1).
Implements: Clone, Copy, Debug, Eq, PartialEq, Serialize
FundingState::label
Section titled “FundingState::label”pub fn label(self) -> StringThe oracle’s label, for example Funded for next 2 months.
Source: crates/calternal-money/src/budget.rs:34
OverspendKind
Section titled “OverspendKind”pub enum OverspendKindWhich kind of account last overspent a category this month.
Variants
Cash: Cash spending: reduces next month’s Ready to Assign.Card: Card spending: becomes card debt.Tracking: Tracking-account spending: neither.
Implements: Clone, Copy, Debug, Eq, PartialEq, Serialize
OverspendKind::as_str
Section titled “OverspendKind::as_str”pub const fn as_str(self) -> &'static strThe oracle’s short name.
Source: crates/calternal-money/src/budget.rs:64
Functions
Section titled “Functions”bill_coverage
Section titled “bill_coverage”pub fn bill_coverage(bill: Minor, reserve: Minor) -> Result<BillCoverage>Compare an exact bill (>= 0) with a reserve in the same currency.
Invariant: bill - underfunded + overfunded == max(reserve, 0).
Source: crates/calternal-money/src/budget.rs:194
calculate
Section titled “calculate”pub fn calculate(ledger: &Ledger, months: &[String]) -> Result<Vec<MonthTotals>>Replay months (sorted YYYY-MM, no duplicates) and return one
MonthTotals per month. Transactions and assignments of months not in
the list are ignored, exactly like the oracle.
Conservation invariants (property-tested in tests/properties.rs): with
only cash accounts, Ready to Assign plus every category’s Available equals
the sum of account balances at every month end; with cards whose purchases
are fully funded, it equals the sum of cash balances (the card debt is
exactly covered by its payment reserve).
Source: crates/calternal-money/src/budget.rs:1039
calculate_pair
Section titled “calculate_pair”pub fn calculate_pair( before: &Ledger, after: &Ledger, months: &[String],) -> Result<(Vec<MonthTotals>, Vec<MonthTotals>)>Replay two validated ledger states over the same month range in one paired call. Transaction event routes need both sides of a durable write to detect a zero-to-negative crossing (#984, DESIGN §55); keeping the validation and month walk together prevents those routes from issuing separate replays.
Source: crates/calternal-money/src/budget.rs:1073
calculate_with_check
Section titled “calculate_with_check”pub fn calculate_with_check( ledger: &Ledger, months: &[String], mut check: impl FnMut() -> Result<()>,) -> Result<Vec<MonthTotals>>Replay sorted months with bounded row checkpoints for cancellable large
import previews (#462, DESIGN §48). The ordinary budget path calls
calculate and uses the same arithmetic without external cancellation.
Source: crates/calternal-money/src/budget.rs:1046
continuous_months
Section titled “continuous_months”pub fn continuous_months(ledger: &Ledger, through: &str) -> Result<Vec<String>>Every month from the first month with data through through, inclusive.
The product view uses this range: a month without a file counts as a month with no transactions, and assignments in such a month still count. (The oracle only walks months that have a file; see the crate docs.)
Source: crates/calternal-money/src/budget.rs:284
file_months
Section titled “file_months”pub fn file_months(ledger: &Ledger) -> Vec<String>The months of every month file, sorted: the oracle’s month list.
Source: crates/calternal-money/src/budget.rs:269
funding_state
Section titled “funding_state”pub fn funding_state( kind: CategoryKind, available: Minor, overspend: Option<OverspendKind>, target: Option<Minor>,) -> Result<(FundingState, Option<i64>, Option<Minor>)>Derive a category’s funding state and target months from exact Available.
Invariants: target, when present, is > 0; months_funded * target + remainder == max(available, 0) and 0 <= remainder < target.
Source: crates/calternal-money/src/budget.rs:96
ready_to_assign_label
Section titled “ready_to_assign_label”pub fn ready_to_assign_label(amount: Minor) -> &'static strThe Ready to Assign label: Assigned more than you have below zero.
Source: crates/calternal-money/src/budget.rs:131
rollover
Section titled “rollover”pub fn rollover(available: Minor, overspend: OverspendKind) -> (Minor, Minor)Carry a month-end Available into the next month.
Returns (next Available, change to next Ready to Assign): positive values carry; a cash deficit resets to zero and is deducted; any other deficit resets to zero with no deduction.