Conditional Planning with means-plan#

means-plan turns one explicit scenario into an exact-cent cashflow and debt projection. It can project one repayment strategy or compare all implemented strategies under the same assumptions.

Important

This is a conditional deterministic projection, not a prediction or financial advice. The output says what the implemented rules calculate if the supplied dates, amounts, rates, and recurring events occur as written. It assigns no probability, confidence, or real-world accuracy to those assumptions.

The command is local and read-only with respect to Ledger data. It reads only the scenario file named on the command line, makes no provider or model request, and never reads or mutates a Ledger file. The only optional write is the report path explicitly supplied with --output.

Run a Fictional Scenario#

Save this public, fictional example as fictional-plan.yaml:

name: Fictional household baseline
as_of: 2026-08-01
through: 2027-07-31
starting_cash: '3500.00'
reserve_floor: '1500.00'
extra_debt_payment: '300.00'
allocation_day: 2

flows:
  - uid: salary
    name: Fictional salary
    category: income
    kind: inflow
    amount: '3200.00'
    start: 2026-08-01
    frequency: monthly

  - uid: rent
    name: Fictional rent
    category: housing
    kind: outflow
    amount: '1350.00'
    start: 2026-08-01
    frequency: monthly

  - uid: groceries
    name: Fictional groceries
    category: food
    kind: outflow
    amount: '125.00'
    start: 2026-08-03
    frequency: weekly

  - uid: repair
    name: Fictional repair
    category: maintenance
    kind: outflow
    amount: '425.00'
    start: 2026-09-10
    frequency: once

debts:
  - uid: card-ruby
    name: Fictional Ruby Card
    balance: '2400.00'
    annual_rate: '0.2199'
    minimum_payment: '75.00'
    due_day: 15
    monthly_charge: '0.00'

  - uid: card-cobalt
    name: Fictional Cobalt Card
    balance: '900.00'
    annual_rate: '0.1200'
    minimum_payment: '40.00'
    due_day: 20
    monthly_charge: '0.00'

Project the default avalanche strategy as JSON:

means-plan project --scenario fictional-plan.yaml --json

Select another strategy or produce spreadsheet-ready CSV:

means-plan project \
  --scenario fictional-plan.yaml \
  --strategy snowball \
  --csv \
  --output fictional-snowball.csv

Compare all three strategies under the identical scenario:

means-plan compare --scenario fictional-plan.yaml --json
means-plan compare \
  --scenario fictional-plan.yaml \
  --csv \
  --output fictional-comparison.csv

JSON is the default when --csv is absent, and --json and --csv are mutually exclusive. Without --output, report data goes to stdout. With --output, stdout stays empty, the report is written to an owner-only file, and a short wrote ... receipt goes to stderr. Invalid or unreadable scenario input returns exit code 2; an output write error returns exit code 3.

Scenario Schema#

Scenario files may be YAML (.yaml or .yml) or JSON (.json). The extension selects the parser; other extensions are rejected rather than guessed. The document root must be a mapping, and unknown keys are rejected recursively at the scenario, cashflow, and debt boundaries.

Scenario Fields#

Field

Type

Default

Rule

name

text

—

Required and nonblank

as_of

ISO date

—

First inclusive date in the horizon

through

ISO date

—

Last inclusive date; cannot precede as_of

starting_cash

money

—

Required exact-cent value; may be negative

reserve_floor

money

0.00

Nonnegative exact-cent amount protected from debt payments

flows

list of cashflows

[]

Cashflow uid values must be unique

debts

list of debts

[]

Debt uid values must be unique

extra_debt_payment

money

0.00

Nonnegative monthly target for avalanche or snowball allocation

allocation_day

integer from 1–31

1

Monthly date for extra allocation; overlong dates clamp to month end

Cashflow Fields#

Field

Type

Default

Rule

uid

text

—

Required, nonblank, and unique within flows

name

text

—

Required and nonblank

category

text

—

Required and nonblank; retained as an assumption label

kind

inflow or outflow

—

Direction of the cash event

amount

money

—

Positive exact-cent amount

start

ISO date

—

Recurrence anchor

frequency

once, weekly, biweekly, monthly, or annual

once

Recurrence rule

end

ISO date or null

null

Inclusive recurrence end; cannot precede start; once allows only null or start

Weekly events advance by seven days and biweekly events by fourteen days from start. Monthly events stay anchored to the start day, clamping to shorter month ends without drifting the next month. Annual events use the start month and day and apply the same month-end clamp. Only occurrences inside the inclusive scenario horizon and optional end are projected.

Debt Fields#

Field

Type

Default

Rule

uid

text

—

Required, nonblank, and unique within debts

name

text

—

Required and nonblank

balance

money

—

Nonnegative exact-cent starting balance

annual_rate

decimal ratio

—

Finite value from 0 through 5; 0.2199 means 21.99% APR

minimum_payment

money

—

Nonnegative exact-cent amount; must be positive when balance is positive

due_day

integer from 1–31

—

Monthly charge, interest, and minimum date; clamps to shorter month ends

monthly_charge

money

0.00

Nonnegative exact-cent charge added on each due date before interest accrues

Quote monetary and rate values in YAML, as in the example, so the parser hands decimal text to the exact-decimal model instead of first constructing a binary floating-point value. Money must represent an integral number of cents. Calculated money is rounded to cents with decimal ROUND_HALF_UP.

Calculation Rules#

Projection rows are emitted on event dates, not for every calendar day. On each event date, the engine applies the following sequence:

  1. Add all inflows and subtract all outflows for the date, ordered by stable cashflow uid.

  2. For each configured debt with a positive balance or monthly charge due that date, ordered by uid:

    • if same-day cash cannot fund every minimum, this uid order determines which debt receives cash first;

    • add monthly_charge, which can revive a zero balance;

    • accrue one month of interest as balance × annual_rate ÷ 12, rounded to cents;

    • attempt the lesser of the configured minimum and the current balance;

    • cap the actual payment at cash above reserve_floor; and

    • apply payment to accrued interest first, then principal.

  3. On allocation_day, allocate at most extra_debt_payment, again using only cash above the reserve.

    • avalanche orders active debts by highest annual rate, then lowest balance, then uid;

    • snowball orders them by lowest balance, then highest annual rate, then uid; and

    • minimum_only makes no extra allocation.

    If one debt is paid off, unused extra-payment money rolls to the next debt in the same ordered list.

  4. Evaluate the implemented cash warnings for that date.

The budget summary is an assumption summary, not a horizon-filtered cash budget. It converts every configured recurring amount to a monthly equivalent:

  • weekly: amount × 52 ÷ 12;

  • biweekly: amount × 26 ÷ 12;

  • monthly: amount; and

  • annual: amount ÷ 12.

It includes configured minimum payments even for zero starting balances, leaves one-off inflows and outflows separate, and does not include monthly_charge in monthly spending. Projection rows, rather than this summary, show which dated events actually intersect the horizon.

Warnings#

Warnings are rule-triggered calculation results, not a complete risk assessment.

Warning kind

Fires when

minimum_shortfall

Cash above the reserve cannot fund the required minimum payment on a due date

reserve_shortfall

Closing cash on an event date is below reserve_floor

negative_cash

Closing cash on an event date is below zero

debt_remains

Terminal debt is positive at the end of the horizon

A single event may produce both reserve and negative-cash warnings, and both records count separately in strategy comparison. debt_remains is a terminal projection warning rather than a row warning. Warnings are checked only on modeled event dates: a starting balance below the reserve is first warned when an event is processed. Configured outflows may cross the reserve, while minimum and extra debt payments cannot. An underfunded extra-payment target does not itself fire a warning.

Warning

Zero warnings means no implemented warning rule fired. It does not mean the scenario is safe, complete, likely, affordable, contract-compliant, or suitable for a real financial decision.

Strategy Comparison#

means-plan compare runs minimum_only, avalanche, and snowball with identical scenario assumptions. For each strategy it reports final cash, remaining debt, total interest, the date all modeled debts are paid off when that happens inside the horizon, and a shortfall_count. That count includes minimum, reserve, and negative-cash warnings and excludes the terminal debt_remains warning.

The recommended field is a rule-selected strategy under a disclosed comparison objective, not advice. The engine selects the lexicographically smallest result by:

  1. lowest remaining debt;

  2. lowest total interest paid;

  3. fewest counted shortfalls; and

  4. fixed tie-break preference: avalanche, then snowball, then minimum-only.

There is no separate feasibility gate. A strategy with shortfalls can therefore be selected, and payoff date is reported but is not part of this selection objective. Always inspect the metrics and warnings instead of treating the selected label as a real-world recommendation.

JSON and CSV Contracts#

JSON preserves the full nested result and serializes dates as ISO strings, enum values as their stable names, and decimal money as strings rather than binary floats. Those strings preserve exact decimal values but are not normalized to two displayed fractional digits, so an input such as '1' can render as "1" rather than "1.00".

A project JSON report contains:

  • scenario name, inclusive horizon, and selected strategy;

  • monthly-equivalent budget summary;

  • event rows with cash movement, interest, payments, debt balance, and row warnings;

  • per-debt outcomes with principal, interest, charges, remaining balance, and optional payoff date; and

  • final cash, remaining debt, total interest paid, and aggregate warnings.

total_interest is interest actually paid during the horizon. Accrued but unpaid interest remains in remaining_debt.

A compare JSON report contains the scenario name, one summary per strategy, and the rule-selected recommended strategy.

CSV is deliberately rectangular and less detailed:

  • projection CSV has one event row with scenario, date, opening_cash, inflows, outflows, interest_accrued, debt_payments, closing_cash, and total_debt;

  • comparison CSV has one strategy row with scenario, strategy, final_cash, remaining_debt, total_interest, payoff_date, shortfall_count, and recommended.

Use JSON when payments, per-debt outcomes, warnings, or the budget summary matter. Projection CSV does not include those nested details.

Privacy and Source Boundaries#

Keep personal scenario files and generated reports outside this public repository. Use opaque or fictional labels when sharing a reproduction. JSON includes scenario and debt labels plus warning messages; the command does not redact or encrypt them. Files created through --output are owner-only, while callers that redirect stdout are responsible for the destination’s permissions.

means-plan currently:

  • does not parse a historical Ledger into assumptions;

  • does not fetch bank or provider data;

  • does not call an LLM or infer omitted events;

  • does not mutate a source scenario or Ledger; and

  • writes only the explicit --output report when requested.

Ledger history and forward assumptions therefore remain separate by construction. If private Ledger information is used to author a private scenario or workbook, that extraction and the resulting artifacts remain a separate, operator-controlled workflow.

Honest Limitations#

The current model is intentionally narrow:

  • It models one implicit currency; there is no currency field or conversion.

  • It models explicit fixed cashflows, fixed APRs, fixed minimums, and one optional fixed monthly charge.

  • Interest is the implemented monthly APR ÷ 12 calculation on each due date, not a lender-specific daily balance, statement-cycle, grace-period, fee, or compounding contract.

  • It does not infer taxes, inflation, raises, irregular spending, late fees, credit limits, missed-payment consequences, or unlisted obligations.

  • It does not model probability, uncertainty distributions, ranges, or confidence intervals.

  • compare varies only the repayment strategy; it does not compare different cashflow or debt assumptions.

  • Rows show modeled event dates, so an absent row is not proof that the cash balance was externally observed on that day.

  • The budget summary covers configured assumptions rather than only in-horizon occurrences and omits monthly charges.

  • Exact decimal output is not a claim of real-world accuracy and may display fewer than two fractional digits.

  • There is no configured horizon, collection-size, or text-length limit; very large scenarios can require substantial local work.

  • There is no first-run scenario builder; the optional terminal and web interfaces load existing scenario documents rather than authoring them interactively.

Treat every output as a reproducible calculation receipt for a named set of assumptions. Revise those assumptions explicitly and rerun the command to examine a different hypothesis.