@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,275 @@
1
+ /**
2
+ * 1040-seeded onboarding, guided v1 (onboarding-import-and-migration step 5).
3
+ *
4
+ * "Start from your tax return": the user types ~12 line values off last
5
+ * year's Form 1040 and this maps them into a coherent draft plan — income
6
+ * streams, filing/bracket context, an estimated taxable account, and the
7
+ * IRMAA-lookback MAGI. Guided entry only: no PDF parsing, no OCR (explicitly
8
+ * deferred), nothing leaves the device. Every prefilled value carries a
9
+ * "from your 1040" review item and is ordinary editable plan data afterward.
10
+ */
11
+
12
+ import type { Plan } from '@retiregolden/engine/model/plan'
13
+ import { createEmptyPlan, parsePlan } from '@retiregolden/engine/model/plan'
14
+ import { MAX_REASONABLE_DOLLARS } from './csv'
15
+ import type { ImportReviewItem } from './reviewChecklist'
16
+
17
+ export interface TenFortyInputs {
18
+ filingStatus: 'single' | 'marriedFilingJointly'
19
+ /** Two-letter state of residence. */
20
+ state: string
21
+ /** ISO date of birth; needed to anchor ages (not on the 1040 itself). */
22
+ primaryDob: string
23
+ /** Required when filing jointly. */
24
+ spouseDob?: string
25
+ /** Line 1a — wages, salaries, tips. */
26
+ wages: number
27
+ /** Line 2a — tax-exempt interest (feeds MAGI). */
28
+ taxExemptInterest: number
29
+ /** Line 2b — taxable interest. */
30
+ taxableInterest: number
31
+ /** Line 3a — qualified dividends. */
32
+ qualifiedDividends: number
33
+ /** Line 3b — ordinary dividends (includes 3a). */
34
+ ordinaryDividends: number
35
+ /** Line 4b — taxable IRA distributions. */
36
+ iraDistributions: number
37
+ /** Line 5b — taxable pensions and annuities. */
38
+ pensionsAndAnnuities: number
39
+ /** Line 6a — total Social Security benefits (before the taxable portion). */
40
+ socialSecurityBenefits: number
41
+ /** Line 7 — capital gain or (loss); negative allowed. */
42
+ capitalGain: number
43
+ /** Line 11 — adjusted gross income. */
44
+ agi: number
45
+ }
46
+
47
+ export type TenFortyResult = { ok: true; plan: Plan; review: ImportReviewItem[] } | { ok: false; message: string }
48
+
49
+ /** Combined interest+dividend yield used to size the estimated taxable account. */
50
+ export const ASSUMED_TAXABLE_YIELD_PCT = 2.5
51
+
52
+ /** True for a real calendar date in YYYY-MM-DD (rejects 2026-13-40, 2026-02-30, …). */
53
+ function isValidIsoDate(s: string): boolean {
54
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s)
55
+ if (!m) return false
56
+ const y = Number(m[1])
57
+ const mo = Number(m[2])
58
+ const d = Number(m[3])
59
+ const date = new Date(y, mo - 1, d)
60
+ return date.getFullYear() === y && date.getMonth() === mo - 1 && date.getDate() === d
61
+ }
62
+
63
+ function badDollar(v: number): boolean {
64
+ return !Number.isFinite(v) || Math.abs(v) > MAX_REASONABLE_DOLLARS
65
+ }
66
+
67
+ function ageAt(dobIso: string, now: Date): number {
68
+ const dob = new Date(`${dobIso}T00:00:00`)
69
+ let age = now.getFullYear() - dob.getFullYear()
70
+ const beforeBirthday =
71
+ now.getMonth() < dob.getMonth() || (now.getMonth() === dob.getMonth() && now.getDate() < dob.getDate())
72
+ if (beforeBirthday) age--
73
+ return age
74
+ }
75
+
76
+ export function seedPlanFromTenForty(
77
+ inputs: TenFortyInputs,
78
+ newId: () => string = () => crypto.randomUUID(),
79
+ now: () => Date = () => new Date(),
80
+ ): TenFortyResult {
81
+ if (!isValidIsoDate(inputs.primaryDob)) {
82
+ return { ok: false, message: 'Enter your date of birth as a real calendar date (YYYY-MM-DD).' }
83
+ }
84
+ if (inputs.filingStatus === 'marriedFilingJointly' && (!inputs.spouseDob || !isValidIsoDate(inputs.spouseDob))) {
85
+ return { ok: false, message: "Married filing jointly needs your spouse's date of birth too (YYYY-MM-DD)." }
86
+ }
87
+ if (!/^[A-Za-z]{2}$/.test(inputs.state)) return { ok: false, message: 'Enter your state as its two-letter code.' }
88
+ const dollarFields: Array<[keyof TenFortyInputs, number]> = [
89
+ ['wages', inputs.wages],
90
+ ['taxExemptInterest', inputs.taxExemptInterest],
91
+ ['taxableInterest', inputs.taxableInterest],
92
+ ['qualifiedDividends', inputs.qualifiedDividends],
93
+ ['ordinaryDividends', inputs.ordinaryDividends],
94
+ ['iraDistributions', inputs.iraDistributions],
95
+ ['pensionsAndAnnuities', inputs.pensionsAndAnnuities],
96
+ ['socialSecurityBenefits', inputs.socialSecurityBenefits],
97
+ ['capitalGain', inputs.capitalGain],
98
+ ['agi', inputs.agi],
99
+ ]
100
+ for (const [field, value] of dollarFields) {
101
+ if (badDollar(value)) return { ok: false, message: `The ${field} value is not a usable dollar amount.` }
102
+ if (field !== 'capitalGain' && value < 0) {
103
+ return { ok: false, message: `The ${field} value cannot be negative (only capital gain can be a loss).` }
104
+ }
105
+ }
106
+
107
+ const review: ImportReviewItem[] = []
108
+ const plan = createEmptyPlan({ newId, now, name: 'Seeded from your 1040' })
109
+ const today = now()
110
+
111
+ // --- Household / filing context -----------------------------------------
112
+ plan.household.filingStatus = inputs.filingStatus
113
+ plan.household.state = inputs.state.toUpperCase()
114
+ const primary = plan.household.people[0]!
115
+ primary.dob = inputs.primaryDob
116
+ if (inputs.filingStatus === 'marriedFilingJointly') {
117
+ plan.household.people.push({
118
+ id: newId(),
119
+ name: 'Spouse',
120
+ dob: inputs.spouseDob!,
121
+ sex: 'average',
122
+ retirementAge: 65,
123
+ longevity: { planningAge: 95, source: 'manual' },
124
+ })
125
+ }
126
+ review.push({
127
+ status: 'mapped',
128
+ source: 'Filing status & state (1040 header)',
129
+ detail: `Filing ${inputs.filingStatus === 'single' ? 'single' : 'jointly'}, resident of ${plan.household.state}.`,
130
+ })
131
+
132
+ // --- Line 1a: wages -------------------------------------------------------
133
+ if (inputs.wages > 0) {
134
+ plan.incomes.push({ type: 'wages', id: newId(), personId: primary.id, annualGross: inputs.wages, endAge: null, realGrowthPct: 0 })
135
+ review.push({
136
+ status: inputs.filingStatus === 'marriedFilingJointly' ? 'defaulted' : 'mapped',
137
+ source: 'From your 1040 — line 1a (wages)',
138
+ detail:
139
+ `$${inputs.wages.toLocaleString('en-US')} /yr of wages until retirement` +
140
+ (inputs.filingStatus === 'marriedFilingJointly'
141
+ ? ', all placed on you — split it between spouses on the Income screen so retirement dates apply per person.'
142
+ : '.'),
143
+ })
144
+ }
145
+
146
+ // --- Lines 2b/3a/3b: estimated taxable account ----------------------------
147
+ const investmentIncome = inputs.taxableInterest + inputs.ordinaryDividends
148
+ if (investmentIncome > 0) {
149
+ // Clamp: sub-dollar investment income must not round the denominator to 0.
150
+ const estimatedBalance = Math.max(1, Math.round(investmentIncome / (ASSUMED_TAXABLE_YIELD_PCT / 100)))
151
+ const qualifiedRatio = inputs.ordinaryDividends > 0 ? Math.min(1, inputs.qualifiedDividends / inputs.ordinaryDividends) : 0
152
+ plan.accounts.push({
153
+ id: newId(),
154
+ type: 'taxable',
155
+ name: 'Brokerage (estimated from your 1040)',
156
+ ownerPersonId: null,
157
+ annualReturnPct: null,
158
+ balance: estimatedBalance,
159
+ costBasis: estimatedBalance,
160
+ interestYieldPct: Math.round((inputs.taxableInterest / estimatedBalance) * 10000) / 100,
161
+ dividendYieldPct: Math.round((inputs.ordinaryDividends / estimatedBalance) * 10000) / 100,
162
+ qualifiedRatio: Math.round(qualifiedRatio * 100) / 100,
163
+ reinvestDividends: true,
164
+ annualContribution: 0,
165
+ })
166
+ review.push({
167
+ status: 'defaulted',
168
+ source: 'From your 1040 — lines 2b/3a/3b (interest & dividends)',
169
+ detail:
170
+ `Your $${investmentIncome.toLocaleString('en-US')} of interest + dividends implies roughly a ` +
171
+ `$${estimatedBalance.toLocaleString('en-US')} taxable balance at a ${ASSUMED_TAXABLE_YIELD_PCT}% yield — an estimate to replace ` +
172
+ 'with the real balance and cost basis on the Accounts screen. The qualified-dividend share was kept.',
173
+ })
174
+ }
175
+
176
+ // --- Line 4b: IRA distributions -------------------------------------------
177
+ if (inputs.iraDistributions > 0) {
178
+ review.push({
179
+ status: 'unmapped',
180
+ source: 'From your 1040 — line 4b (IRA distributions)',
181
+ detail:
182
+ `You took $${inputs.iraDistributions.toLocaleString('en-US')} from IRAs — RetireGolden models withdrawals from account balances, ` +
183
+ 'so add your traditional IRA/401(k) accounts with their balances on the Accounts screen (a broker CSV can fill them).',
184
+ })
185
+ }
186
+
187
+ // --- Line 5b: pensions ------------------------------------------------------
188
+ if (inputs.pensionsAndAnnuities > 0) {
189
+ const startAge = Math.min(80, Math.max(40, ageAt(inputs.primaryDob, today)))
190
+ plan.accounts.push({
191
+ id: newId(),
192
+ type: 'pension',
193
+ name: 'Pension (from your 1040)',
194
+ ownerPersonId: primary.id,
195
+ annualReturnPct: null,
196
+ source: 'private',
197
+ startAge,
198
+ monthlyAmount: Math.round(inputs.pensionsAndAnnuities / 12),
199
+ colaPct: 0,
200
+ survivorPct: 50,
201
+ })
202
+ review.push({
203
+ status: 'defaulted',
204
+ source: 'From your 1040 — line 5b (pensions & annuities)',
205
+ detail:
206
+ `A pension paying $${Math.round(inputs.pensionsAndAnnuities / 12).toLocaleString('en-US')} /mo starting now, with no COLA and a 50% ` +
207
+ 'survivor benefit — check the COLA, survivor percentage, and public/private split on the Accounts screen.',
208
+ })
209
+ }
210
+
211
+ // --- Line 6a: Social Security ------------------------------------------------
212
+ if (inputs.socialSecurityBenefits > 0) {
213
+ const monthly = Math.round(inputs.socialSecurityBenefits / 12)
214
+ plan.incomes.push({
215
+ type: 'socialSecurity',
216
+ id: newId(),
217
+ personId: primary.id,
218
+ piaMonthly: monthly,
219
+ earnings: null,
220
+ claimAge: { years: 67, months: 0 },
221
+ })
222
+ review.push({
223
+ status: 'defaulted',
224
+ source: 'From your 1040 — line 6a (Social Security benefits)',
225
+ detail:
226
+ `$${monthly.toLocaleString('en-US')} /mo entered as the benefit basis, assuming a claim at 67 — if you claimed earlier or later, ` +
227
+ 'set the real claim age (or import your SSA statement) on the Social Security screen.' +
228
+ (inputs.filingStatus === 'marriedFilingJointly'
229
+ ? ' Line 6a is the joint total, but it was all placed on you — if both spouses receive benefits, split it into one ' +
230
+ 'stream per person on the Social Security screen (survivor-year benefits and benefit end dates depend on whose record is whose).'
231
+ : ''),
232
+ })
233
+ } else {
234
+ review.push({
235
+ status: 'unmapped',
236
+ source: 'Social Security',
237
+ detail: 'No benefits on the return — set up future Social Security on its screen (your SSA statement XML imports there).',
238
+ })
239
+ }
240
+
241
+ // --- Line 7: capital gains -----------------------------------------------------
242
+ if (inputs.capitalGain !== 0) {
243
+ review.push({
244
+ status: 'unmapped',
245
+ source: 'From your 1040 — line 7 (capital gain/loss)',
246
+ detail:
247
+ inputs.capitalGain > 0
248
+ ? 'Last year\'s realized gains are not projected forward — RetireGolden realizes gains from actual modeled sales and yields instead.'
249
+ : `A $${Math.abs(inputs.capitalGain).toLocaleString('en-US')} loss may leave a carryforward — enter any remaining capital-loss carryforward on the Household screen.`,
250
+ })
251
+ }
252
+
253
+ // --- Line 11 (+2a): MAGI for the IRMAA lookback ---------------------------------
254
+ const magi = inputs.agi + inputs.taxExemptInterest
255
+ plan.assumptions.recentAnnualMagi = magi
256
+ review.push({
257
+ status: 'mapped',
258
+ source: 'From your 1040 — lines 11 + 2a (AGI + tax-exempt interest)',
259
+ detail: `Recent MAGI of $${magi.toLocaleString('en-US')} recorded — Medicare IRMAA looks back two years, so early projection years use it.`,
260
+ })
261
+
262
+ // --- What a 1040 cannot tell us ---------------------------------------------------
263
+ review.push({
264
+ status: 'unmapped',
265
+ source: 'Spending, balances & retirement dates',
266
+ detail:
267
+ 'A tax return shows income, not spending or savings — set baseline spending on the Spending screen, account balances on the Accounts screen, and retirement ages on the Household screen.',
268
+ })
269
+
270
+ const parsed = parsePlan(plan)
271
+ if (!parsed.ok) {
272
+ return { ok: false, message: `The seeded plan failed validation: ${parsed.issues.join('; ')}` }
273
+ }
274
+ return { ok: true, plan: parsed.plan, review }
275
+ }