v0.3: Required Savings solver, Fixed-Pct strategy, Black Swan + Withdrawal event fields on TimelineRow#2
Merged
fall-development-rob merged 1 commit intoApr 16, 2026
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements
CONTRACT-016(engine solver/strategy contract) plus the four supporting ADRs from the shore-crest-education foundation. Four headline changes:findRequiredSavingsreverse solver (ADR-025) — bisectscontrib_amountover[0, upperBound](default100_000), capped at 24 iterations and 300 internal MC runs, returning theSolverResultshape from DDD-009. MirrorsfindEarliestRetirementAge's call shape; the optimizer's public signature is unchanged.Fixed-Pctwithdrawal strategy (ADR-026) — addscalculateFixedPctWithdrawal({ fixed_withdrawal_pct, priorEndBalance, availableBalance? })plus a'Fixed-Pct'case on the dispatcher. TheScenario.withdrawal_strategyenum is widened additively (legacy scenarios still validate);fixed_withdrawal_pctdefaults to4inDEFAULT_SCENARIO.TimelineRowpopulated withblack_swan_lossandwithdrawal_event(ADR-026 / ADR-027) — every row in bothrunProjectionandrunAdvancedProjectionnow carries:black_swan_loss: number(nominal-$ loss in the shock year,0everywhere else; advanced mode aggregates per-item losses across liquid investments only, per ADR-027 §2)withdrawal_event: 'standard' | 'cut' | 'raise' | 'band'(cut/raiseonly when GK guardrails fire;bandonly when an Age-Banded phase matched;standardotherwise — including Fixed-Pct, per CONTRACT-016)Sensitivity & Backtest force-disable Black Swan (ADR-027 §"Sensitivity ignores the shock" / §"Backtest ignores the shock") —
runSensitivityAnalysisandrunHistoricalBacktestclone the scenario and setblack_swan_enabled: falsebefore 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 existinggk_*engine internals rather than renaming. New names are accepted as optionalScenariofields and consulted first; legacygk_*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 buildcleannpm run lintcleannode src/__tests__/smoke.test.mjs— 22/22 assertions pass, covering:calculateFixedPctWithdrawalreturns 4% of prior balance with default params (incl. zero, cap, and negative-clamp edge cases)runProjectionpopulatesblack_swan_losscorrectly on the shock year and zero elsewhere; every row has both new fieldsrunProjectionwith therunSensitivityAnalysiswrapper never receivesblack_swan_enabled: trueand no projection produces a shock rowfindRequiredSavingsreturns a feasible answer for a moderate scenario, respects the 24-iteration cap, and returns{ feasible: false, reason: 'plan_never_succeeds' }when no solution existsFixed-Pctstrategy runs end-to-end throughrunProjectionwithwithdrawal_event === 'standard'per CONTRACT-016dist/via the existing symlink atapps/calculator/node_modules/@robotixai/calculator-engine— verified locally; downstream UI agents (2b/2c/2d) will integrate.References
docs/contracts/CONTRACT-016-engine-solver-strategy.mddocs/adrs/ADR-025-reverse-solvers.mddocs/adrs/ADR-026-withdrawal-strategies.mddocs/adrs/ADR-027-black-swan-stress-event.md