@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.
Files changed (298) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +181 -0
  3. package/package.json +77 -0
  4. package/src/App.tsx +246 -0
  5. package/src/RouteErrorBoundary.tsx +45 -0
  6. package/src/assets/hero.png +0 -0
  7. package/src/assets/react.svg +1 -0
  8. package/src/assets/vite.svg +1 -0
  9. package/src/data/fedInvestClient.ts +113 -0
  10. package/src/data/localStore.ts +42 -0
  11. package/src/data/planOrigin.ts +24 -0
  12. package/src/data/planStore.ts +165 -0
  13. package/src/data/v2Backup.ts +101 -0
  14. package/src/import/ImportPage.tsx +347 -0
  15. package/src/import/ReviewChecklistView.tsx +38 -0
  16. package/src/import/brokerCsv.ts +395 -0
  17. package/src/import/csv.ts +133 -0
  18. package/src/import/genericCsv.ts +224 -0
  19. package/src/import/projectionLab.ts +350 -0
  20. package/src/import/reviewChecklist.ts +33 -0
  21. package/src/import/tenForty.ts +275 -0
  22. package/src/index.css +630 -0
  23. package/src/index.ts +16 -0
  24. package/src/learn/ArticleBody.tsx +78 -0
  25. package/src/learn/ArticlePage.tsx +57 -0
  26. package/src/learn/GlossaryPage.tsx +33 -0
  27. package/src/learn/LearnAboutScreen.tsx +41 -0
  28. package/src/learn/LearnCards.tsx +41 -0
  29. package/src/learn/LearnLink.tsx +91 -0
  30. package/src/learn/LearningCenterPage.tsx +114 -0
  31. package/src/learn/SourcesPage.tsx +98 -0
  32. package/src/learn/components/ArticleFigure.tsx +34 -0
  33. package/src/learn/components/ArticleShell.tsx +86 -0
  34. package/src/learn/components/ComparisonTable.tsx +42 -0
  35. package/src/learn/components/FormulaBlock.tsx +34 -0
  36. package/src/learn/components/PurchasingPowerChart.tsx +41 -0
  37. package/src/learn/components/RelatedArticles.tsx +27 -0
  38. package/src/learn/components/ScenarioCard.tsx +24 -0
  39. package/src/learn/components/SourceList.tsx +23 -0
  40. package/src/learn/components/charts.tsx +21 -0
  41. package/src/learn/content/about-retiregolden.ts +100 -0
  42. package/src/learn/content/aca-premium-tax-credits-and-magi.ts +103 -0
  43. package/src/learn/content/account-types-overview.ts +106 -0
  44. package/src/learn/content/after-tax-estate.ts +111 -0
  45. package/src/learn/content/agi-magi-and-taxable-income.ts +112 -0
  46. package/src/learn/content/appealing-irmaa-ssa-44.ts +95 -0
  47. package/src/learn/content/assumption-general-inflation.ts +82 -0
  48. package/src/learn/content/assumption-healthcare-inflation.ts +85 -0
  49. package/src/learn/content/assumption-heir-tax-rate.ts +79 -0
  50. package/src/learn/content/assumption-investment-returns.ts +90 -0
  51. package/src/learn/content/assumption-longevity-planning-age.ts +78 -0
  52. package/src/learn/content/assumption-recent-magi.ts +83 -0
  53. package/src/learn/content/assumption-social-security-cola.ts +89 -0
  54. package/src/learn/content/assumption-social-security-trust-fund.ts +83 -0
  55. package/src/learn/content/assumption-state-tax-override.ts +79 -0
  56. package/src/learn/content/beneficiaries-and-account-titling.ts +99 -0
  57. package/src/learn/content/break-even-useful-lens.ts +94 -0
  58. package/src/learn/content/building-a-retirement-spending-budget.ts +100 -0
  59. package/src/learn/content/cola-and-inflation-protection.ts +102 -0
  60. package/src/learn/content/divorced-spousal-and-survivor-records.ts +104 -0
  61. package/src/learn/content/dynamic-spending-guardrails.ts +90 -0
  62. package/src/learn/content/earnings-test-before-fra.ts +100 -0
  63. package/src/learn/content/employer-match-and-contribution-order.ts +104 -0
  64. package/src/learn/content/examplePlanArticles.ts +525 -0
  65. package/src/learn/content/fees-expense-ratios-and-compounding-drag.ts +98 -0
  66. package/src/learn/content/fi-number-and-four-percent-rule.ts +64 -0
  67. package/src/learn/content/filling-a-tax-bracket-with-roth-conversions.ts +98 -0
  68. package/src/learn/content/funded-ratio.ts +70 -0
  69. package/src/learn/content/healthcare-after-65.ts +103 -0
  70. package/src/learn/content/healthcare-before-65.ts +104 -0
  71. package/src/learn/content/historical-vs-random-return-models.ts +101 -0
  72. package/src/learn/content/how-assumptions-change-the-answer.ts +105 -0
  73. package/src/learn/content/how-much-can-i-spend.ts +105 -0
  74. package/src/learn/content/how-social-security-is-taxed.ts +95 -0
  75. package/src/learn/content/how-the-optimizer-thinks.ts +102 -0
  76. package/src/learn/content/how-the-optimizer-values-after-tax-estate.ts +97 -0
  77. package/src/learn/content/how-to-model-accumulation.ts +67 -0
  78. package/src/learn/content/how-to-read-a-retirement-projection.ts +115 -0
  79. package/src/learn/content/hsas-and-qualified-medical-expenses.ts +108 -0
  80. package/src/learn/content/hsas-as-retirement-accounts.ts +101 -0
  81. package/src/learn/content/inflation-risk.ts +98 -0
  82. package/src/learn/content/inherited-ira-10-year-rule.ts +105 -0
  83. package/src/learn/content/insurance-in-your-retirement-plan.ts +103 -0
  84. package/src/learn/content/irmaa-two-year-lookback.ts +99 -0
  85. package/src/learn/content/long-term-care-costs-and-insurance.ts +103 -0
  86. package/src/learn/content/long-term-care-insurance-as-risk-transfer.ts +98 -0
  87. package/src/learn/content/longevity-risk.ts +99 -0
  88. package/src/learn/content/marginal-vs-effective-tax-rate.ts +98 -0
  89. package/src/learn/content/medicare-part-b-vs-part-d-irmaa.ts +102 -0
  90. package/src/learn/content/mortality-weighted-social-security.ts +113 -0
  91. package/src/learn/content/moving-to-retiregolden.ts +86 -0
  92. package/src/learn/content/niit-high-income-investment-tax.ts +98 -0
  93. package/src/learn/content/ordinary-income-vs-capital-gains.ts +103 -0
  94. package/src/learn/content/paying-conversion-taxes-taxable-vs-ira.ts +102 -0
  95. package/src/learn/content/pensions-and-annuities.ts +101 -0
  96. package/src/learn/content/permanent-life-insurance-in-a-plan.ts +106 -0
  97. package/src/learn/content/pia-aime-and-bend-points.ts +103 -0
  98. package/src/learn/content/planner-overview.ts +106 -0
  99. package/src/learn/content/planning-for-couples-and-survivor-years.ts +108 -0
  100. package/src/learn/content/privacy-what-stays-in-your-browser.ts +99 -0
  101. package/src/learn/content/qcds-qualified-charitable-distributions.ts +101 -0
  102. package/src/learn/content/reading-the-results-page.ts +96 -0
  103. package/src/learn/content/reading-the-social-security-analysis-page.ts +106 -0
  104. package/src/learn/content/real-estate-home-equity-and-debt.ts +100 -0
  105. package/src/learn/content/reports-csv-exports-and-sharing.ts +101 -0
  106. package/src/learn/content/risk-based-guardrails.ts +100 -0
  107. package/src/learn/content/rmds-required-minimum-distributions.ts +100 -0
  108. package/src/learn/content/roth-conversion-basics.ts +104 -0
  109. package/src/learn/content/rsus-and-espp.ts +101 -0
  110. package/src/learn/content/rule-of-55-and-72t.ts +107 -0
  111. package/src/learn/content/savings-rate-biggest-lever.ts +66 -0
  112. package/src/learn/content/seed-your-plan-from-your-tax-return.ts +93 -0
  113. package/src/learn/content/sensitivity-testing-what-changes-the-answer.ts +104 -0
  114. package/src/learn/content/sequence-of-returns-risk.ts +98 -0
  115. package/src/learn/content/social-security-bridge.ts +67 -0
  116. package/src/learn/content/social-security-claiming-age-basics.ts +113 -0
  117. package/src/learn/content/social-security-taxes-vs-benefits.ts +76 -0
  118. package/src/learn/content/spending-profiles-and-the-retirement-smile.ts +92 -0
  119. package/src/learn/content/spousal-and-survivor-benefits.ts +120 -0
  120. package/src/learn/content/ssdi-and-retirement-planning.ts +72 -0
  121. package/src/learn/content/standard-deduction-senior-deduction-and-itemizing.ts +97 -0
  122. package/src/learn/content/state-income-taxes-in-retirement.ts +97 -0
  123. package/src/learn/content/step-up-in-basis.ts +102 -0
  124. package/src/learn/content/survivor-planning-for-couples.ts +110 -0
  125. package/src/learn/content/survivor-spending-in-couple-plans.ts +98 -0
  126. package/src/learn/content/tax-cliffs-and-bracket-edges.ts +105 -0
  127. package/src/learn/content/tax-loss-and-gain-harvesting.ts +99 -0
  128. package/src/learn/content/taxable-brokerage-basis-and-capital-gains.ts +99 -0
  129. package/src/learn/content/three-big-questions-spending-time-risk.ts +103 -0
  130. package/src/learn/content/tips-ladders.ts +92 -0
  131. package/src/learn/content/todays-dollars-vs-future-dollars.ts +107 -0
  132. package/src/learn/content/traditional-vs-roth-contributions.ts +113 -0
  133. package/src/learn/content/troubleshooting-surprising-results.ts +105 -0
  134. package/src/learn/content/trust-fund-haircut-scenarios.ts +101 -0
  135. package/src/learn/content/understanding-monte-carlo-success-rate.ts +118 -0
  136. package/src/learn/content/understanding-your-plan-assumptions.ts +134 -0
  137. package/src/learn/content/using-assumptions-and-provenance.ts +98 -0
  138. package/src/learn/content/using-scenarios-to-compare-choices.ts +99 -0
  139. package/src/learn/content/what-changes-when-you-move-states.ts +141 -0
  140. package/src/learn/content/what-is-fire.ts +65 -0
  141. package/src/learn/content/what-monte-carlo-proves.ts +98 -0
  142. package/src/learn/content/what-retiregolden-models.ts +103 -0
  143. package/src/learn/content/what-retirement-healthcare-really-costs.ts +117 -0
  144. package/src/learn/content/why-95-percent-is-not-a-guarantee.ts +98 -0
  145. package/src/learn/content/why-roth-conversions-raise-other-costs.ts +106 -0
  146. package/src/learn/content/why-small-tax-cliffs-can-matter.ts +109 -0
  147. package/src/learn/content/widows-penalty-and-survivor-brackets.ts +106 -0
  148. package/src/learn/content/withdrawal-order-basics.ts +105 -0
  149. package/src/learn/glossary.ts +191 -0
  150. package/src/learn/inlineMarkdown.tsx +54 -0
  151. package/src/learn/learn.css +537 -0
  152. package/src/learn/learningRegistry.ts +502 -0
  153. package/src/longevity/LongevityResults.tsx +85 -0
  154. package/src/longevity/LongevityWizard.tsx +305 -0
  155. package/src/longevity/constants.ts +15 -0
  156. package/src/longevity/factors.ts +125 -0
  157. package/src/longevity/model.ts +31 -0
  158. package/src/longevity/persistedGuard.ts +129 -0
  159. package/src/longevity/storage.ts +40 -0
  160. package/src/mc/messages.ts +118 -0
  161. package/src/mc/monteCarlo.worker.ts +44 -0
  162. package/src/mc/pool.ts +267 -0
  163. package/src/mc/runRequest.ts +125 -0
  164. package/src/optimize/messages.ts +84 -0
  165. package/src/optimize/optimize.worker.ts +29 -0
  166. package/src/optimize/runOptimize.ts +92 -0
  167. package/src/optimize/runSpendingSolve.ts +47 -0
  168. package/src/optimize/runner.ts +21 -0
  169. package/src/optimize/spendingMessages.ts +44 -0
  170. package/src/optimize/spendingRunner.ts +21 -0
  171. package/src/optimize/spendingSolve.worker.ts +18 -0
  172. package/src/planner/AssumptionsCardPage.tsx +136 -0
  173. package/src/planner/BucketLensCard.tsx +114 -0
  174. package/src/planner/ComparePlansPage.tsx +219 -0
  175. package/src/planner/DisclaimerPage.tsx +88 -0
  176. package/src/planner/HowTestedPage.tsx +159 -0
  177. package/src/planner/LiveStatus.tsx +15 -0
  178. package/src/planner/LongevityModal.tsx +55 -0
  179. package/src/planner/Modal.tsx +97 -0
  180. package/src/planner/MonteCarloPage.tsx +907 -0
  181. package/src/planner/OptimizePage.tsx +611 -0
  182. package/src/planner/PlanContext.tsx +198 -0
  183. package/src/planner/PlanPickerPage.tsx +124 -0
  184. package/src/planner/PlanWorkspace.tsx +290 -0
  185. package/src/planner/ProvenancePanel.tsx +45 -0
  186. package/src/planner/RelocationComparePage.tsx +485 -0
  187. package/src/planner/ReportPage.tsx +375 -0
  188. package/src/planner/ResultsPage.tsx +817 -0
  189. package/src/planner/ScenariosPage.tsx +285 -0
  190. package/src/planner/SocialSecuritySection.tsx +556 -0
  191. package/src/planner/SpendingSolverPage.tsx +512 -0
  192. package/src/planner/SsAnalysisPage.tsx +1134 -0
  193. package/src/planner/SurvivalPercentileModal.tsx +161 -0
  194. package/src/planner/SurvivorTransitionPage.tsx +286 -0
  195. package/src/planner/assumptionsExport.ts +371 -0
  196. package/src/planner/bucketLens.ts +89 -0
  197. package/src/planner/chartFrame.ts +8 -0
  198. package/src/planner/chartStyle.ts +11 -0
  199. package/src/planner/dialogViews.tsx +184 -0
  200. package/src/planner/dialogs.tsx +133 -0
  201. package/src/planner/examples/ExampleLibrary.tsx +189 -0
  202. package/src/planner/examples/ExamplePreviewBanner.tsx +55 -0
  203. package/src/planner/examples/ExamplesPage.tsx +25 -0
  204. package/src/planner/examples/OpenExampleButton.tsx +61 -0
  205. package/src/planner/examples/buildAggressiveSaver.ts +102 -0
  206. package/src/planner/examples/buildAnnuityEstate.ts +137 -0
  207. package/src/planner/examples/buildBaristaFire.ts +115 -0
  208. package/src/planner/examples/buildBracketFillRoth.ts +65 -0
  209. package/src/planner/examples/buildBridgeEarlyRetirement.ts +94 -0
  210. package/src/planner/examples/buildBrokerageNoHsa.ts +109 -0
  211. package/src/planner/examples/buildCoastFire.ts +88 -0
  212. package/src/planner/examples/buildContext.ts +20 -0
  213. package/src/planner/examples/buildEarlyCareerMatch.ts +93 -0
  214. package/src/planner/examples/buildEarlyRetireeAca.ts +61 -0
  215. package/src/planner/examples/buildExampleCouple.ts +103 -0
  216. package/src/planner/examples/buildFixedTargetSpending.ts +74 -0
  217. package/src/planner/examples/buildGlidepathAllocation.ts +131 -0
  218. package/src/planner/examples/buildGuardrailsFlex.ts +120 -0
  219. package/src/planner/examples/buildHsaPropertyDepth.ts +109 -0
  220. package/src/planner/examples/buildHsaStealthRetirement.ts +97 -0
  221. package/src/planner/examples/buildLeanFatFire.ts +109 -0
  222. package/src/planner/examples/buildLtcShock.ts +62 -0
  223. package/src/planner/examples/buildMovingStateTax.ts +53 -0
  224. package/src/planner/examples/buildNoAnnuityBrokerage.ts +92 -0
  225. package/src/planner/examples/buildRmdIrmaa.ts +55 -0
  226. package/src/planner/examples/buildSalaryGrowthEscalation.ts +96 -0
  227. package/src/planner/examples/buildStaticAllocationControl.ts +96 -0
  228. package/src/planner/examples/buildSurvivorYears.ts +62 -0
  229. package/src/planner/examples/buildUnderSavedSingle.ts +51 -0
  230. package/src/planner/examples/exampleCopy.ts +23 -0
  231. package/src/planner/examples/loadExample.ts +90 -0
  232. package/src/planner/examples/registry.ts +313 -0
  233. package/src/planner/explainPanels.tsx +233 -0
  234. package/src/planner/fields.tsx +381 -0
  235. package/src/planner/format.ts +33 -0
  236. package/src/planner/home/DataAndPrivacyCard.tsx +56 -0
  237. package/src/planner/home/GettingStartedPaths.tsx +46 -0
  238. package/src/planner/home/GettingStartedReopener.tsx +32 -0
  239. package/src/planner/home/StartHereLinks.tsx +22 -0
  240. package/src/planner/home/WelcomeHero.tsx +39 -0
  241. package/src/planner/home/YourPlans.tsx +72 -0
  242. package/src/planner/home/importErrorMessage.ts +22 -0
  243. package/src/planner/home/startHereSlugs.ts +7 -0
  244. package/src/planner/home/useHomeData.ts +190 -0
  245. package/src/planner/home/useHomeMode.ts +47 -0
  246. package/src/planner/householdActions.ts +22 -0
  247. package/src/planner/insights/InsightCardView.tsx +340 -0
  248. package/src/planner/insights/InsightsPage.tsx +204 -0
  249. package/src/planner/insights/categoryLabels.ts +11 -0
  250. package/src/planner/learnLinks.ts +85 -0
  251. package/src/planner/marketModelPicker.ts +172 -0
  252. package/src/planner/optimizePageChart.ts +40 -0
  253. package/src/planner/optimizePageClaim.ts +64 -0
  254. package/src/planner/planCompleteness.ts +27 -0
  255. package/src/planner/planContextCore.ts +26 -0
  256. package/src/planner/planner.css +2304 -0
  257. package/src/planner/provenanceLinks.ts +25 -0
  258. package/src/planner/sections/AccountFields.tsx +872 -0
  259. package/src/planner/sections/AccountsSection.tsx +89 -0
  260. package/src/planner/sections/AllocationPanel.tsx +261 -0
  261. package/src/planner/sections/AssumptionsSection.tsx +256 -0
  262. package/src/planner/sections/HouseholdSection.tsx +243 -0
  263. package/src/planner/sections/IncomeFloorSection.tsx +418 -0
  264. package/src/planner/sections/IncomeSection.tsx +170 -0
  265. package/src/planner/sections/InsuranceSection.tsx +362 -0
  266. package/src/planner/sections/SpendingSection.tsx +904 -0
  267. package/src/planner/sections/StrategySection.tsx +349 -0
  268. package/src/planner/sections/UpdateBalancesPanel.tsx +182 -0
  269. package/src/planner/sections/sectionHelpers.ts +48 -0
  270. package/src/planner/sections/shared.tsx +15 -0
  271. package/src/planner/sections.tsx +15 -0
  272. package/src/planner/ssAnalysis.ts +325 -0
  273. package/src/planner/successBand.ts +20 -0
  274. package/src/planner/survivorAnalysis.ts +277 -0
  275. package/src/planner/usStates.ts +19 -0
  276. package/src/planner/useMcSuccessRate.ts +77 -0
  277. package/src/planner/useProjection.ts +63 -0
  278. package/src/relocation/messages.ts +21 -0
  279. package/src/relocation/relocation.worker.ts +18 -0
  280. package/src/relocation/runRelocation.ts +17 -0
  281. package/src/relocation/runner.ts +22 -0
  282. package/src/report/brandingContext.ts +15 -0
  283. package/src/report/downloadReport.ts +34 -0
  284. package/src/report/reportHtml.ts +547 -0
  285. package/src/routes/LearnRoutes.tsx +46 -0
  286. package/src/routes/PlanRoutes.tsx +55 -0
  287. package/src/routes/RouteFallback.tsx +9 -0
  288. package/src/socialSecurity/breakEven.ts +107 -0
  289. package/src/socialSecurity/expectedPv.ts +164 -0
  290. package/src/socialSecurity/explain.ts +92 -0
  291. package/src/socialSecurity/ficaReturn.ts +81 -0
  292. package/src/socialSecurity/persistedSsGuard.ts +138 -0
  293. package/src/socialSecurity/ssFormUtils.ts +48 -0
  294. package/src/socialSecurity/ssaStatementXml.ts +156 -0
  295. package/src/socialSecurity/storage.ts +69 -0
  296. package/src/socialSecurity/survivorSwitching.ts +153 -0
  297. package/src/testSupport/samplePlan.ts +2 -0
  298. package/src/workers/run.ts +45 -0
@@ -0,0 +1,93 @@
1
+ import { createEmptyPlan, parsePlan, type Plan } from '@retiregolden/engine/model/plan'
2
+ import { exampleEntityId, exampleFixedNow, exampleIdFactory } from './buildContext'
3
+
4
+ const EXAMPLE_ID = 'early-career-match'
5
+
6
+ export function buildEarlyCareerMatch(): Plan {
7
+ const p1 = exampleEntityId(EXAMPLE_ID, 'p1')
8
+ const plan = createEmptyPlan({ name: 'Just getting started', now: exampleFixedNow, newId: exampleIdFactory(EXAMPLE_ID) })
9
+ plan.household = {
10
+ filingStatus: 'single',
11
+ hasQualifyingDependent: false,
12
+ state: 'CA',
13
+ stateMoves: [],
14
+ capitalLossCarryforward: 0,
15
+ people: [
16
+ { id: p1, name: 'Alex', dob: '2001-01-01', sex: 'average', retirementAge: 60, longevity: { planningAge: 90, source: 'manual' } },
17
+ ],
18
+ }
19
+ plan.accounts = [
20
+ {
21
+ type: 'cash',
22
+ id: exampleEntityId(EXAMPLE_ID, 'savings'),
23
+ name: 'Emergency Fund',
24
+ ownerPersonId: null,
25
+ annualReturnPct: 3,
26
+ balance: 10_000,
27
+ annualContribution: 0,
28
+ },
29
+ {
30
+ type: 'traditional',
31
+ id: exampleEntityId(EXAMPLE_ID, 'traditional-401k'),
32
+ name: 'Employer 401(k)',
33
+ ownerPersonId: p1,
34
+ annualReturnPct: 7,
35
+ kind: 'employer',
36
+ balance: 5_000,
37
+ annualContribution: 6_000,
38
+ employerMatch: {
39
+ matchPct: 100,
40
+ capPctOfPay: 4,
41
+ },
42
+ },
43
+ {
44
+ type: 'roth',
45
+ id: exampleEntityId(EXAMPLE_ID, 'roth-ira'),
46
+ name: 'Roth IRA',
47
+ ownerPersonId: p1,
48
+ annualReturnPct: 7,
49
+ kind: 'ira',
50
+ balance: 2_000,
51
+ annualContribution: 3_000,
52
+ },
53
+ ]
54
+ plan.incomes = [
55
+ {
56
+ type: 'wages',
57
+ id: exampleEntityId(EXAMPLE_ID, 'wages'),
58
+ personId: p1,
59
+ annualGross: 65_000,
60
+ endAge: null,
61
+ realGrowthPct: 3,
62
+ },
63
+ {
64
+ type: 'socialSecurity',
65
+ id: exampleEntityId(EXAMPLE_ID, 'ss'),
66
+ personId: p1,
67
+ piaMonthly: 2_000,
68
+ earnings: null,
69
+ claimAge: { years: 67, months: 0 },
70
+ },
71
+ ]
72
+ plan.expenses = {
73
+ baseAnnual: 45_000,
74
+ phases: [],
75
+ oneTimeGoals: [],
76
+ healthcare: { pre65MonthlyPremiumPerPerson: 350, applyAcaCredit: true, medicareExtrasMonthlyPerPerson: 150 },
77
+ }
78
+ plan.assumptions = {
79
+ inflationPct: 2.5,
80
+ healthcareExtraInflationPct: 2,
81
+ defaultReturnPct: 6,
82
+ ssCola: { mode: 'matchInflation' },
83
+ ssHaircut: null,
84
+ stateEffectiveTaxPct: 0,
85
+ localIncomeTaxPct: 0,
86
+ recentAnnualMagi: 65_000,
87
+ heirTaxRatePct: 25,
88
+ safeWithdrawalRatePct: 4,
89
+ }
90
+ const parsed = parsePlan(plan)
91
+ if (!parsed.ok) throw new Error(`early-career-match invalid: ${parsed.issues.join('; ')}`)
92
+ return parsed.plan
93
+ }
@@ -0,0 +1,61 @@
1
+ /** Early retiree and the ACA cliff — pre-65 MAGI vs premium-credit cliff. */
2
+
3
+ import { createEmptyPlan, parsePlan, type Plan } from '@retiregolden/engine/model/plan'
4
+ import { EXAMPLE_FIXED_YEAR, exampleEntityId, exampleFixedNow, exampleIdFactory } from './buildContext'
5
+
6
+ const EXAMPLE_ID = 'early-retiree-aca'
7
+
8
+ export function buildEarlyRetireeAca(): Plan {
9
+ const p1 = exampleEntityId(EXAMPLE_ID, 'p1')
10
+ const plan = createEmptyPlan({ name: 'Early retiree & the ACA cliff', 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: 'Casey', dob: '1964-01-01', sex: 'female', retirementAge: 58, longevity: { planningAge: 92, source: 'manual' } },
19
+ ],
20
+ }
21
+ plan.accounts = [
22
+ { type: 'cash', id: exampleEntityId(EXAMPLE_ID, 'cash'), name: 'Cash', ownerPersonId: null, annualReturnPct: 2, balance: 200_000, annualContribution: 0 },
23
+ { type: 'traditional', id: exampleEntityId(EXAMPLE_ID, 'ira'), name: 'Traditional IRA', ownerPersonId: p1, annualReturnPct: null, kind: 'ira', balance: 450_000, annualContribution: 0 },
24
+ { type: 'roth', id: exampleEntityId(EXAMPLE_ID, 'roth'), name: 'Roth IRA', ownerPersonId: p1, annualReturnPct: null, kind: 'ira', balance: 120_000, annualContribution: 0 },
25
+ ]
26
+ plan.incomes = [
27
+ { type: 'recurring', id: exampleEntityId(EXAMPLE_ID, 'consulting'), label: 'Consulting', annualAmount: 55_000, startYear: EXAMPLE_FIXED_YEAR, endYear: null, inflationAdjusted: true, taxTreatment: 'ordinary' },
28
+ ]
29
+ plan.expenses = {
30
+ baseAnnual: 48_000,
31
+ phases: [],
32
+ oneTimeGoals: [],
33
+ healthcare: { pre65MonthlyPremiumPerPerson: 1_000, applyAcaCredit: true, medicareExtrasMonthlyPerPerson: 0 },
34
+ }
35
+ plan.strategies = {
36
+ withdrawalOrder: { mode: 'sequential' },
37
+ rothConversion: {
38
+ mode: 'fillToTarget',
39
+ target: 'topOfBracket',
40
+ targetValue: 12,
41
+ startYear: EXAMPLE_FIXED_YEAR,
42
+ endYear: EXAMPLE_FIXED_YEAR + 4,
43
+ },
44
+ qcdAnnual: 0,
45
+ }
46
+ plan.assumptions = {
47
+ inflationPct: 2.5,
48
+ healthcareExtraInflationPct: 3,
49
+ defaultReturnPct: 5,
50
+ ssCola: { mode: 'matchInflation' },
51
+ ssHaircut: null,
52
+ stateEffectiveTaxPct: 0,
53
+ localIncomeTaxPct: 0,
54
+ recentAnnualMagi: 50_000,
55
+ heirTaxRatePct: 22,
56
+ safeWithdrawalRatePct: 4,
57
+ }
58
+ const parsed = parsePlan(plan)
59
+ if (!parsed.ok) throw new Error(`early retiree ACA invalid: ${parsed.issues.join('; ')}`)
60
+ return parsed.plan
61
+ }
@@ -0,0 +1,103 @@
1
+ /** Example couple — the full picture: accounts, SS, Roth strategy, insurance, scenarios. */
2
+
3
+ import { createEmptyPlan, parsePlan, type Plan } from '@retiregolden/engine/model/plan'
4
+ import { TRUSTEES_DEFAULT_SS_HAIRCUT } from '@retiregolden/engine/params'
5
+ import { EXAMPLE_FIXED_YEAR, exampleEntityId, exampleFixedNow, exampleIdFactory } from './buildContext'
6
+
7
+ const EXAMPLE_ID = 'example-couple'
8
+
9
+ export function buildExampleCouple(): Plan {
10
+ const meId = exampleEntityId(EXAMPLE_ID, 'alex')
11
+ const partnerId = exampleEntityId(EXAMPLE_ID, 'sam')
12
+ const plan = createEmptyPlan({ name: 'Example couple', now: exampleFixedNow, newId: exampleIdFactory(EXAMPLE_ID) })
13
+ plan.household = {
14
+ filingStatus: 'marriedFilingJointly',
15
+ hasQualifyingDependent: false,
16
+ state: 'KY',
17
+ stateMoves: [],
18
+ capitalLossCarryforward: 0,
19
+ people: [
20
+ { id: meId, name: 'Alex', dob: '1962-04-15', sex: 'male', retirementAge: 66, longevity: { planningAge: 92, source: 'manual' } },
21
+ { id: partnerId, name: 'Sam', dob: '1964-09-02', sex: 'female', retirementAge: 64, longevity: { planningAge: 95, source: 'manual' } },
22
+ ],
23
+ }
24
+ plan.accounts = [
25
+ { type: 'cash', id: exampleEntityId(EXAMPLE_ID, 'savings'), name: 'Savings', ownerPersonId: null, annualReturnPct: 2, balance: 60_000, annualContribution: 0 },
26
+ { type: 'taxable', id: exampleEntityId(EXAMPLE_ID, 'brokerage'), name: 'Joint brokerage', ownerPersonId: null, annualReturnPct: null, balance: 650_000, costBasis: 420_000, annualContribution: 12_000 },
27
+ { type: 'equityComp', id: exampleEntityId(EXAMPLE_ID, 'rsu'), name: 'Alex RSUs', ownerPersonId: meId, annualReturnPct: null, balance: 120_000, costBasis: 95_000, annualContribution: 0, vestingMode: 'cliff', vestDate: '2028-03-15' },
28
+ { type: 'traditional', id: exampleEntityId(EXAMPLE_ID, '401k'), name: 'Alex 401(k)', ownerPersonId: meId, annualReturnPct: null, kind: 'employer', balance: 820_000, annualContribution: 24_000 },
29
+ { type: 'traditional', id: exampleEntityId(EXAMPLE_ID, 'sam-ira'), name: 'Sam IRA', ownerPersonId: partnerId, annualReturnPct: null, kind: 'ira', balance: 310_000, annualContribution: 0 },
30
+ { type: 'roth', id: exampleEntityId(EXAMPLE_ID, 'roth'), name: 'Alex Roth IRA', ownerPersonId: meId, annualReturnPct: null, kind: 'ira', balance: 145_000, annualContribution: 7_500, contributionBasis: 80_000 },
31
+ { type: 'property', id: exampleEntityId(EXAMPLE_ID, 'home'), name: 'Home', ownerPersonId: null, annualReturnPct: null, value: 420_000, plannedSaleYear: null, expectedNetProceeds: null, propertyTaxAnnual: 6_000, insuranceAnnual: 1_800 },
32
+ { type: 'debt', id: exampleEntityId(EXAMPLE_ID, 'mortgage'), name: 'Mortgage', ownerPersonId: null, annualReturnPct: null, balance: 180_000, interestPct: 3, monthlyPayment: 1_100 },
33
+ ]
34
+ plan.insurance = [
35
+ {
36
+ kind: 'permanentLife', id: exampleEntityId(EXAMPLE_ID, 'wl'), name: 'Alex whole life', insured: meId, beneficiary: 'estate',
37
+ annualPremium: 3_600, premiumMode: 'lifetime', deathBenefit: 350_000,
38
+ cashValue: 85_000, cashValueMode: 'flatRate', cashValueGrowthPct: 4,
39
+ },
40
+ {
41
+ kind: 'ltc', id: exampleEntityId(EXAMPLE_ID, 'ltc-alex'), name: 'Alex LTC', owner: meId,
42
+ annualPremium: 2_400, premiumMode: 'lifetime', benefitMonthly: 6_000, benefitPeriodYears: 3,
43
+ eliminationPeriodDays: 90, inflationRiderPct: 3,
44
+ },
45
+ {
46
+ kind: 'ltc', id: exampleEntityId(EXAMPLE_ID, 'ltc-sam'), name: 'Sam LTC', owner: partnerId,
47
+ annualPremium: 2_200, premiumMode: 'lifetime', benefitMonthly: 6_000, benefitPeriodYears: 3,
48
+ eliminationPeriodDays: 90, inflationRiderPct: 3,
49
+ },
50
+ ]
51
+ plan.careEvents = [
52
+ { id: exampleEntityId(EXAMPLE_ID, 'care'), personId: meId, startAge: 88, durationYears: 3, annualCost: 90_000 },
53
+ ]
54
+ plan.incomes = [
55
+ { type: 'wages', id: exampleEntityId(EXAMPLE_ID, 'wages-alex'), personId: meId, annualGross: 140_000, endAge: null, realGrowthPct: 0 },
56
+ { type: 'wages', id: exampleEntityId(EXAMPLE_ID, 'wages-sam'), personId: partnerId, annualGross: 85_000, endAge: null, realGrowthPct: 0 },
57
+ { type: 'socialSecurity', id: exampleEntityId(EXAMPLE_ID, 'ss-alex'), personId: meId, piaMonthly: 2_900, earnings: null, claimAge: { years: 70, months: 0 } },
58
+ { type: 'socialSecurity', id: exampleEntityId(EXAMPLE_ID, 'ss-sam'), personId: partnerId, piaMonthly: 1_950, earnings: null, claimAge: { years: 67, months: 0 } },
59
+ ]
60
+ plan.expenses = {
61
+ baseAnnual: 96_000,
62
+ phases: [
63
+ { fromAge: 75, multiplier: 0.9 },
64
+ { fromAge: 85, multiplier: 0.8 },
65
+ ],
66
+ oneTimeGoals: [{ id: exampleEntityId(EXAMPLE_ID, 'remodel'), label: 'Kitchen remodel', year: EXAMPLE_FIXED_YEAR + 3, amount: 45_000 }],
67
+ healthcare: { pre65MonthlyPremiumPerPerson: 950, applyAcaCredit: true, medicareExtrasMonthlyPerPerson: 180 },
68
+ }
69
+ plan.strategies = {
70
+ withdrawalOrder: { mode: 'sequential' },
71
+ rothConversion: {
72
+ mode: 'fillToTarget',
73
+ target: 'topOfBracket',
74
+ targetValue: 22,
75
+ startYear: EXAMPLE_FIXED_YEAR + 2,
76
+ endYear: EXAMPLE_FIXED_YEAR + 10,
77
+ },
78
+ qcdAnnual: 0,
79
+ }
80
+ plan.assumptions = {
81
+ inflationPct: 2.5,
82
+ healthcareExtraInflationPct: 3,
83
+ defaultReturnPct: 5.5,
84
+ ssCola: { mode: 'matchInflation' },
85
+ ssHaircut: null,
86
+ stateEffectiveTaxPct: 0,
87
+ localIncomeTaxPct: 0,
88
+ recentAnnualMagi: 225_000,
89
+ heirTaxRatePct: 25,
90
+ safeWithdrawalRatePct: 4,
91
+ }
92
+ plan.scenarios = [
93
+ {
94
+ id: exampleEntityId(EXAMPLE_ID, 'ss-cut'),
95
+ name: `${TRUSTEES_DEFAULT_SS_HAIRCUT.cutPct}% Social Security cut in ${TRUSTEES_DEFAULT_SS_HAIRCUT.fromYear}`,
96
+ patch: { assumptions: { ssHaircut: { ...TRUSTEES_DEFAULT_SS_HAIRCUT } } },
97
+ },
98
+ { id: exampleEntityId(EXAMPLE_ID, 'spend-more'), name: 'Spend 15% more', patch: { expenses: { baseAnnual: Math.round(96_000 * 1.15) } } },
99
+ ]
100
+ const parsed = parsePlan(plan)
101
+ if (!parsed.ok) throw new Error(`example couple invalid: ${parsed.issues.join('; ')}`)
102
+ return parsed.plan
103
+ }
@@ -0,0 +1,74 @@
1
+ /** Fixed target spending control (A-B pair for guardrails comparison).
2
+ * Identical household, balances, and target lifestyle as guardrails-flex-goals,
3
+ * but uses classic fixed target spending (no required floor, no guardrails policy,
4
+ * no classified/movable goals). Use with the guardrails example in Compare Plans
5
+ * to see success rate / depletion differences.
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 = 'fixed-target-spending'
12
+
13
+ export function buildFixedTargetSpending(): Plan {
14
+ const p1 = exampleEntityId(EXAMPLE_ID, 'p1')
15
+ const plan = createEmptyPlan({ name: 'Fixed target spending (control)', now: exampleFixedNow, newId: exampleIdFactory(EXAMPLE_ID) })
16
+
17
+ plan.household = {
18
+ filingStatus: 'single',
19
+ hasQualifyingDependent: false,
20
+ state: 'KY',
21
+ stateMoves: [],
22
+ capitalLossCarryforward: 0,
23
+ people: [
24
+ { id: p1, name: 'Riley', dob: '1963-06-15', sex: 'female', retirementAge: 62, longevity: { planningAge: 92, source: 'manual' } },
25
+ ],
26
+ }
27
+
28
+ plan.accounts = [
29
+ { type: 'cash', id: exampleEntityId(EXAMPLE_ID, 'cash'), name: 'Cash reserve', ownerPersonId: null, annualReturnPct: 2.0, balance: 85_000, annualContribution: 0 },
30
+ { type: 'taxable', id: exampleEntityId(EXAMPLE_ID, 'brokerage'), name: 'Taxable brokerage', ownerPersonId: null, annualReturnPct: null, balance: 420_000, costBasis: 280_000, annualContribution: 0 },
31
+ ]
32
+
33
+ // Classic fixed target spending — no layers, no policy, simple goals
34
+ plan.expenses = {
35
+ baseAnnual: 58_000,
36
+ phases: [
37
+ { fromAge: 78, multiplier: 0.92 },
38
+ ],
39
+ oneTimeGoals: [
40
+ { id: exampleEntityId(EXAMPLE_ID, 'roof'), label: 'Roof replacement', year: EXAMPLE_FIXED_YEAR + 4, amount: 28_000 },
41
+ { id: exampleEntityId(EXAMPLE_ID, 'trip'), label: 'Big anniversary trip', year: EXAMPLE_FIXED_YEAR + 2, amount: 18_000 },
42
+ { id: exampleEntityId(EXAMPLE_ID, 'gift'), label: 'Family gift / remodel', year: EXAMPLE_FIXED_YEAR + 6, amount: 35_000 },
43
+ ],
44
+ healthcare: { pre65MonthlyPremiumPerPerson: 720, applyAcaCredit: true, medicareExtrasMonthlyPerPerson: 190 },
45
+ // No spendingPolicy, no required/ideal/excess
46
+ }
47
+
48
+ plan.incomes = [
49
+ { type: 'socialSecurity', id: exampleEntityId(EXAMPLE_ID, 'ss'), personId: p1, piaMonthly: 1850, earnings: null, claimAge: { years: 67, months: 0 } },
50
+ ]
51
+
52
+ plan.strategies = {
53
+ withdrawalOrder: { mode: 'sequential' },
54
+ rothConversion: { mode: 'none' },
55
+ qcdAnnual: 0,
56
+ }
57
+
58
+ plan.assumptions = {
59
+ inflationPct: 2.4,
60
+ healthcareExtraInflationPct: 3.2,
61
+ defaultReturnPct: 4.8,
62
+ ssCola: { mode: 'matchInflation' },
63
+ ssHaircut: null,
64
+ stateEffectiveTaxPct: 0,
65
+ localIncomeTaxPct: 0,
66
+ recentAnnualMagi: 0,
67
+ heirTaxRatePct: 24,
68
+ safeWithdrawalRatePct: 3.8,
69
+ }
70
+
71
+ const parsed = parsePlan(plan)
72
+ if (!parsed.ok) throw new Error(`fixed-target-spending invalid: ${parsed.issues.join('; ')}`)
73
+ return parsed.plan
74
+ }
@@ -0,0 +1,131 @@
1
+ /** Glidepath allocation + rebalancing + MC v2 — positive demonstration of 4-class allocation,
2
+ * linear glidepath to conservative, annual rebalancing (with taxable realization), class yields,
3
+ * and improved downside risk metrics vs single-return baseline.
4
+ * Also surfaces asset-location opportunities via Insights.
5
+ */
6
+
7
+ import { createEmptyPlan, parsePlan, type Account, type Plan } from '@retiregolden/engine/model/plan'
8
+ import { EXAMPLE_FIXED_YEAR, exampleEntityId, exampleFixedNow, exampleIdFactory } from './buildContext'
9
+
10
+ const EXAMPLE_ID = 'glidepath-allocation'
11
+
12
+ export function buildGlidepathAllocation(): Plan {
13
+ const p1 = exampleEntityId(EXAMPLE_ID, 'p1')
14
+ const plan = createEmptyPlan({ name: 'Glidepath allocation retiree', now: exampleFixedNow, newId: exampleIdFactory(EXAMPLE_ID) })
15
+
16
+ plan.household = {
17
+ filingStatus: 'single',
18
+ hasQualifyingDependent: false,
19
+ state: 'CA',
20
+ stateMoves: [],
21
+ capitalLossCarryforward: 12000,
22
+ people: [
23
+ { id: p1, name: 'Morgan', dob: '1962-09-05', sex: 'male', retirementAge: 63, longevity: { planningAge: 91, source: 'manual' } },
24
+ ],
25
+ }
26
+
27
+ const allocAggressive = { usStocks: 55, intlStocks: 15, bonds: 25, cash: 5 }
28
+ const allocMid = { usStocks: 40, intlStocks: 10, bonds: 40, cash: 10 }
29
+ const allocConserv = { usStocks: 25, intlStocks: 5, bonds: 55, cash: 15 }
30
+
31
+ plan.accounts = [
32
+ {
33
+ type: 'taxable',
34
+ id: exampleEntityId(EXAMPLE_ID, 'broker'),
35
+ name: 'Taxable brokerage (gliding)',
36
+ ownerPersonId: null,
37
+ annualReturnPct: null,
38
+ balance: 380_000,
39
+ costBasis: 195_000,
40
+ annualContribution: 0,
41
+ // Linear glide from now-ish to later conservative
42
+ allocation: {
43
+ mode: 'linear',
44
+ rebalancing: 'annual',
45
+ startYear: EXAMPLE_FIXED_YEAR,
46
+ endYear: EXAMPLE_FIXED_YEAR + 12,
47
+ from: allocAggressive,
48
+ to: allocConserv,
49
+ },
50
+ },
51
+ {
52
+ type: 'traditional',
53
+ id: exampleEntityId(EXAMPLE_ID, 't401k'),
54
+ name: 'Traditional 401(k)',
55
+ ownerPersonId: p1,
56
+ annualReturnPct: null,
57
+ kind: 'employer',
58
+ balance: 920_000,
59
+ annualContribution: 0,
60
+ allocation: {
61
+ mode: 'staged',
62
+ rebalancing: 'annual',
63
+ stages: [
64
+ { fromYear: EXAMPLE_FIXED_YEAR, weights: allocAggressive },
65
+ { fromYear: EXAMPLE_FIXED_YEAR + 8, weights: allocMid },
66
+ ],
67
+ },
68
+ },
69
+ {
70
+ type: 'roth',
71
+ id: exampleEntityId(EXAMPLE_ID, 'roth'),
72
+ name: 'Roth IRA',
73
+ ownerPersonId: p1,
74
+ annualReturnPct: null,
75
+ kind: 'ira',
76
+ balance: 165_000,
77
+ annualContribution: 0,
78
+ allocation: { mode: 'static', rebalancing: 'annual', weights: allocMid },
79
+ },
80
+ ]
81
+
82
+ plan.expenses = {
83
+ baseAnnual: 62_000,
84
+ phases: [],
85
+ oneTimeGoals: [],
86
+ healthcare: { pre65MonthlyPremiumPerPerson: 810, applyAcaCredit: true, medicareExtrasMonthlyPerPerson: 175 },
87
+ }
88
+
89
+ plan.incomes = [
90
+ { type: 'socialSecurity', id: exampleEntityId(EXAMPLE_ID, 'ss'), personId: p1, piaMonthly: 2100, earnings: null, claimAge: { years: 70, months: 0 } },
91
+ ]
92
+
93
+ plan.strategies = {
94
+ withdrawalOrder: { mode: 'sequential' },
95
+ rothConversion: { mode: 'fillToTarget', target: 'topOfBracket', targetValue: 22, startYear: EXAMPLE_FIXED_YEAR + 1, endYear: EXAMPLE_FIXED_YEAR + 9 },
96
+ qcdAnnual: 3000,
97
+ }
98
+
99
+ plan.assumptions = {
100
+ inflationPct: 2.5,
101
+ healthcareExtraInflationPct: 3.1,
102
+ defaultReturnPct: 5.0,
103
+ ssCola: { mode: 'matchInflation' },
104
+ ssHaircut: null,
105
+ stateEffectiveTaxPct: 0,
106
+ localIncomeTaxPct: 0,
107
+ recentAnnualMagi: 95000,
108
+ heirTaxRatePct: 26,
109
+ safeWithdrawalRatePct: 3.7,
110
+ // Optional class overrides to highlight drag/returns (additive)
111
+ assetClassParams: {
112
+ usStocks: { returnPct: 7.2, volatilityPct: 16 },
113
+ bonds: { returnPct: 4.1, volatilityPct: 6 },
114
+ },
115
+ }
116
+
117
+ // Scenario: single-factor (no allocation) to contrast MC risk
118
+ plan.scenarios = [
119
+ {
120
+ id: exampleEntityId(EXAMPLE_ID, 'single-return'),
121
+ name: 'Single return (no allocation)',
122
+ patch: {
123
+ accounts: plan.accounts.map((a: Account) => ({ ...a, allocation: undefined, annualReturnPct: 5.0 })),
124
+ },
125
+ },
126
+ ]
127
+
128
+ const parsed = parsePlan(plan)
129
+ if (!parsed.ok) throw new Error(`glidepath-allocation invalid: ${parsed.issues.join('; ')}`)
130
+ return parsed.plan
131
+ }
@@ -0,0 +1,120 @@
1
+ /** Guardrails + flexible goals — positive/negative demonstration of required floor protection,
2
+ * discretionary cuts, movable/skippable/partial goals, and layered MC success rates.
3
+ * Exercises spending guardrails enhancement + MC/Insights surfaces.
4
+ */
5
+
6
+ import { createEmptyPlan, parsePlan, type Plan } from '@retiregolden/engine/model/plan'
7
+ import { EXAMPLE_FIXED_YEAR, exampleEntityId, exampleFixedNow, exampleIdFactory } from './buildContext'
8
+
9
+ const EXAMPLE_ID = 'guardrails-flex-goals'
10
+
11
+ export function buildGuardrailsFlex(): Plan {
12
+ const p1 = exampleEntityId(EXAMPLE_ID, 'p1')
13
+ const plan = createEmptyPlan({ name: 'Guardrails and flexible goals', now: exampleFixedNow, newId: exampleIdFactory(EXAMPLE_ID) })
14
+
15
+ plan.household = {
16
+ filingStatus: 'single',
17
+ hasQualifyingDependent: false,
18
+ state: 'KY',
19
+ stateMoves: [],
20
+ capitalLossCarryforward: 0,
21
+ people: [
22
+ { id: p1, name: 'Riley', dob: '1963-06-15', sex: 'female', retirementAge: 62, longevity: { planningAge: 92, source: 'manual' } },
23
+ ],
24
+ }
25
+
26
+ plan.accounts = [
27
+ { type: 'cash', id: exampleEntityId(EXAMPLE_ID, 'cash'), name: 'Cash reserve', ownerPersonId: null, annualReturnPct: 2.0, balance: 85_000, annualContribution: 0 },
28
+ { type: 'taxable', id: exampleEntityId(EXAMPLE_ID, 'brokerage'), name: 'Taxable brokerage', ownerPersonId: null, annualReturnPct: null, balance: 420_000, costBasis: 280_000, annualContribution: 0 },
29
+ ]
30
+
31
+ // Core spending layers for guardrails demo
32
+ plan.expenses = {
33
+ baseAnnual: 58_000,
34
+ requiredAnnual: 34_000, // protected floor
35
+ idealAnnual: 12_000, // aspirational layer
36
+ excessAnnual: 6_000, // opportunistic
37
+ phases: [
38
+ { fromAge: 78, multiplier: 0.92 },
39
+ ],
40
+ oneTimeGoals: [
41
+ // Fixed essential (high priority)
42
+ {
43
+ id: exampleEntityId(EXAMPLE_ID, 'roof'),
44
+ label: 'Roof replacement (required)',
45
+ year: EXAMPLE_FIXED_YEAR + 4,
46
+ amount: 28_000,
47
+ classification: 'required',
48
+ flexibility: 'fixed',
49
+ priority: 1,
50
+ },
51
+ // Movable target goal (can slide in bad markets)
52
+ {
53
+ id: exampleEntityId(EXAMPLE_ID, 'trip'),
54
+ label: 'Big anniversary trip (target)',
55
+ year: EXAMPLE_FIXED_YEAR + 2,
56
+ amount: 18_000,
57
+ classification: 'target',
58
+ flexibility: 'movable',
59
+ earliestYear: EXAMPLE_FIXED_YEAR + 1,
60
+ latestYear: EXAMPLE_FIXED_YEAR + 5,
61
+ priority: 10,
62
+ allowPartialFunding: true,
63
+ minFundingPct: 60,
64
+ },
65
+ // Skippable ideal (low priority)
66
+ {
67
+ id: exampleEntityId(EXAMPLE_ID, 'gift'),
68
+ label: 'Family gift / remodel (ideal)',
69
+ year: EXAMPLE_FIXED_YEAR + 6,
70
+ amount: 35_000,
71
+ classification: 'ideal',
72
+ flexibility: 'skippable',
73
+ earliestYear: EXAMPLE_FIXED_YEAR + 3,
74
+ latestYear: EXAMPLE_FIXED_YEAR + 8,
75
+ priority: 30,
76
+ },
77
+ ],
78
+ healthcare: { pre65MonthlyPremiumPerPerson: 720, applyAcaCredit: true, medicareExtrasMonthlyPerPerson: 190 },
79
+ spendingPolicy: {
80
+ mode: 'withdrawalRateGuardrails',
81
+ upperGuardrailPct: 125,
82
+ },
83
+ }
84
+
85
+ plan.incomes = [
86
+ { type: 'socialSecurity', id: exampleEntityId(EXAMPLE_ID, 'ss'), personId: p1, piaMonthly: 1850, earnings: null, claimAge: { years: 67, months: 0 } },
87
+ ]
88
+
89
+ plan.strategies = {
90
+ withdrawalOrder: { mode: 'sequential' },
91
+ rothConversion: { mode: 'none' },
92
+ qcdAnnual: 0,
93
+ }
94
+
95
+ plan.assumptions = {
96
+ inflationPct: 2.4,
97
+ healthcareExtraInflationPct: 3.2,
98
+ defaultReturnPct: 4.8,
99
+ ssCola: { mode: 'matchInflation' },
100
+ ssHaircut: null,
101
+ stateEffectiveTaxPct: 0,
102
+ localIncomeTaxPct: 0,
103
+ recentAnnualMagi: 0,
104
+ heirTaxRatePct: 24,
105
+ safeWithdrawalRatePct: 3.8,
106
+ }
107
+
108
+ // Scenario to compare guardrails off (for side-by-side)
109
+ plan.scenarios = [
110
+ {
111
+ id: exampleEntityId(EXAMPLE_ID, 'no-guard'),
112
+ name: 'No guardrails (full target always)',
113
+ patch: { expenses: { spendingPolicy: undefined, requiredAnnual: undefined } },
114
+ },
115
+ ]
116
+
117
+ const parsed = parsePlan(plan)
118
+ if (!parsed.ok) throw new Error(`guardrails-flex-goals invalid: ${parsed.issues.join('; ')}`)
119
+ return parsed.plan
120
+ }