--- numbering: title: true --- # 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`: ```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: ```console means-plan project --scenario fictional-plan.yaml --json ``` Select another strategy or produce spreadsheet-ready CSV: ```console means-plan project \ --scenario fictional-plan.yaml \ --strategy snowball \ --csv \ --output fictional-snowball.csv ``` Compare all three strategies under the identical scenario: ```console 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.