@retiregolden/planner-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +181 -0
- package/package.json +77 -0
- package/src/App.tsx +246 -0
- package/src/RouteErrorBoundary.tsx +45 -0
- package/src/assets/hero.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/assets/vite.svg +1 -0
- package/src/data/fedInvestClient.ts +113 -0
- package/src/data/localStore.ts +42 -0
- package/src/data/planOrigin.ts +24 -0
- package/src/data/planStore.ts +165 -0
- package/src/data/v2Backup.ts +101 -0
- package/src/import/ImportPage.tsx +347 -0
- package/src/import/ReviewChecklistView.tsx +38 -0
- package/src/import/brokerCsv.ts +395 -0
- package/src/import/csv.ts +133 -0
- package/src/import/genericCsv.ts +224 -0
- package/src/import/projectionLab.ts +350 -0
- package/src/import/reviewChecklist.ts +33 -0
- package/src/import/tenForty.ts +275 -0
- package/src/index.css +630 -0
- package/src/index.ts +16 -0
- package/src/learn/ArticleBody.tsx +78 -0
- package/src/learn/ArticlePage.tsx +57 -0
- package/src/learn/GlossaryPage.tsx +33 -0
- package/src/learn/LearnAboutScreen.tsx +41 -0
- package/src/learn/LearnCards.tsx +41 -0
- package/src/learn/LearnLink.tsx +91 -0
- package/src/learn/LearningCenterPage.tsx +114 -0
- package/src/learn/SourcesPage.tsx +98 -0
- package/src/learn/components/ArticleFigure.tsx +34 -0
- package/src/learn/components/ArticleShell.tsx +86 -0
- package/src/learn/components/ComparisonTable.tsx +42 -0
- package/src/learn/components/FormulaBlock.tsx +34 -0
- package/src/learn/components/PurchasingPowerChart.tsx +41 -0
- package/src/learn/components/RelatedArticles.tsx +27 -0
- package/src/learn/components/ScenarioCard.tsx +24 -0
- package/src/learn/components/SourceList.tsx +23 -0
- package/src/learn/components/charts.tsx +21 -0
- package/src/learn/content/about-retiregolden.ts +100 -0
- package/src/learn/content/aca-premium-tax-credits-and-magi.ts +103 -0
- package/src/learn/content/account-types-overview.ts +106 -0
- package/src/learn/content/after-tax-estate.ts +111 -0
- package/src/learn/content/agi-magi-and-taxable-income.ts +112 -0
- package/src/learn/content/appealing-irmaa-ssa-44.ts +95 -0
- package/src/learn/content/assumption-general-inflation.ts +82 -0
- package/src/learn/content/assumption-healthcare-inflation.ts +85 -0
- package/src/learn/content/assumption-heir-tax-rate.ts +79 -0
- package/src/learn/content/assumption-investment-returns.ts +90 -0
- package/src/learn/content/assumption-longevity-planning-age.ts +78 -0
- package/src/learn/content/assumption-recent-magi.ts +83 -0
- package/src/learn/content/assumption-social-security-cola.ts +89 -0
- package/src/learn/content/assumption-social-security-trust-fund.ts +83 -0
- package/src/learn/content/assumption-state-tax-override.ts +79 -0
- package/src/learn/content/beneficiaries-and-account-titling.ts +99 -0
- package/src/learn/content/break-even-useful-lens.ts +94 -0
- package/src/learn/content/building-a-retirement-spending-budget.ts +100 -0
- package/src/learn/content/cola-and-inflation-protection.ts +102 -0
- package/src/learn/content/divorced-spousal-and-survivor-records.ts +104 -0
- package/src/learn/content/dynamic-spending-guardrails.ts +90 -0
- package/src/learn/content/earnings-test-before-fra.ts +100 -0
- package/src/learn/content/employer-match-and-contribution-order.ts +104 -0
- package/src/learn/content/examplePlanArticles.ts +525 -0
- package/src/learn/content/fees-expense-ratios-and-compounding-drag.ts +98 -0
- package/src/learn/content/fi-number-and-four-percent-rule.ts +64 -0
- package/src/learn/content/filling-a-tax-bracket-with-roth-conversions.ts +98 -0
- package/src/learn/content/funded-ratio.ts +70 -0
- package/src/learn/content/healthcare-after-65.ts +103 -0
- package/src/learn/content/healthcare-before-65.ts +104 -0
- package/src/learn/content/historical-vs-random-return-models.ts +101 -0
- package/src/learn/content/how-assumptions-change-the-answer.ts +105 -0
- package/src/learn/content/how-much-can-i-spend.ts +105 -0
- package/src/learn/content/how-social-security-is-taxed.ts +95 -0
- package/src/learn/content/how-the-optimizer-thinks.ts +102 -0
- package/src/learn/content/how-the-optimizer-values-after-tax-estate.ts +97 -0
- package/src/learn/content/how-to-model-accumulation.ts +67 -0
- package/src/learn/content/how-to-read-a-retirement-projection.ts +115 -0
- package/src/learn/content/hsas-and-qualified-medical-expenses.ts +108 -0
- package/src/learn/content/hsas-as-retirement-accounts.ts +101 -0
- package/src/learn/content/inflation-risk.ts +98 -0
- package/src/learn/content/inherited-ira-10-year-rule.ts +105 -0
- package/src/learn/content/insurance-in-your-retirement-plan.ts +103 -0
- package/src/learn/content/irmaa-two-year-lookback.ts +99 -0
- package/src/learn/content/long-term-care-costs-and-insurance.ts +103 -0
- package/src/learn/content/long-term-care-insurance-as-risk-transfer.ts +98 -0
- package/src/learn/content/longevity-risk.ts +99 -0
- package/src/learn/content/marginal-vs-effective-tax-rate.ts +98 -0
- package/src/learn/content/medicare-part-b-vs-part-d-irmaa.ts +102 -0
- package/src/learn/content/mortality-weighted-social-security.ts +113 -0
- package/src/learn/content/moving-to-retiregolden.ts +86 -0
- package/src/learn/content/niit-high-income-investment-tax.ts +98 -0
- package/src/learn/content/ordinary-income-vs-capital-gains.ts +103 -0
- package/src/learn/content/paying-conversion-taxes-taxable-vs-ira.ts +102 -0
- package/src/learn/content/pensions-and-annuities.ts +101 -0
- package/src/learn/content/permanent-life-insurance-in-a-plan.ts +106 -0
- package/src/learn/content/pia-aime-and-bend-points.ts +103 -0
- package/src/learn/content/planner-overview.ts +106 -0
- package/src/learn/content/planning-for-couples-and-survivor-years.ts +108 -0
- package/src/learn/content/privacy-what-stays-in-your-browser.ts +99 -0
- package/src/learn/content/qcds-qualified-charitable-distributions.ts +101 -0
- package/src/learn/content/reading-the-results-page.ts +96 -0
- package/src/learn/content/reading-the-social-security-analysis-page.ts +106 -0
- package/src/learn/content/real-estate-home-equity-and-debt.ts +100 -0
- package/src/learn/content/reports-csv-exports-and-sharing.ts +101 -0
- package/src/learn/content/risk-based-guardrails.ts +100 -0
- package/src/learn/content/rmds-required-minimum-distributions.ts +100 -0
- package/src/learn/content/roth-conversion-basics.ts +104 -0
- package/src/learn/content/rsus-and-espp.ts +101 -0
- package/src/learn/content/rule-of-55-and-72t.ts +107 -0
- package/src/learn/content/savings-rate-biggest-lever.ts +66 -0
- package/src/learn/content/seed-your-plan-from-your-tax-return.ts +93 -0
- package/src/learn/content/sensitivity-testing-what-changes-the-answer.ts +104 -0
- package/src/learn/content/sequence-of-returns-risk.ts +98 -0
- package/src/learn/content/social-security-bridge.ts +67 -0
- package/src/learn/content/social-security-claiming-age-basics.ts +113 -0
- package/src/learn/content/social-security-taxes-vs-benefits.ts +76 -0
- package/src/learn/content/spending-profiles-and-the-retirement-smile.ts +92 -0
- package/src/learn/content/spousal-and-survivor-benefits.ts +120 -0
- package/src/learn/content/ssdi-and-retirement-planning.ts +72 -0
- package/src/learn/content/standard-deduction-senior-deduction-and-itemizing.ts +97 -0
- package/src/learn/content/state-income-taxes-in-retirement.ts +97 -0
- package/src/learn/content/step-up-in-basis.ts +102 -0
- package/src/learn/content/survivor-planning-for-couples.ts +110 -0
- package/src/learn/content/survivor-spending-in-couple-plans.ts +98 -0
- package/src/learn/content/tax-cliffs-and-bracket-edges.ts +105 -0
- package/src/learn/content/tax-loss-and-gain-harvesting.ts +99 -0
- package/src/learn/content/taxable-brokerage-basis-and-capital-gains.ts +99 -0
- package/src/learn/content/three-big-questions-spending-time-risk.ts +103 -0
- package/src/learn/content/tips-ladders.ts +92 -0
- package/src/learn/content/todays-dollars-vs-future-dollars.ts +107 -0
- package/src/learn/content/traditional-vs-roth-contributions.ts +113 -0
- package/src/learn/content/troubleshooting-surprising-results.ts +105 -0
- package/src/learn/content/trust-fund-haircut-scenarios.ts +101 -0
- package/src/learn/content/understanding-monte-carlo-success-rate.ts +118 -0
- package/src/learn/content/understanding-your-plan-assumptions.ts +134 -0
- package/src/learn/content/using-assumptions-and-provenance.ts +98 -0
- package/src/learn/content/using-scenarios-to-compare-choices.ts +99 -0
- package/src/learn/content/what-changes-when-you-move-states.ts +141 -0
- package/src/learn/content/what-is-fire.ts +65 -0
- package/src/learn/content/what-monte-carlo-proves.ts +98 -0
- package/src/learn/content/what-retiregolden-models.ts +103 -0
- package/src/learn/content/what-retirement-healthcare-really-costs.ts +117 -0
- package/src/learn/content/why-95-percent-is-not-a-guarantee.ts +98 -0
- package/src/learn/content/why-roth-conversions-raise-other-costs.ts +106 -0
- package/src/learn/content/why-small-tax-cliffs-can-matter.ts +109 -0
- package/src/learn/content/widows-penalty-and-survivor-brackets.ts +106 -0
- package/src/learn/content/withdrawal-order-basics.ts +105 -0
- package/src/learn/glossary.ts +191 -0
- package/src/learn/inlineMarkdown.tsx +54 -0
- package/src/learn/learn.css +537 -0
- package/src/learn/learningRegistry.ts +502 -0
- package/src/longevity/LongevityResults.tsx +85 -0
- package/src/longevity/LongevityWizard.tsx +305 -0
- package/src/longevity/constants.ts +15 -0
- package/src/longevity/factors.ts +125 -0
- package/src/longevity/model.ts +31 -0
- package/src/longevity/persistedGuard.ts +129 -0
- package/src/longevity/storage.ts +40 -0
- package/src/mc/messages.ts +118 -0
- package/src/mc/monteCarlo.worker.ts +44 -0
- package/src/mc/pool.ts +267 -0
- package/src/mc/runRequest.ts +125 -0
- package/src/optimize/messages.ts +84 -0
- package/src/optimize/optimize.worker.ts +29 -0
- package/src/optimize/runOptimize.ts +92 -0
- package/src/optimize/runSpendingSolve.ts +47 -0
- package/src/optimize/runner.ts +21 -0
- package/src/optimize/spendingMessages.ts +44 -0
- package/src/optimize/spendingRunner.ts +21 -0
- package/src/optimize/spendingSolve.worker.ts +18 -0
- package/src/planner/AssumptionsCardPage.tsx +136 -0
- package/src/planner/BucketLensCard.tsx +114 -0
- package/src/planner/ComparePlansPage.tsx +219 -0
- package/src/planner/DisclaimerPage.tsx +88 -0
- package/src/planner/HowTestedPage.tsx +159 -0
- package/src/planner/LiveStatus.tsx +15 -0
- package/src/planner/LongevityModal.tsx +55 -0
- package/src/planner/Modal.tsx +97 -0
- package/src/planner/MonteCarloPage.tsx +907 -0
- package/src/planner/OptimizePage.tsx +611 -0
- package/src/planner/PlanContext.tsx +198 -0
- package/src/planner/PlanPickerPage.tsx +124 -0
- package/src/planner/PlanWorkspace.tsx +290 -0
- package/src/planner/ProvenancePanel.tsx +45 -0
- package/src/planner/RelocationComparePage.tsx +485 -0
- package/src/planner/ReportPage.tsx +375 -0
- package/src/planner/ResultsPage.tsx +817 -0
- package/src/planner/ScenariosPage.tsx +285 -0
- package/src/planner/SocialSecuritySection.tsx +556 -0
- package/src/planner/SpendingSolverPage.tsx +512 -0
- package/src/planner/SsAnalysisPage.tsx +1134 -0
- package/src/planner/SurvivalPercentileModal.tsx +161 -0
- package/src/planner/SurvivorTransitionPage.tsx +286 -0
- package/src/planner/assumptionsExport.ts +371 -0
- package/src/planner/bucketLens.ts +89 -0
- package/src/planner/chartFrame.ts +8 -0
- package/src/planner/chartStyle.ts +11 -0
- package/src/planner/dialogViews.tsx +184 -0
- package/src/planner/dialogs.tsx +133 -0
- package/src/planner/examples/ExampleLibrary.tsx +189 -0
- package/src/planner/examples/ExamplePreviewBanner.tsx +55 -0
- package/src/planner/examples/ExamplesPage.tsx +25 -0
- package/src/planner/examples/OpenExampleButton.tsx +61 -0
- package/src/planner/examples/buildAggressiveSaver.ts +102 -0
- package/src/planner/examples/buildAnnuityEstate.ts +137 -0
- package/src/planner/examples/buildBaristaFire.ts +115 -0
- package/src/planner/examples/buildBracketFillRoth.ts +65 -0
- package/src/planner/examples/buildBridgeEarlyRetirement.ts +94 -0
- package/src/planner/examples/buildBrokerageNoHsa.ts +109 -0
- package/src/planner/examples/buildCoastFire.ts +88 -0
- package/src/planner/examples/buildContext.ts +20 -0
- package/src/planner/examples/buildEarlyCareerMatch.ts +93 -0
- package/src/planner/examples/buildEarlyRetireeAca.ts +61 -0
- package/src/planner/examples/buildExampleCouple.ts +103 -0
- package/src/planner/examples/buildFixedTargetSpending.ts +74 -0
- package/src/planner/examples/buildGlidepathAllocation.ts +131 -0
- package/src/planner/examples/buildGuardrailsFlex.ts +120 -0
- package/src/planner/examples/buildHsaPropertyDepth.ts +109 -0
- package/src/planner/examples/buildHsaStealthRetirement.ts +97 -0
- package/src/planner/examples/buildLeanFatFire.ts +109 -0
- package/src/planner/examples/buildLtcShock.ts +62 -0
- package/src/planner/examples/buildMovingStateTax.ts +53 -0
- package/src/planner/examples/buildNoAnnuityBrokerage.ts +92 -0
- package/src/planner/examples/buildRmdIrmaa.ts +55 -0
- package/src/planner/examples/buildSalaryGrowthEscalation.ts +96 -0
- package/src/planner/examples/buildStaticAllocationControl.ts +96 -0
- package/src/planner/examples/buildSurvivorYears.ts +62 -0
- package/src/planner/examples/buildUnderSavedSingle.ts +51 -0
- package/src/planner/examples/exampleCopy.ts +23 -0
- package/src/planner/examples/loadExample.ts +90 -0
- package/src/planner/examples/registry.ts +313 -0
- package/src/planner/explainPanels.tsx +233 -0
- package/src/planner/fields.tsx +381 -0
- package/src/planner/format.ts +33 -0
- package/src/planner/home/DataAndPrivacyCard.tsx +56 -0
- package/src/planner/home/GettingStartedPaths.tsx +46 -0
- package/src/planner/home/GettingStartedReopener.tsx +32 -0
- package/src/planner/home/StartHereLinks.tsx +22 -0
- package/src/planner/home/WelcomeHero.tsx +39 -0
- package/src/planner/home/YourPlans.tsx +72 -0
- package/src/planner/home/importErrorMessage.ts +22 -0
- package/src/planner/home/startHereSlugs.ts +7 -0
- package/src/planner/home/useHomeData.ts +190 -0
- package/src/planner/home/useHomeMode.ts +47 -0
- package/src/planner/householdActions.ts +22 -0
- package/src/planner/insights/InsightCardView.tsx +340 -0
- package/src/planner/insights/InsightsPage.tsx +204 -0
- package/src/planner/insights/categoryLabels.ts +11 -0
- package/src/planner/learnLinks.ts +85 -0
- package/src/planner/marketModelPicker.ts +172 -0
- package/src/planner/optimizePageChart.ts +40 -0
- package/src/planner/optimizePageClaim.ts +64 -0
- package/src/planner/planCompleteness.ts +27 -0
- package/src/planner/planContextCore.ts +26 -0
- package/src/planner/planner.css +2304 -0
- package/src/planner/provenanceLinks.ts +25 -0
- package/src/planner/sections/AccountFields.tsx +872 -0
- package/src/planner/sections/AccountsSection.tsx +89 -0
- package/src/planner/sections/AllocationPanel.tsx +261 -0
- package/src/planner/sections/AssumptionsSection.tsx +256 -0
- package/src/planner/sections/HouseholdSection.tsx +243 -0
- package/src/planner/sections/IncomeFloorSection.tsx +418 -0
- package/src/planner/sections/IncomeSection.tsx +170 -0
- package/src/planner/sections/InsuranceSection.tsx +362 -0
- package/src/planner/sections/SpendingSection.tsx +904 -0
- package/src/planner/sections/StrategySection.tsx +349 -0
- package/src/planner/sections/UpdateBalancesPanel.tsx +182 -0
- package/src/planner/sections/sectionHelpers.ts +48 -0
- package/src/planner/sections/shared.tsx +15 -0
- package/src/planner/sections.tsx +15 -0
- package/src/planner/ssAnalysis.ts +325 -0
- package/src/planner/successBand.ts +20 -0
- package/src/planner/survivorAnalysis.ts +277 -0
- package/src/planner/usStates.ts +19 -0
- package/src/planner/useMcSuccessRate.ts +77 -0
- package/src/planner/useProjection.ts +63 -0
- package/src/relocation/messages.ts +21 -0
- package/src/relocation/relocation.worker.ts +18 -0
- package/src/relocation/runRelocation.ts +17 -0
- package/src/relocation/runner.ts +22 -0
- package/src/report/brandingContext.ts +15 -0
- package/src/report/downloadReport.ts +34 -0
- package/src/report/reportHtml.ts +547 -0
- package/src/routes/LearnRoutes.tsx +46 -0
- package/src/routes/PlanRoutes.tsx +55 -0
- package/src/routes/RouteFallback.tsx +9 -0
- package/src/socialSecurity/breakEven.ts +107 -0
- package/src/socialSecurity/expectedPv.ts +164 -0
- package/src/socialSecurity/explain.ts +92 -0
- package/src/socialSecurity/ficaReturn.ts +81 -0
- package/src/socialSecurity/persistedSsGuard.ts +138 -0
- package/src/socialSecurity/ssFormUtils.ts +48 -0
- package/src/socialSecurity/ssaStatementXml.ts +156 -0
- package/src/socialSecurity/storage.ts +69 -0
- package/src/socialSecurity/survivorSwitching.ts +153 -0
- package/src/testSupport/samplePlan.ts +2 -0
- package/src/workers/run.ts +45 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Static / no-glidepath allocation control (A-B pair).
|
|
2
|
+
* Exact same starting balances and household as glidepath-allocation,
|
|
3
|
+
* but NO allocation policies on any account (falls back to single defaultReturnPct).
|
|
4
|
+
* Same starting dollar amounts. Compare the two in Plan Compare to see
|
|
5
|
+
* the effect of glidepath + rebalancing + class MC on risk metrics and ending values.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createEmptyPlan, parsePlan, type Plan } from '@retiregolden/engine/model/plan'
|
|
9
|
+
import { EXAMPLE_FIXED_YEAR, exampleEntityId, exampleFixedNow, exampleIdFactory } from './buildContext'
|
|
10
|
+
|
|
11
|
+
const EXAMPLE_ID = 'static-allocation-control'
|
|
12
|
+
|
|
13
|
+
export function buildStaticAllocationControl(): Plan {
|
|
14
|
+
const p1 = exampleEntityId(EXAMPLE_ID, 'p1')
|
|
15
|
+
const plan = createEmptyPlan({ name: 'Static allocation control', now: exampleFixedNow, newId: exampleIdFactory(EXAMPLE_ID) })
|
|
16
|
+
|
|
17
|
+
plan.household = {
|
|
18
|
+
filingStatus: 'single',
|
|
19
|
+
hasQualifyingDependent: false,
|
|
20
|
+
state: 'CA',
|
|
21
|
+
stateMoves: [],
|
|
22
|
+
capitalLossCarryforward: 12000,
|
|
23
|
+
people: [
|
|
24
|
+
{ id: p1, name: 'Morgan', dob: '1962-09-05', sex: 'male', retirementAge: 63, longevity: { planningAge: 91, source: 'manual' } },
|
|
25
|
+
],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Identical starting balances, NO allocation objects
|
|
29
|
+
plan.accounts = [
|
|
30
|
+
{
|
|
31
|
+
type: 'taxable',
|
|
32
|
+
id: exampleEntityId(EXAMPLE_ID, 'broker'),
|
|
33
|
+
name: 'Taxable brokerage',
|
|
34
|
+
ownerPersonId: null,
|
|
35
|
+
annualReturnPct: 5.0, // explicit single return (matches what glide would average-ish)
|
|
36
|
+
balance: 380_000,
|
|
37
|
+
costBasis: 195_000,
|
|
38
|
+
annualContribution: 0,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: 'traditional',
|
|
42
|
+
id: exampleEntityId(EXAMPLE_ID, 't401k'),
|
|
43
|
+
name: 'Traditional 401(k)',
|
|
44
|
+
ownerPersonId: p1,
|
|
45
|
+
annualReturnPct: 5.0,
|
|
46
|
+
kind: 'employer',
|
|
47
|
+
balance: 920_000,
|
|
48
|
+
annualContribution: 0,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'roth',
|
|
52
|
+
id: exampleEntityId(EXAMPLE_ID, 'roth'),
|
|
53
|
+
name: 'Roth IRA',
|
|
54
|
+
ownerPersonId: p1,
|
|
55
|
+
annualReturnPct: 5.0,
|
|
56
|
+
kind: 'ira',
|
|
57
|
+
balance: 165_000,
|
|
58
|
+
annualContribution: 0,
|
|
59
|
+
},
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
plan.expenses = {
|
|
63
|
+
baseAnnual: 62_000,
|
|
64
|
+
phases: [],
|
|
65
|
+
oneTimeGoals: [],
|
|
66
|
+
healthcare: { pre65MonthlyPremiumPerPerson: 810, applyAcaCredit: true, medicareExtrasMonthlyPerPerson: 175 },
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
plan.incomes = [
|
|
70
|
+
{ type: 'socialSecurity', id: exampleEntityId(EXAMPLE_ID, 'ss'), personId: p1, piaMonthly: 2100, earnings: null, claimAge: { years: 70, months: 0 } },
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
plan.strategies = {
|
|
74
|
+
withdrawalOrder: { mode: 'sequential' },
|
|
75
|
+
rothConversion: { mode: 'fillToTarget', target: 'topOfBracket', targetValue: 22, startYear: EXAMPLE_FIXED_YEAR + 1, endYear: EXAMPLE_FIXED_YEAR + 9 },
|
|
76
|
+
qcdAnnual: 3000,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
plan.assumptions = {
|
|
80
|
+
inflationPct: 2.5,
|
|
81
|
+
healthcareExtraInflationPct: 3.1,
|
|
82
|
+
defaultReturnPct: 5.0,
|
|
83
|
+
ssCola: { mode: 'matchInflation' },
|
|
84
|
+
ssHaircut: null,
|
|
85
|
+
stateEffectiveTaxPct: 0,
|
|
86
|
+
localIncomeTaxPct: 0,
|
|
87
|
+
recentAnnualMagi: 95000,
|
|
88
|
+
heirTaxRatePct: 26,
|
|
89
|
+
safeWithdrawalRatePct: 3.7,
|
|
90
|
+
// No special assetClassParams needed for the control
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const parsed = parsePlan(plan)
|
|
94
|
+
if (!parsed.ok) throw new Error(`static-allocation-control invalid: ${parsed.issues.join('; ')}`)
|
|
95
|
+
return parsed.plan
|
|
96
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Survivor years (widow's penalty) — first death flips to single brackets and survivor SS. */
|
|
2
|
+
|
|
3
|
+
import { createEmptyPlan, parsePlan, type Plan } from '@retiregolden/engine/model/plan'
|
|
4
|
+
import { exampleEntityId, exampleFixedNow, exampleIdFactory } from './buildContext'
|
|
5
|
+
|
|
6
|
+
const EXAMPLE_ID = 'survivor-years'
|
|
7
|
+
|
|
8
|
+
export function buildSurvivorYears(): Plan {
|
|
9
|
+
const p1 = exampleEntityId(EXAMPLE_ID, 'p1')
|
|
10
|
+
const p2 = exampleEntityId(EXAMPLE_ID, 'p2')
|
|
11
|
+
const plan = createEmptyPlan({ name: 'Survivor years (widow\'s penalty)', now: exampleFixedNow, newId: exampleIdFactory(EXAMPLE_ID) })
|
|
12
|
+
plan.household = {
|
|
13
|
+
filingStatus: 'marriedFilingJointly',
|
|
14
|
+
hasQualifyingDependent: false,
|
|
15
|
+
state: 'FL',
|
|
16
|
+
stateMoves: [],
|
|
17
|
+
capitalLossCarryforward: 0,
|
|
18
|
+
people: [
|
|
19
|
+
{ id: p1, name: 'Lee', dob: '1962-06-15', sex: 'female', retirementAge: 65, longevity: { planningAge: 95, source: 'manual' } },
|
|
20
|
+
{ id: p2, name: 'Chris', dob: '1960-06-15', sex: 'male', retirementAge: 65, longevity: { planningAge: 78, source: 'manual' } },
|
|
21
|
+
],
|
|
22
|
+
}
|
|
23
|
+
plan.accounts = [
|
|
24
|
+
{ type: 'cash', id: exampleEntityId(EXAMPLE_ID, 'cash'), name: 'Cash', ownerPersonId: null, annualReturnPct: 2, balance: 100_000, annualContribution: 0 },
|
|
25
|
+
{
|
|
26
|
+
type: 'pension',
|
|
27
|
+
id: exampleEntityId(EXAMPLE_ID, 'pension'),
|
|
28
|
+
name: 'Chris pension',
|
|
29
|
+
ownerPersonId: p2,
|
|
30
|
+
annualReturnPct: 0,
|
|
31
|
+
startAge: 65,
|
|
32
|
+
monthlyAmount: 4_000,
|
|
33
|
+
colaPct: 0,
|
|
34
|
+
survivorPct: 50,
|
|
35
|
+
},
|
|
36
|
+
]
|
|
37
|
+
plan.incomes = [
|
|
38
|
+
{ type: 'socialSecurity', id: exampleEntityId(EXAMPLE_ID, 'ss-lee'), personId: p1, piaMonthly: 1_000, earnings: null, claimAge: { years: 67, months: 0 } },
|
|
39
|
+
{ type: 'socialSecurity', id: exampleEntityId(EXAMPLE_ID, 'ss-chris'), personId: p2, piaMonthly: 3_000, earnings: null, claimAge: { years: 67, months: 0 } },
|
|
40
|
+
]
|
|
41
|
+
plan.expenses = {
|
|
42
|
+
baseAnnual: 72_000,
|
|
43
|
+
phases: [{ fromAge: 80, multiplier: 0.9 }],
|
|
44
|
+
oneTimeGoals: [],
|
|
45
|
+
healthcare: { pre65MonthlyPremiumPerPerson: 0, applyAcaCredit: false, medicareExtrasMonthlyPerPerson: 200 },
|
|
46
|
+
}
|
|
47
|
+
plan.assumptions = {
|
|
48
|
+
inflationPct: 2.5,
|
|
49
|
+
healthcareExtraInflationPct: 3,
|
|
50
|
+
defaultReturnPct: 4.5,
|
|
51
|
+
ssCola: { mode: 'matchInflation' },
|
|
52
|
+
ssHaircut: null,
|
|
53
|
+
stateEffectiveTaxPct: 0,
|
|
54
|
+
localIncomeTaxPct: 0,
|
|
55
|
+
recentAnnualMagi: 0,
|
|
56
|
+
heirTaxRatePct: 22,
|
|
57
|
+
safeWithdrawalRatePct: 4,
|
|
58
|
+
}
|
|
59
|
+
const parsed = parsePlan(plan)
|
|
60
|
+
if (!parsed.ok) throw new Error(`survivor years invalid: ${parsed.issues.join('; ')}`)
|
|
61
|
+
return parsed.plan
|
|
62
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Under-saved single retiree — spending outpaces savings toward a depletion year. */
|
|
2
|
+
|
|
3
|
+
import { createEmptyPlan, parsePlan, type Plan } from '@retiregolden/engine/model/plan'
|
|
4
|
+
import { exampleEntityId, exampleFixedNow, exampleIdFactory } from './buildContext'
|
|
5
|
+
|
|
6
|
+
const EXAMPLE_ID = 'under-saved-single'
|
|
7
|
+
|
|
8
|
+
export function buildUnderSavedSingle(): Plan {
|
|
9
|
+
const p1 = exampleEntityId(EXAMPLE_ID, 'p1')
|
|
10
|
+
const plan = createEmptyPlan({ name: 'Under-saved single retiree', now: exampleFixedNow, newId: exampleIdFactory(EXAMPLE_ID) })
|
|
11
|
+
plan.household = {
|
|
12
|
+
filingStatus: 'single',
|
|
13
|
+
hasQualifyingDependent: false,
|
|
14
|
+
state: 'FL',
|
|
15
|
+
stateMoves: [],
|
|
16
|
+
capitalLossCarryforward: 0,
|
|
17
|
+
people: [
|
|
18
|
+
{ id: p1, name: 'Jordan', dob: '1962-01-01', sex: 'average', retirementAge: 62, longevity: { planningAge: 90, source: 'manual' } },
|
|
19
|
+
],
|
|
20
|
+
}
|
|
21
|
+
plan.accounts = [
|
|
22
|
+
{ type: 'cash', id: exampleEntityId(EXAMPLE_ID, 'cash'), name: 'Savings', ownerPersonId: null, annualReturnPct: 2, balance: 75_000, annualContribution: 0 },
|
|
23
|
+
{ type: 'taxable', id: exampleEntityId(EXAMPLE_ID, 'brokerage'), name: 'Brokerage', ownerPersonId: null, annualReturnPct: null, balance: 250_000, costBasis: 180_000, annualContribution: 0 },
|
|
24
|
+
{ type: 'traditional', id: exampleEntityId(EXAMPLE_ID, 'ira'), name: 'Traditional IRA', ownerPersonId: p1, annualReturnPct: null, kind: 'ira', balance: 300_000, annualContribution: 0 },
|
|
25
|
+
]
|
|
26
|
+
plan.incomes = [
|
|
27
|
+
{ type: 'recurring', id: exampleEntityId(EXAMPLE_ID, 'consulting'), label: 'Part-time consulting', annualAmount: 20_000, startYear: null, endYear: null, inflationAdjusted: true, taxTreatment: 'ordinary' },
|
|
28
|
+
{ type: 'socialSecurity', id: exampleEntityId(EXAMPLE_ID, 'ss'), personId: p1, piaMonthly: 2_500, earnings: null, claimAge: { years: 67, months: 0 } },
|
|
29
|
+
]
|
|
30
|
+
plan.expenses = {
|
|
31
|
+
baseAnnual: 72_000,
|
|
32
|
+
phases: [],
|
|
33
|
+
oneTimeGoals: [],
|
|
34
|
+
healthcare: { pre65MonthlyPremiumPerPerson: 650, applyAcaCredit: true, medicareExtrasMonthlyPerPerson: 200 },
|
|
35
|
+
}
|
|
36
|
+
plan.assumptions = {
|
|
37
|
+
inflationPct: 2.5,
|
|
38
|
+
healthcareExtraInflationPct: 3,
|
|
39
|
+
defaultReturnPct: 5,
|
|
40
|
+
ssCola: { mode: 'matchInflation' },
|
|
41
|
+
ssHaircut: null,
|
|
42
|
+
stateEffectiveTaxPct: 0,
|
|
43
|
+
localIncomeTaxPct: 0,
|
|
44
|
+
recentAnnualMagi: 35_000,
|
|
45
|
+
heirTaxRatePct: 22,
|
|
46
|
+
safeWithdrawalRatePct: 4,
|
|
47
|
+
}
|
|
48
|
+
const parsed = parsePlan(plan)
|
|
49
|
+
if (!parsed.ok) throw new Error(`under-saved single invalid: ${parsed.issues.join('; ')}`)
|
|
50
|
+
return parsed.plan
|
|
51
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One truthful persistence story for library examples, shared across the
|
|
3
|
+
* preview banner, the workspace save indicator, and the open-example dialog so
|
|
4
|
+
* they cannot drift apart (UI/UX round 2, Step 2).
|
|
5
|
+
*
|
|
6
|
+
* The behavior these describe: an opened example is saved on this device under
|
|
7
|
+
* a fixed `example:<id>` slot, so edits persist across reloads. It stays out of
|
|
8
|
+
* the "Your plans" list until "Save to my plans" promotes it to a real plan;
|
|
9
|
+
* "Load a fresh copy" resets the slot to the example's defaults.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Save indicator shown in the workspace header for `origin: 'example'` plans. */
|
|
13
|
+
export const EXAMPLE_SAVE_INDICATOR = 'Example — edits kept on this device until you reset'
|
|
14
|
+
|
|
15
|
+
/** Preview-banner sentence describing where an example's edits live. */
|
|
16
|
+
export const EXAMPLE_BANNER_PERSISTENCE =
|
|
17
|
+
'Your edits are kept on this device, but this example stays out of Your plans until you Save to my plans.'
|
|
18
|
+
|
|
19
|
+
/** "Open my version" choice: opening the previously edited example. */
|
|
20
|
+
export const EXAMPLE_OPEN_EXISTING_DESC = 'Keeps the edits you made last time, saved on this device.'
|
|
21
|
+
|
|
22
|
+
/** "Load a fresh copy" choice: resetting the example slot to defaults. */
|
|
23
|
+
export const EXAMPLE_LOAD_FRESH_DESC = 'Resets this example to its defaults, discarding edits kept on this device.'
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load, refresh, and convert library example demos in IndexedDB.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { exampleStorageId } from '../../data/planOrigin'
|
|
6
|
+
import { convertExampleToUserPlan, loadPlan, savePlan, type SavePlanResult } from '../../data/planStore'
|
|
7
|
+
import type { Plan } from '@retiregolden/engine/model/plan'
|
|
8
|
+
import { exampleFixedNow } from './buildContext'
|
|
9
|
+
import { getExampleById, type ExamplePlan } from './registry'
|
|
10
|
+
|
|
11
|
+
export function demoPlanId(exampleId: string): string {
|
|
12
|
+
return exampleStorageId(exampleId)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isDemoPlan(plan: Plan): boolean {
|
|
16
|
+
return plan.origin === 'example'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function stampDemo(example: ExamplePlan): Plan {
|
|
20
|
+
const built = example.build()
|
|
21
|
+
return {
|
|
22
|
+
...built,
|
|
23
|
+
id: demoPlanId(example.id),
|
|
24
|
+
name: example.title,
|
|
25
|
+
origin: 'example',
|
|
26
|
+
exampleSourceId: example.id,
|
|
27
|
+
createdAtIso: exampleFixedNow().toISOString(),
|
|
28
|
+
updatedAtIso: exampleFixedNow().toISOString(),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function demoRecordExists(exampleId: string): Promise<boolean> {
|
|
33
|
+
const loaded = await loadPlan(demoPlanId(exampleId))
|
|
34
|
+
return loaded.ok
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function loadDemoRecord(exampleId: string): Promise<Plan | null> {
|
|
38
|
+
const loaded = await loadPlan(demoPlanId(exampleId))
|
|
39
|
+
return loaded.ok ? loaded.plan : null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function saveFreshDemo(example: ExamplePlan): Promise<SavePlanResult> {
|
|
43
|
+
return savePlan(stampDemo(example), exampleFixedNow)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type OpenExampleChoice = 'open-existing' | 'load-fresh'
|
|
47
|
+
|
|
48
|
+
export interface OpenExampleResult {
|
|
49
|
+
ok: true
|
|
50
|
+
planId: string
|
|
51
|
+
/** When a record already existed and the caller should prompt. */
|
|
52
|
+
needsChoice?: true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function prepareExampleOpen(exampleId: string): Promise<OpenExampleResult | { ok: false; reason: string }> {
|
|
56
|
+
const example = getExampleById(exampleId)
|
|
57
|
+
if (!example) return { ok: false, reason: 'unknown example' }
|
|
58
|
+
|
|
59
|
+
const exists = await demoRecordExists(exampleId)
|
|
60
|
+
if (!exists) {
|
|
61
|
+
const saved = await saveFreshDemo(example)
|
|
62
|
+
if (!saved.ok) return { ok: false, reason: saved.issues.join('; ') }
|
|
63
|
+
return { ok: true, planId: saved.plan.id }
|
|
64
|
+
}
|
|
65
|
+
return { ok: true, planId: demoPlanId(exampleId), needsChoice: true }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function openExampleExisting(exampleId: string): Promise<{ ok: true; planId: string } | { ok: false; reason: string }> {
|
|
69
|
+
const loaded = await loadDemoRecord(exampleId)
|
|
70
|
+
if (!loaded) return { ok: false, reason: 'demo not found' }
|
|
71
|
+
return { ok: true, planId: loaded.id }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function openExampleFresh(exampleId: string): Promise<{ ok: true; planId: string } | { ok: false; reason: string }> {
|
|
75
|
+
const example = getExampleById(exampleId)
|
|
76
|
+
if (!example) return { ok: false, reason: 'unknown example' }
|
|
77
|
+
const saved = await saveFreshDemo(example)
|
|
78
|
+
if (!saved.ok) return { ok: false, reason: saved.issues.join('; ') }
|
|
79
|
+
return { ok: true, planId: saved.plan.id }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function saveExampleToMyPlans(
|
|
83
|
+
plan: Plan,
|
|
84
|
+
opts: { newId?: () => string } = {},
|
|
85
|
+
): Promise<SavePlanResult> {
|
|
86
|
+
if (plan.origin !== 'example') {
|
|
87
|
+
return { ok: false, issues: ['Only library examples can be converted.'] }
|
|
88
|
+
}
|
|
89
|
+
return convertExampleToUserPlan(plan, opts)
|
|
90
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated example plan registry — single source of truth for library demos.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Plan } from '@retiregolden/engine/model/plan'
|
|
6
|
+
import { buildBracketFillRoth } from './buildBracketFillRoth'
|
|
7
|
+
import { buildEarlyRetireeAca } from './buildEarlyRetireeAca'
|
|
8
|
+
import { buildExampleCouple } from './buildExampleCouple'
|
|
9
|
+
import { buildLtcShock } from './buildLtcShock'
|
|
10
|
+
import { buildMovingStateTax } from './buildMovingStateTax'
|
|
11
|
+
import { buildRmdIrmaa } from './buildRmdIrmaa'
|
|
12
|
+
import { buildSurvivorYears } from './buildSurvivorYears'
|
|
13
|
+
import { buildUnderSavedSingle } from './buildUnderSavedSingle'
|
|
14
|
+
import { buildEarlyCareerMatch } from './buildEarlyCareerMatch'
|
|
15
|
+
import { buildAggressiveSaver } from './buildAggressiveSaver'
|
|
16
|
+
import { buildCoastFire } from './buildCoastFire'
|
|
17
|
+
import { buildBaristaFire } from './buildBaristaFire'
|
|
18
|
+
import { buildBridgeEarlyRetirement } from './buildBridgeEarlyRetirement'
|
|
19
|
+
import { buildLeanFatFire } from './buildLeanFatFire'
|
|
20
|
+
import { buildHsaStealthRetirement } from './buildHsaStealthRetirement'
|
|
21
|
+
import { buildSalaryGrowthEscalation } from './buildSalaryGrowthEscalation'
|
|
22
|
+
import { buildGuardrailsFlex } from './buildGuardrailsFlex'
|
|
23
|
+
import { buildAnnuityEstate } from './buildAnnuityEstate'
|
|
24
|
+
import { buildGlidepathAllocation } from './buildGlidepathAllocation'
|
|
25
|
+
import { buildHsaPropertyDepth } from './buildHsaPropertyDepth'
|
|
26
|
+
import { buildFixedTargetSpending } from './buildFixedTargetSpending'
|
|
27
|
+
import { buildNoAnnuityBrokerage } from './buildNoAnnuityBrokerage'
|
|
28
|
+
import { buildStaticAllocationControl } from './buildStaticAllocationControl'
|
|
29
|
+
import { buildBrokerageNoHsa } from './buildBrokerageNoHsa'
|
|
30
|
+
|
|
31
|
+
export type ExampleTheme =
|
|
32
|
+
| 'overview'
|
|
33
|
+
| 'shortfall'
|
|
34
|
+
| 'roth-conversions'
|
|
35
|
+
| 'aca'
|
|
36
|
+
| 'rmd-irmaa'
|
|
37
|
+
| 'survivor'
|
|
38
|
+
| 'state-tax'
|
|
39
|
+
| 'ltc'
|
|
40
|
+
| 'fire'
|
|
41
|
+
| 'accumulation'
|
|
42
|
+
| 'coast-fire'
|
|
43
|
+
| 'barista-fire'
|
|
44
|
+
| 'guardrails'
|
|
45
|
+
| 'annuity-estate'
|
|
46
|
+
| 'allocation'
|
|
47
|
+
| 'hsa-depth'
|
|
48
|
+
|
|
49
|
+
export interface ExamplePlan {
|
|
50
|
+
id: string
|
|
51
|
+
title: string
|
|
52
|
+
summary: string
|
|
53
|
+
teaches: string
|
|
54
|
+
themeTags: ExampleTheme[]
|
|
55
|
+
learnSlug: string
|
|
56
|
+
/** Optional in-preview hint — phrased qualitatively to survive parameter updates. */
|
|
57
|
+
lookFor?: string
|
|
58
|
+
build: () => Plan
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const EXAMPLE_PLANS: ExamplePlan[] = [
|
|
62
|
+
{
|
|
63
|
+
id: 'example-couple',
|
|
64
|
+
title: 'Example couple',
|
|
65
|
+
summary: 'Married couple two years from retirement with diversified accounts.',
|
|
66
|
+
teaches: 'The full picture — accounts, Social Security, Roth strategy, insurance, and scenarios.',
|
|
67
|
+
themeTags: ['overview'],
|
|
68
|
+
learnSlug: 'example-couple',
|
|
69
|
+
lookFor: 'How Roth conversions, RMDs, and LTC care interact across a long horizon.',
|
|
70
|
+
build: buildExampleCouple,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'under-saved-single',
|
|
74
|
+
title: 'Under-saved single retiree',
|
|
75
|
+
summary: 'Single retiree with modest savings and steady spending.',
|
|
76
|
+
teaches: 'When spending outpaces savings — watch the depletion year and shortfall warnings.',
|
|
77
|
+
themeTags: ['shortfall'],
|
|
78
|
+
learnSlug: 'example-under-saved-single',
|
|
79
|
+
lookFor: 'The year investable assets run out and how Social Security changes the cash-flow picture.',
|
|
80
|
+
build: buildUnderSavedSingle,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'bracket-fill-roth',
|
|
84
|
+
title: 'Bracket-fill Roth conversions',
|
|
85
|
+
summary: 'Retired couple with large traditional balances and a fill-to-bracket strategy.',
|
|
86
|
+
teaches: 'Converting up to a bracket top — lifetime tax vs ending Roth balance.',
|
|
87
|
+
themeTags: ['roth-conversions'],
|
|
88
|
+
learnSlug: 'example-bracket-fill-roth',
|
|
89
|
+
lookFor: 'Annual conversion amounts, RMDs, and how QCDs offset taxable income.',
|
|
90
|
+
build: buildBracketFillRoth,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'early-retiree-aca',
|
|
94
|
+
title: 'Early retiree & the ACA cliff',
|
|
95
|
+
summary: 'Pre-65 retiree with consulting income and marketplace coverage.',
|
|
96
|
+
teaches: 'How MAGI affects ACA credits — and why a Roth conversion can cost more than the tax bill.',
|
|
97
|
+
themeTags: ['aca'],
|
|
98
|
+
learnSlug: 'example-early-retiree-aca',
|
|
99
|
+
lookFor: 'Healthcare premiums before Medicare and warnings when MAGI crosses the subsidy cliff.',
|
|
100
|
+
build: buildEarlyRetireeAca,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'rmd-irmaa',
|
|
104
|
+
title: 'High balances: RMDs & IRMAA',
|
|
105
|
+
summary: 'High-net-worth single retiree with a large IRA.',
|
|
106
|
+
teaches: 'Large traditional balances drive RMDs that can push MAGI into IRMAA tiers.',
|
|
107
|
+
themeTags: ['rmd-irmaa'],
|
|
108
|
+
learnSlug: 'example-rmd-irmaa',
|
|
109
|
+
lookFor: 'RMD amounts, QCD relief, and Medicare surcharge years in the results ledger.',
|
|
110
|
+
build: buildRmdIrmaa,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'survivor-years',
|
|
114
|
+
title: 'Survivor years (widow\'s penalty)',
|
|
115
|
+
summary: 'Couple with unequal Social Security and a pension with survivor benefits.',
|
|
116
|
+
teaches: 'After the first death, single tax brackets and survivor benefits can raise taxes.',
|
|
117
|
+
themeTags: ['survivor'],
|
|
118
|
+
learnSlug: 'example-survivor-years',
|
|
119
|
+
lookFor: 'The tax year when filing status effectively shifts and survivor Social Security steps up.',
|
|
120
|
+
build: buildSurvivorYears,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'moving-state-tax',
|
|
124
|
+
title: 'Moving in retirement (state tax)',
|
|
125
|
+
summary: 'Consultant relocating from Florida to Kentucky mid-plan.',
|
|
126
|
+
teaches: 'A state move changes lifetime tax even when federal income stays the same.',
|
|
127
|
+
themeTags: ['state-tax'],
|
|
128
|
+
learnSlug: 'example-moving-state-tax',
|
|
129
|
+
lookFor: 'Compare the base plan vs scenarios for state-tax differences after the move year.',
|
|
130
|
+
build: buildMovingStateTax,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'ltc-shock',
|
|
134
|
+
title: 'Long-term-care shock',
|
|
135
|
+
summary: 'Retiree with LTC insurance facing a multi-year care episode.',
|
|
136
|
+
teaches: 'How a care shock drains assets — and how insurance offsets the hit.',
|
|
137
|
+
themeTags: ['ltc'],
|
|
138
|
+
learnSlug: 'example-ltc-shock',
|
|
139
|
+
lookFor: 'Care-event years in Results and the difference insurance makes to ending net worth.',
|
|
140
|
+
build: buildLtcShock,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: 'early-career-match',
|
|
144
|
+
title: 'Just getting started',
|
|
145
|
+
summary: 'Young professional beginning their career with employer match capture and Roth IRA savings.',
|
|
146
|
+
teaches: 'Capturing employer match, compounding over time, and basic tax diversification.',
|
|
147
|
+
themeTags: ['fire', 'accumulation'],
|
|
148
|
+
learnSlug: 'example-early-career-match',
|
|
149
|
+
lookFor: 'Capture the employer 401(k) match, fund a Roth IRA, and watch wages grow.',
|
|
150
|
+
build: buildEarlyCareerMatch,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'aggressive-saver',
|
|
154
|
+
title: 'Aggressive saver to early retirement',
|
|
155
|
+
summary: 'High-income saver achieving early retirement in 15 years with a 50% savings rate.',
|
|
156
|
+
teaches: 'The power of a high savings rate, FI targets, and escalating contribution schedules.',
|
|
157
|
+
themeTags: ['fire', 'accumulation'],
|
|
158
|
+
learnSlug: 'example-aggressive-saver',
|
|
159
|
+
lookFor: 'FI target progress and escalating taxable brokerage contributions.',
|
|
160
|
+
build: buildAggressiveSaver,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'coast-fire',
|
|
164
|
+
title: 'Coast FIRE',
|
|
165
|
+
summary: 'Front-loading retirement savings early, then letting compounding carry the plan without contributions.',
|
|
166
|
+
teaches: 'Front-loaded contributions and Coast-FIRE portfolio growth over time.',
|
|
167
|
+
themeTags: ['fire', 'coast-fire'],
|
|
168
|
+
learnSlug: 'example-coast-fire',
|
|
169
|
+
lookFor: 'Active contribution phase ending early, while traditional IRA balance coasts to goal.',
|
|
170
|
+
build: buildCoastFire,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'barista-fire',
|
|
174
|
+
title: 'Barista FIRE',
|
|
175
|
+
summary: 'Transitioning to part-time work at age 40, using partial wages and ACA subsidies to bridge to retirement.',
|
|
176
|
+
teaches: 'Modeling part-time wages, bridging early retirement, and utilizing ACA healthcare credits.',
|
|
177
|
+
themeTags: ['fire', 'barista-fire'],
|
|
178
|
+
learnSlug: 'example-barista-fire',
|
|
179
|
+
lookFor: 'Wages transition to barista work at age 40, alongside ACA healthcare premium credits.',
|
|
180
|
+
build: buildBaristaFire,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'bridge-early-retirement',
|
|
184
|
+
title: 'Bridge to 59½ (SEPP)',
|
|
185
|
+
summary: 'Retiring at age 45 and using a 72(t) SEPP program to access retirement accounts penalty-free.',
|
|
186
|
+
teaches: 'Substantially Equal Periodic Payments (SEPP) to access pre-tax IRAs before 59½.',
|
|
187
|
+
themeTags: ['fire', 'accumulation'],
|
|
188
|
+
learnSlug: 'example-bridge-early-retirement',
|
|
189
|
+
lookFor: 'Deterministic 72(t) distributions from the traditional account starting at age 45.',
|
|
190
|
+
build: buildBridgeEarlyRetirement,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: 'lean-fat-fire',
|
|
194
|
+
title: 'Lean vs. Fat FIRE',
|
|
195
|
+
summary: 'Comparing Lean FIRE vs. Fat FIRE spending levels for a single household.',
|
|
196
|
+
teaches: 'Comparing different target retirement lifestyles and FI numbers side by side.',
|
|
197
|
+
themeTags: ['fire'],
|
|
198
|
+
learnSlug: 'example-lean-fat-fire',
|
|
199
|
+
lookFor: 'Compare base plan (Lean FIRE) vs Scenario (Fat FIRE) for FI years and balances.',
|
|
200
|
+
build: buildLeanFatFire,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'hsa-stealth-retirement',
|
|
204
|
+
title: 'HSA as a stealth account',
|
|
205
|
+
summary: 'Using a triple-tax-advantaged HSA as a primary investment vehicle for early retirement.',
|
|
206
|
+
teaches: 'Maximizing HSA contributions and investing them for long-term tax-free growth.',
|
|
207
|
+
themeTags: ['fire', 'accumulation'],
|
|
208
|
+
learnSlug: 'example-hsa-stealth-retirement',
|
|
209
|
+
lookFor: 'Stealth HSA account contributions and growth alongside a traditional 401(k).',
|
|
210
|
+
build: buildHsaStealthRetirement,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'salary-growth-escalation',
|
|
214
|
+
title: 'Salary growth & escalation',
|
|
215
|
+
summary: 'Visualizing how annual raises and auto-escalating savings change retirement trajectories.',
|
|
216
|
+
teaches: 'The combined compounding effect of wage growth and escalating contribution schedules.',
|
|
217
|
+
themeTags: ['fire', 'accumulation'],
|
|
218
|
+
learnSlug: 'example-salary-growth-escalation',
|
|
219
|
+
lookFor: 'Compounded raise factor on wages and annual escalation on pre-tax contributions.',
|
|
220
|
+
build: buildSalaryGrowthEscalation,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'guardrails-flex-goals',
|
|
224
|
+
title: 'Guardrails and flexible goals',
|
|
225
|
+
summary: 'Single retiree with layered spending (required floor + ideal/excess) and adaptive one-time goals under withdrawal-rate guardrails.',
|
|
226
|
+
teaches: 'Required floor is always protected; discretionary spending and flexible goals (movable/skippable/partial) are rationed in weak markets. MC reports separate required vs target success.',
|
|
227
|
+
themeTags: ['guardrails', 'shortfall'],
|
|
228
|
+
learnSlug: 'example-guardrails-flex-goals',
|
|
229
|
+
lookFor: 'Guardrail actions and factors in Results, required/targetShortfall split, flexibleGoals counters, and layered success rates in Monte Carlo.',
|
|
230
|
+
build: buildGuardrailsFlex,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: 'annuity-purchases-estate',
|
|
234
|
+
title: 'Annuity ladder and estate beneficiaries',
|
|
235
|
+
summary: 'Couple purchasing SPIA (non-qualified) and QLAC (qualified) with differentiated per-account estate beneficiaries (spouse, charity, non-spouse).',
|
|
236
|
+
teaches: 'Annuity purchase events create guaranteed income streams (exclusion ratio or taxable); QLAC reduces future RMDs; estate uses account-specific destination rules (rollover, untaxed charity, heir tax class).',
|
|
237
|
+
themeTags: ['annuity-estate', 'survivor'],
|
|
238
|
+
learnSlug: 'example-annuity-purchases-estate',
|
|
239
|
+
lookFor: 'Annuity income and purchase cash-flows in Results ledger; RMD reduction from QLAC; after-tax estate differences driven by beneficiary designations.',
|
|
240
|
+
build: buildAnnuityEstate,
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: 'glidepath-allocation',
|
|
244
|
+
title: 'Glidepath allocation and rebalancing',
|
|
245
|
+
summary: 'Near-retiree using 4-class allocations with glidepaths, staged targets, annual rebalancing, and class yields on taxable.',
|
|
246
|
+
teaches: 'Accounts grow at blended class returns with drift + rebalance (realization in taxable); MC applies class-correlated shocks; asset location opportunities visible in Insights.',
|
|
247
|
+
themeTags: ['allocation', 'rmd-irmaa'],
|
|
248
|
+
learnSlug: 'example-glidepath-allocation',
|
|
249
|
+
lookFor: 'Allocation editor and glide targets on Accounts; class returns/drag; MC frontiers + historical stress + downside percentiles vs the single-return scenario.',
|
|
250
|
+
build: buildGlidepathAllocation,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: 'hsa-property-depth',
|
|
254
|
+
title: 'HSA medical sub-ledger and home sale',
|
|
255
|
+
summary: 'HSA with medical-expense cap + reimburse-later strategy alongside a primary residence sale using cost basis, §121 exclusion, and recapture.',
|
|
256
|
+
teaches: 'HSA qualified withdrawals capped by modeled medical + accumulated reimburse pool; property sales compute exact gain after selling costs and exclusion (with ordinary recapture). Nondeductible basis applies pro-rata on IRA conversions.',
|
|
257
|
+
themeTags: ['hsa-depth', 'shortfall'],
|
|
258
|
+
learnSlug: 'example-hsa-property-depth',
|
|
259
|
+
lookFor: 'HSA withdrawalTreatment and reimburseLater fields; qualified vs taxable HSA flows in Results; property sale tax line items and net proceeds.',
|
|
260
|
+
build: buildHsaPropertyDepth,
|
|
261
|
+
},
|
|
262
|
+
// A-B control variants for side-by-side Plan Compare (same starting dollars / household where possible)
|
|
263
|
+
{
|
|
264
|
+
id: 'fixed-target-spending',
|
|
265
|
+
title: 'Fixed target spending (no guardrails)',
|
|
266
|
+
summary: 'Control version of the guardrails example: same balances and target lifestyle but classic fixed spending (no required floor or guardrail policy).',
|
|
267
|
+
teaches: 'A-B comparison: load both this and "Guardrails and flexible goals", then use Compare Plans to see how guardrails affect required success rate, depletion, and goal funding.',
|
|
268
|
+
themeTags: ['guardrails', 'shortfall'],
|
|
269
|
+
learnSlug: 'example-fixed-target-spending',
|
|
270
|
+
lookFor: 'Compare depletion year and MC success rates (especially requiredFloorSuccessRate) against the guardrails version.',
|
|
271
|
+
build: buildFixedTargetSpending,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: 'no-annuity-brokerage',
|
|
275
|
+
title: 'Brokerage only (no annuity purchases)',
|
|
276
|
+
summary: 'Control version: same couple and starting capital, but the annuity premiums stay invested in cash + traditional instead of being used to buy SPIA/QLAC.',
|
|
277
|
+
teaches: 'A-B comparison: load with "Annuity ladder and estate beneficiaries". Compare income stability, RMDs, and after-tax estate when money stays liquid vs annuitized.',
|
|
278
|
+
themeTags: ['annuity-estate', 'survivor'],
|
|
279
|
+
learnSlug: 'example-no-annuity-brokerage',
|
|
280
|
+
lookFor: 'Higher liquid balances early, no guaranteed income stream, different estate math. Use Compare to quantify the trade-off.',
|
|
281
|
+
build: buildNoAnnuityBrokerage,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: 'static-allocation-control',
|
|
285
|
+
title: 'Static allocation (no glidepath)',
|
|
286
|
+
summary: 'Control version with identical starting dollar amounts and accounts, but no allocation policies or rebalancing (single default return).',
|
|
287
|
+
teaches: 'A-B comparison with "Glidepath allocation and rebalancing". Same starting balances — see the impact of glide + rebalancing + class-correlated MC on risk metrics and outcomes.',
|
|
288
|
+
themeTags: ['allocation', 'rmd-irmaa'],
|
|
289
|
+
learnSlug: 'example-static-allocation-control',
|
|
290
|
+
lookFor: 'Run Monte Carlo on both and compare 10th-percentile estate, success curves, and depletion probability in the Compare view.',
|
|
291
|
+
build: buildStaticAllocationControl,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: 'brokerage-no-hsa',
|
|
295
|
+
title: 'Brokerage instead of HSA',
|
|
296
|
+
summary: 'Control version: the HSA dollars and contribution rate are held in taxable brokerage instead. Same property sale and other accounts.',
|
|
297
|
+
teaches: 'A-B comparison with "HSA medical sub-ledger and home sale". See the difference between triple-tax-advantaged HSA (with medical cap) vs ordinary brokerage growth and withdrawals.',
|
|
298
|
+
themeTags: ['hsa-depth', 'shortfall'],
|
|
299
|
+
learnSlug: 'example-brokerage-no-hsa',
|
|
300
|
+
lookFor: 'Compare tax drag on withdrawals, qualified medical treatment, and ending balances. Great for showing HSA advantage in Results.',
|
|
301
|
+
build: buildBrokerageNoHsa,
|
|
302
|
+
},
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
const BY_ID = new Map(EXAMPLE_PLANS.map((e) => [e.id, e]))
|
|
306
|
+
|
|
307
|
+
export function getExampleById(id: string): ExamplePlan | undefined {
|
|
308
|
+
return BY_ID.get(id)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function getExampleByLearnSlug(slug: string): ExamplePlan | undefined {
|
|
312
|
+
return EXAMPLE_PLANS.find((e) => e.learnSlug === slug)
|
|
313
|
+
}
|