@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,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
|
+
}
|