Skip to content

v0.3: Required Savings solver, Fixed-Pct strategy, Black Swan + Withdrawal event fields on TimelineRow#2

Merged
fall-development-rob merged 1 commit into
mainfrom
feat/v0.3-comparison-solver-stress-additions
Apr 16, 2026
Merged

v0.3: Required Savings solver, Fixed-Pct strategy, Black Swan + Withdrawal event fields on TimelineRow#2
fall-development-rob merged 1 commit into
mainfrom
feat/v0.3-comparison-solver-stress-additions

Conversation

@fall-development-rob
Copy link
Copy Markdown
Contributor

Summary

Implements CONTRACT-016 (engine solver/strategy contract) plus the four supporting ADRs from the shore-crest-education foundation. Four headline changes:

  1. findRequiredSavings reverse solver (ADR-025) — bisects contrib_amount over [0, upperBound] (default 100_000), capped at 24 iterations and 300 internal MC runs, returning the SolverResult shape from DDD-009. Mirrors findEarliestRetirementAge's call shape; the optimizer's public signature is unchanged.

  2. Fixed-Pct withdrawal strategy (ADR-026) — adds calculateFixedPctWithdrawal({ fixed_withdrawal_pct, priorEndBalance, availableBalance? }) plus a 'Fixed-Pct' case on the dispatcher. The Scenario.withdrawal_strategy enum is widened additively (legacy scenarios still validate); fixed_withdrawal_pct defaults to 4 in DEFAULT_SCENARIO.

  3. TimelineRow populated with black_swan_loss and withdrawal_event (ADR-026 / ADR-027) — every row in both runProjection and runAdvancedProjection now carries:

    • black_swan_loss: number (nominal-$ loss in the shock year, 0 everywhere else; advanced mode aggregates per-item losses across liquid investments only, per ADR-027 §2)
    • withdrawal_event: 'standard' | 'cut' | 'raise' | 'band' (cut/raise only when GK guardrails fire; band only when an Age-Banded phase matched; standard otherwise — including Fixed-Pct, per CONTRACT-016)
  4. Sensitivity & Backtest force-disable Black Swan (ADR-027 §"Sensitivity ignores the shock" / §"Backtest ignores the shock") — runSensitivityAnalysis and runHistoricalBacktest clone the scenario and set black_swan_enabled: false before each per-band / per-window projection.

Schema mapping

CONTRACT-016 introduces new Guyton-Klinger field names (guyton_guard_up_pct, guyton_guard_down_pct, guyton_cut_pct, guyton_raise_pct, guyton_max_cut_per_year_pct). Per the engine-side constraint, the dispatcher maps the new names onto the existing gk_* engine internals rather than renaming. New names are accepted as optional Scenario fields and consulted first; legacy gk_* fields remain authoritative when the new names are absent.

Version

Bumped 0.2.0 -> 0.3.0. Not published to npm — this PR only pushes the branch.

Test plan

  • npm run build clean
  • npm run lint clean
  • node src/__tests__/smoke.test.mjs22/22 assertions pass, covering:
    • calculateFixedPctWithdrawal returns 4% of prior balance with default params (incl. zero, cap, and negative-clamp edge cases)
    • runProjection populates black_swan_loss correctly on the shock year and zero elsewhere; every row has both new fields
    • runProjection with the runSensitivityAnalysis wrapper never receives black_swan_enabled: true and no projection produces a shock row
    • findRequiredSavings returns a feasible answer for a moderate scenario, respects the 24-iteration cap, and returns { feasible: false, reason: 'plan_never_succeeds' } when no solution exists
    • Fixed-Pct strategy runs end-to-end through runProjection with withdrawal_event === 'standard' per CONTRACT-016
  • Calculator app picks up the rebuilt dist/ via the existing symlink at apps/calculator/node_modules/@robotixai/calculator-engine — verified locally; downstream UI agents (2b/2c/2d) will integrate.

References

  • shore-crest-education docs/contracts/CONTRACT-016-engine-solver-strategy.md
  • shore-crest-education docs/adrs/ADR-025-reverse-solvers.md
  • shore-crest-education docs/adrs/ADR-026-withdrawal-strategies.md
  • shore-crest-education docs/adrs/ADR-027-black-swan-stress-event.md

…ow event/loss fields

Implements CONTRACT-016 (engine solver/strategy contract) and the supporting
ADRs from the shore-crest-education foundation:

- ADR-025: new findRequiredSavings reverse solver (bisection over
  contrib_amount, capped at 24 iterations and 300 internal MC runs;
  returns the SolverResult shape from DDD-009).
- ADR-026: new 'Fixed-Pct' withdrawal strategy plus calculateFixedPctWithdrawal
  primitive. The Scenario.withdrawal_strategy enum is widened additively;
  legacy scenarios continue to validate. CONTRACT-016 Guyton-Klinger field
  names (guyton_guard_up_pct etc.) are accepted and mapped onto the existing
  gk_* engine internals via the dispatcher with no rename.
- ADR-026 / ADR-027: TimelineRow now carries black_swan_loss (nominal $ loss
  in the shock year, zero otherwise) and withdrawal_event ('standard' | 'cut'
  | 'raise' | 'band'). The GK helper signals which guardrail fired; the
  age-banded helper signals when a band matched.
- ADR-027: runSensitivityAnalysis and runHistoricalBacktest now force-disable
  black_swan_enabled on per-call scenario clones so stress events are
  excluded from sensitivity bands and historical windows.

Also bumps version to 0.3.0 and adds src/__tests__/smoke.test.mjs (22 assertions
covering the new behaviour), runnable directly under Node via the included
extension-rewriting loader.

Build (tsc) and lint (tsc --noEmit) both clean.
@fall-development-rob fall-development-rob merged commit f40aade into main Apr 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant