mirror of
https://github.com/we-promise/sure.git
synced 2026-05-29 23:39:03 +00:00
Pure-Ruby projection engine for a single plan. Models in real (today's-money) terms: portfolio grows at the real return, spending and pension incomes are held in today's money, so no inflation parameter is needed and output is fully deterministic. v2 swaps in a Sidekiq Monte Carlo behind the same call interface. POROs (app/models/retirement/): - Fire::Forecast — annual stepper. Accumulate (×(1+r)+savings) to retire_age, then draw down max(target − net pension income, 0) with lump payouts as portfolio deltas. Computes the glide series, money-lasts-to age, terminal value, Coast FIRE age (bisection on the minimum survivable portfolio at retirement), feasibility, warnings. - Fire::Payout — normalises a PensionSource to gross annual income + one-time lump per age, across the four payout shapes. - Fire::Adjustment — age-bounded signed change to the spending target. - Fire::CohortAccess — min access age (UK NMPA 55→57 from 2028, US 59.5/62, DE 63/55). - Tax::StaticRate (+ initializer) — v1 fraction-kept by treatment; de_renten falls with the cohort year. Boot-validated against the PensionSource enum. Wiring: Goal::Retirement gains retirement_params store_accessor (birth_year, retire_age, real_return_pct, monthly_savings, target_spend, terminal_age), bucket_value, payouts, forecast_inputs, memoised #forecast (nil until birth_year set), freedom_date, coast_fire_date. Family#retirement_spending_baseline anchors the default target on the median monthly expense (the precise trailing-12m 10%-trimmed mean + its label ship with PR4's "Why this target?" card). Tests: 28 — exact zero-return stepper checks (accumulation + depletion with shortfall), pension-covered no-drawdown, tax widening the drawdown, adjustment lowering the target, Coast extremes, infeasible warnings, plus tax/payout/cohort units and the model wiring. No new gems.