2026-05-09 21:53:59 +00:00
|
|
|
import { useQuery } from '@tanstack/react-query';
|
frontend: What-If page with fan chart driven by /simulate
New /what-if route. Sticky form on the left (jurisdiction, strategy,
glide, NW seed, spending, savings, horizon, optional floor for
vpw_floor, MC paths) submits to POST /simulate; results panel renders
summary stats + the new FanChart.
FanChart component layers seven series:
- p10 invisible baseline (line, transparent)
- p10→p25 stacked area (low opacity)
- p25→p75 stacked area (IQR, mid opacity)
- p75→p90 stacked area (low opacity)
- p50 solid median line (drawn last, prominent)
- p10 + p90 dashed lines on top of the bands
Stacking deltas keeps the band fills clean — plotting raw quantiles
each as their own area would overlap badly. Reusable by scenario
detail in the next chunk (same ProjectionPoint[] shape).
5 tests pass (was 2). 470 KB gzipped (ECharts).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 22:08:00 +00:00
|
|
|
import { NavLink, Route, Routes, Link } from 'react-router-dom';
|
2026-05-09 21:53:59 +00:00
|
|
|
|
|
|
|
|
import { api } from '@/api/client';
|
2026-05-10 17:49:05 +00:00
|
|
|
import { Navigate } from 'react-router-dom';
|
|
|
|
|
|
2026-05-09 22:14:51 +00:00
|
|
|
import { Compare } from '@/pages/Compare';
|
2026-05-09 21:53:59 +00:00
|
|
|
import { Dashboard } from '@/pages/Dashboard';
|
fire-planner: ProjectionLab parity Wave 1 — tabbed shell, year stats, goals,
income streams, Sankey cashflow, progress overlay, settings sub-pages
Wave 1 (9 features across 4 streams):
Stream A — dashboard skeleton
1.A.1 ScenarioShell with top tabs (Plan/Cash Flow/Tax Analytics/Compare/
Reports/Estate/Settings) + left Sidebar with Plans switcher.
1.A.2 GET /scenarios/{id}/year-stats?year=N returning per-year metrics
(NW, Δ NW, taxable income, taxes, eff. rate, spending, contribs,
investment growth). YearScrubber + YearStatsPanel render the
right-hand sidebar; URL ?year= preserves selection.
1.A.3 FanChart gains optional `milestones` prop (lib/milestone.ts maps
life_event.kind → emoji) + selectedYear marker line.
Stream B — goals + progress
1.B.1 New goals_eval module: target_nw_by_year / never_run_out /
target_real_income probability evaluation. Wired into POST
/simulate (exact, per-path) and GET /scenarios/{id}/projection
(approximated from persisted fan via percentile interpolation).
GoalsSection renders pass/fail badges.
1.B.2 GET /scenarios/{id}/progress overlays AccountSnapshot totals on
the projection fan; ProgressPage shows variance side-panel.
Stream C — income + cashflow
1.C.1 New IncomeStream model + alembic 0003 + CRUD endpoints. Engine
aggregates streams into per-year inflows + taxable arrays;
income tax routes through the jurisdiction tax engine.
IncomeStreamsSection on Plan tab.
1.C.2 GET /scenarios/{id}/cashflow?year=N returns sources/sinks for
an ECharts Sankey (sums conserve). CashflowTab body.
Stream D — settings
1.D.1 SettingsTab + sub-nav (Milestones/Rates/Dividends/Bonds/Tax/
Metrics/Other/Notes); placeholder cards for unbuilt sub-pages.
1.D.2 LifeEventsSection relocated to /scenarios/:id/settings.
1.D.3 RatesSettings (Fixed/Historical/Advanced segmented + per-asset
cards). SimulateRequest gains rates_mode, inflation_pct,
stocks/bonds growth + dividend, stocks_allocation. New
build_fixed_paths() in simulator. Real-return arithmetic
verified against (1+g+d)/(1+i)−1 ≈ 5.4%.
1.D.4 NotesSettings — markdown textarea, save-on-blur, stored in
scenario.config_json.notes.
Backend: 238 pytest pass (+19 new), mypy + ruff clean.
Frontend: typecheck + 7 unit tests + production build clean.
Roadmap for Wave 2-N is documented in the implementation plan.
2026-05-10 12:49:44 +00:00
|
|
|
import { CashflowTab } from '@/pages/CashflowTab';
|
|
|
|
|
import { MilestonesSettings } from '@/pages/MilestonesSettings';
|
|
|
|
|
import { NotesSettings } from '@/pages/NotesSettings';
|
|
|
|
|
import { PlaceholderTab } from '@/pages/PlaceholderTab';
|
|
|
|
|
import { ProgressPage } from '@/pages/ProgressPage';
|
|
|
|
|
import { RatesSettings } from '@/pages/RatesSettings';
|
2026-05-09 22:09:43 +00:00
|
|
|
import { ScenarioDetail } from '@/pages/ScenarioDetail';
|
2026-05-09 22:20:21 +00:00
|
|
|
import { ScenarioEdit } from '@/pages/ScenarioEdit';
|
2026-05-09 22:11:54 +00:00
|
|
|
import { ScenarioNew } from '@/pages/ScenarioNew';
|
fire-planner: ProjectionLab parity Wave 1 — tabbed shell, year stats, goals,
income streams, Sankey cashflow, progress overlay, settings sub-pages
Wave 1 (9 features across 4 streams):
Stream A — dashboard skeleton
1.A.1 ScenarioShell with top tabs (Plan/Cash Flow/Tax Analytics/Compare/
Reports/Estate/Settings) + left Sidebar with Plans switcher.
1.A.2 GET /scenarios/{id}/year-stats?year=N returning per-year metrics
(NW, Δ NW, taxable income, taxes, eff. rate, spending, contribs,
investment growth). YearScrubber + YearStatsPanel render the
right-hand sidebar; URL ?year= preserves selection.
1.A.3 FanChart gains optional `milestones` prop (lib/milestone.ts maps
life_event.kind → emoji) + selectedYear marker line.
Stream B — goals + progress
1.B.1 New goals_eval module: target_nw_by_year / never_run_out /
target_real_income probability evaluation. Wired into POST
/simulate (exact, per-path) and GET /scenarios/{id}/projection
(approximated from persisted fan via percentile interpolation).
GoalsSection renders pass/fail badges.
1.B.2 GET /scenarios/{id}/progress overlays AccountSnapshot totals on
the projection fan; ProgressPage shows variance side-panel.
Stream C — income + cashflow
1.C.1 New IncomeStream model + alembic 0003 + CRUD endpoints. Engine
aggregates streams into per-year inflows + taxable arrays;
income tax routes through the jurisdiction tax engine.
IncomeStreamsSection on Plan tab.
1.C.2 GET /scenarios/{id}/cashflow?year=N returns sources/sinks for
an ECharts Sankey (sums conserve). CashflowTab body.
Stream D — settings
1.D.1 SettingsTab + sub-nav (Milestones/Rates/Dividends/Bonds/Tax/
Metrics/Other/Notes); placeholder cards for unbuilt sub-pages.
1.D.2 LifeEventsSection relocated to /scenarios/:id/settings.
1.D.3 RatesSettings (Fixed/Historical/Advanced segmented + per-asset
cards). SimulateRequest gains rates_mode, inflation_pct,
stocks/bonds growth + dividend, stocks_allocation. New
build_fixed_paths() in simulator. Real-return arithmetic
verified against (1+g+d)/(1+i)−1 ≈ 5.4%.
1.D.4 NotesSettings — markdown textarea, save-on-blur, stored in
scenario.config_json.notes.
Backend: 238 pytest pass (+19 new), mypy + ruff clean.
Frontend: typecheck + 7 unit tests + production build clean.
Roadmap for Wave 2-N is documented in the implementation plan.
2026-05-10 12:49:44 +00:00
|
|
|
import { ScenarioShell } from '@/pages/ScenarioShell';
|
2026-05-09 22:09:43 +00:00
|
|
|
import { Scenarios } from '@/pages/Scenarios';
|
fire-planner: ProjectionLab parity Wave 1 — tabbed shell, year stats, goals,
income streams, Sankey cashflow, progress overlay, settings sub-pages
Wave 1 (9 features across 4 streams):
Stream A — dashboard skeleton
1.A.1 ScenarioShell with top tabs (Plan/Cash Flow/Tax Analytics/Compare/
Reports/Estate/Settings) + left Sidebar with Plans switcher.
1.A.2 GET /scenarios/{id}/year-stats?year=N returning per-year metrics
(NW, Δ NW, taxable income, taxes, eff. rate, spending, contribs,
investment growth). YearScrubber + YearStatsPanel render the
right-hand sidebar; URL ?year= preserves selection.
1.A.3 FanChart gains optional `milestones` prop (lib/milestone.ts maps
life_event.kind → emoji) + selectedYear marker line.
Stream B — goals + progress
1.B.1 New goals_eval module: target_nw_by_year / never_run_out /
target_real_income probability evaluation. Wired into POST
/simulate (exact, per-path) and GET /scenarios/{id}/projection
(approximated from persisted fan via percentile interpolation).
GoalsSection renders pass/fail badges.
1.B.2 GET /scenarios/{id}/progress overlays AccountSnapshot totals on
the projection fan; ProgressPage shows variance side-panel.
Stream C — income + cashflow
1.C.1 New IncomeStream model + alembic 0003 + CRUD endpoints. Engine
aggregates streams into per-year inflows + taxable arrays;
income tax routes through the jurisdiction tax engine.
IncomeStreamsSection on Plan tab.
1.C.2 GET /scenarios/{id}/cashflow?year=N returns sources/sinks for
an ECharts Sankey (sums conserve). CashflowTab body.
Stream D — settings
1.D.1 SettingsTab + sub-nav (Milestones/Rates/Dividends/Bonds/Tax/
Metrics/Other/Notes); placeholder cards for unbuilt sub-pages.
1.D.2 LifeEventsSection relocated to /scenarios/:id/settings.
1.D.3 RatesSettings (Fixed/Historical/Advanced segmented + per-asset
cards). SimulateRequest gains rates_mode, inflation_pct,
stocks/bonds growth + dividend, stocks_allocation. New
build_fixed_paths() in simulator. Real-return arithmetic
verified against (1+g+d)/(1+i)−1 ≈ 5.4%.
1.D.4 NotesSettings — markdown textarea, save-on-blur, stored in
scenario.config_json.notes.
Backend: 238 pytest pass (+19 new), mypy + ruff clean.
Frontend: typecheck + 7 unit tests + production build clean.
Roadmap for Wave 2-N is documented in the implementation plan.
2026-05-10 12:49:44 +00:00
|
|
|
import { SettingsTab } from '@/pages/SettingsTab';
|
frontend: What-If page with fan chart driven by /simulate
New /what-if route. Sticky form on the left (jurisdiction, strategy,
glide, NW seed, spending, savings, horizon, optional floor for
vpw_floor, MC paths) submits to POST /simulate; results panel renders
summary stats + the new FanChart.
FanChart component layers seven series:
- p10 invisible baseline (line, transparent)
- p10→p25 stacked area (low opacity)
- p25→p75 stacked area (IQR, mid opacity)
- p75→p90 stacked area (low opacity)
- p50 solid median line (drawn last, prominent)
- p10 + p90 dashed lines on top of the bands
Stacking deltas keeps the band fills clean — plotting raw quantiles
each as their own area would overlap badly. Reusable by scenario
detail in the next chunk (same ProjectionPoint[] shape).
5 tests pass (was 2). 470 KB gzipped (ECharts).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 22:08:00 +00:00
|
|
|
import { WhatIf } from '@/pages/WhatIf';
|
2026-05-09 21:53:59 +00:00
|
|
|
|
|
|
|
|
export function App() {
|
|
|
|
|
const health = useQuery({ queryKey: ['health'], queryFn: api.health });
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="min-h-screen flex flex-col">
|
|
|
|
|
<header className="border-b border-slate-200 bg-white">
|
|
|
|
|
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
frontend: What-If page with fan chart driven by /simulate
New /what-if route. Sticky form on the left (jurisdiction, strategy,
glide, NW seed, spending, savings, horizon, optional floor for
vpw_floor, MC paths) submits to POST /simulate; results panel renders
summary stats + the new FanChart.
FanChart component layers seven series:
- p10 invisible baseline (line, transparent)
- p10→p25 stacked area (low opacity)
- p25→p75 stacked area (IQR, mid opacity)
- p75→p90 stacked area (low opacity)
- p50 solid median line (drawn last, prominent)
- p10 + p90 dashed lines on top of the bands
Stacking deltas keeps the band fills clean — plotting raw quantiles
each as their own area would overlap badly. Reusable by scenario
detail in the next chunk (same ProjectionPoint[] shape).
5 tests pass (was 2). 470 KB gzipped (ECharts).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 22:08:00 +00:00
|
|
|
<div className="flex items-center gap-6">
|
|
|
|
|
<Link to="/" className="text-xl font-semibold tracking-tight">
|
|
|
|
|
fire-planner
|
|
|
|
|
</Link>
|
|
|
|
|
<nav className="flex gap-4 text-sm">
|
|
|
|
|
<NavTab to="/">Dashboard</NavTab>
|
2026-05-09 22:09:43 +00:00
|
|
|
<NavTab to="/scenarios">Scenarios</NavTab>
|
frontend: What-If page with fan chart driven by /simulate
New /what-if route. Sticky form on the left (jurisdiction, strategy,
glide, NW seed, spending, savings, horizon, optional floor for
vpw_floor, MC paths) submits to POST /simulate; results panel renders
summary stats + the new FanChart.
FanChart component layers seven series:
- p10 invisible baseline (line, transparent)
- p10→p25 stacked area (low opacity)
- p25→p75 stacked area (IQR, mid opacity)
- p75→p90 stacked area (low opacity)
- p50 solid median line (drawn last, prominent)
- p10 + p90 dashed lines on top of the bands
Stacking deltas keeps the band fills clean — plotting raw quantiles
each as their own area would overlap badly. Reusable by scenario
detail in the next chunk (same ProjectionPoint[] shape).
5 tests pass (was 2). 470 KB gzipped (ECharts).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 22:08:00 +00:00
|
|
|
<NavTab to="/what-if">What if</NavTab>
|
|
|
|
|
</nav>
|
|
|
|
|
</div>
|
2026-05-09 21:53:59 +00:00
|
|
|
<div className="text-xs text-slate-500">
|
|
|
|
|
api:{' '}
|
|
|
|
|
<span className={health.data ? 'text-emerald-600' : 'text-amber-600'}>
|
|
|
|
|
{health.isLoading
|
|
|
|
|
? '…'
|
|
|
|
|
: health.data
|
|
|
|
|
? `ok (queue=${health.data.queue_depth})`
|
|
|
|
|
: 'unreachable'}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
<main className="flex-1 max-w-7xl w-full mx-auto px-6 py-8">
|
|
|
|
|
<Routes>
|
|
|
|
|
<Route path="/" element={<Dashboard />} />
|
2026-05-09 22:09:43 +00:00
|
|
|
<Route path="/scenarios" element={<Scenarios />} />
|
2026-05-09 22:11:54 +00:00
|
|
|
<Route path="/scenarios/new" element={<ScenarioNew />} />
|
2026-05-09 22:20:21 +00:00
|
|
|
<Route path="/scenarios/:id/edit" element={<ScenarioEdit />} />
|
fire-planner: ProjectionLab parity Wave 1 — tabbed shell, year stats, goals,
income streams, Sankey cashflow, progress overlay, settings sub-pages
Wave 1 (9 features across 4 streams):
Stream A — dashboard skeleton
1.A.1 ScenarioShell with top tabs (Plan/Cash Flow/Tax Analytics/Compare/
Reports/Estate/Settings) + left Sidebar with Plans switcher.
1.A.2 GET /scenarios/{id}/year-stats?year=N returning per-year metrics
(NW, Δ NW, taxable income, taxes, eff. rate, spending, contribs,
investment growth). YearScrubber + YearStatsPanel render the
right-hand sidebar; URL ?year= preserves selection.
1.A.3 FanChart gains optional `milestones` prop (lib/milestone.ts maps
life_event.kind → emoji) + selectedYear marker line.
Stream B — goals + progress
1.B.1 New goals_eval module: target_nw_by_year / never_run_out /
target_real_income probability evaluation. Wired into POST
/simulate (exact, per-path) and GET /scenarios/{id}/projection
(approximated from persisted fan via percentile interpolation).
GoalsSection renders pass/fail badges.
1.B.2 GET /scenarios/{id}/progress overlays AccountSnapshot totals on
the projection fan; ProgressPage shows variance side-panel.
Stream C — income + cashflow
1.C.1 New IncomeStream model + alembic 0003 + CRUD endpoints. Engine
aggregates streams into per-year inflows + taxable arrays;
income tax routes through the jurisdiction tax engine.
IncomeStreamsSection on Plan tab.
1.C.2 GET /scenarios/{id}/cashflow?year=N returns sources/sinks for
an ECharts Sankey (sums conserve). CashflowTab body.
Stream D — settings
1.D.1 SettingsTab + sub-nav (Milestones/Rates/Dividends/Bonds/Tax/
Metrics/Other/Notes); placeholder cards for unbuilt sub-pages.
1.D.2 LifeEventsSection relocated to /scenarios/:id/settings.
1.D.3 RatesSettings (Fixed/Historical/Advanced segmented + per-asset
cards). SimulateRequest gains rates_mode, inflation_pct,
stocks/bonds growth + dividend, stocks_allocation. New
build_fixed_paths() in simulator. Real-return arithmetic
verified against (1+g+d)/(1+i)−1 ≈ 5.4%.
1.D.4 NotesSettings — markdown textarea, save-on-blur, stored in
scenario.config_json.notes.
Backend: 238 pytest pass (+19 new), mypy + ruff clean.
Frontend: typecheck + 7 unit tests + production build clean.
Roadmap for Wave 2-N is documented in the implementation plan.
2026-05-10 12:49:44 +00:00
|
|
|
<Route path="/scenarios/:id" element={<ScenarioShell />}>
|
|
|
|
|
<Route index element={<ScenarioDetail />} />
|
2026-05-10 17:49:05 +00:00
|
|
|
<Route path="progress" element={<ProgressPage />} />
|
fire-planner: ProjectionLab parity Wave 1 — tabbed shell, year stats, goals,
income streams, Sankey cashflow, progress overlay, settings sub-pages
Wave 1 (9 features across 4 streams):
Stream A — dashboard skeleton
1.A.1 ScenarioShell with top tabs (Plan/Cash Flow/Tax Analytics/Compare/
Reports/Estate/Settings) + left Sidebar with Plans switcher.
1.A.2 GET /scenarios/{id}/year-stats?year=N returning per-year metrics
(NW, Δ NW, taxable income, taxes, eff. rate, spending, contribs,
investment growth). YearScrubber + YearStatsPanel render the
right-hand sidebar; URL ?year= preserves selection.
1.A.3 FanChart gains optional `milestones` prop (lib/milestone.ts maps
life_event.kind → emoji) + selectedYear marker line.
Stream B — goals + progress
1.B.1 New goals_eval module: target_nw_by_year / never_run_out /
target_real_income probability evaluation. Wired into POST
/simulate (exact, per-path) and GET /scenarios/{id}/projection
(approximated from persisted fan via percentile interpolation).
GoalsSection renders pass/fail badges.
1.B.2 GET /scenarios/{id}/progress overlays AccountSnapshot totals on
the projection fan; ProgressPage shows variance side-panel.
Stream C — income + cashflow
1.C.1 New IncomeStream model + alembic 0003 + CRUD endpoints. Engine
aggregates streams into per-year inflows + taxable arrays;
income tax routes through the jurisdiction tax engine.
IncomeStreamsSection on Plan tab.
1.C.2 GET /scenarios/{id}/cashflow?year=N returns sources/sinks for
an ECharts Sankey (sums conserve). CashflowTab body.
Stream D — settings
1.D.1 SettingsTab + sub-nav (Milestones/Rates/Dividends/Bonds/Tax/
Metrics/Other/Notes); placeholder cards for unbuilt sub-pages.
1.D.2 LifeEventsSection relocated to /scenarios/:id/settings.
1.D.3 RatesSettings (Fixed/Historical/Advanced segmented + per-asset
cards). SimulateRequest gains rates_mode, inflation_pct,
stocks/bonds growth + dividend, stocks_allocation. New
build_fixed_paths() in simulator. Real-return arithmetic
verified against (1+g+d)/(1+i)−1 ≈ 5.4%.
1.D.4 NotesSettings — markdown textarea, save-on-blur, stored in
scenario.config_json.notes.
Backend: 238 pytest pass (+19 new), mypy + ruff clean.
Frontend: typecheck + 7 unit tests + production build clean.
Roadmap for Wave 2-N is documented in the implementation plan.
2026-05-10 12:49:44 +00:00
|
|
|
<Route path="cash-flow" element={<CashflowTab />} />
|
|
|
|
|
<Route
|
|
|
|
|
path="tax-analytics"
|
|
|
|
|
element={<PlaceholderTab feature="Tax Analytics" wave={2} />}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="compare"
|
|
|
|
|
element={<PlaceholderTab feature="Side-by-side Compare" wave={2} />}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="reports"
|
|
|
|
|
element={<PlaceholderTab feature="Reports / PDF export" wave={2} />}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="estate"
|
|
|
|
|
element={<PlaceholderTab feature="Estate planning" wave={2} />}
|
|
|
|
|
/>
|
|
|
|
|
<Route path="settings" element={<SettingsTab />}>
|
2026-05-10 17:49:05 +00:00
|
|
|
<Route index element={<Navigate to="milestones" replace />} />
|
2026-05-10 17:17:55 +00:00
|
|
|
<Route path="milestones" element={<MilestonesSettings />} />
|
fire-planner: ProjectionLab parity Wave 1 — tabbed shell, year stats, goals,
income streams, Sankey cashflow, progress overlay, settings sub-pages
Wave 1 (9 features across 4 streams):
Stream A — dashboard skeleton
1.A.1 ScenarioShell with top tabs (Plan/Cash Flow/Tax Analytics/Compare/
Reports/Estate/Settings) + left Sidebar with Plans switcher.
1.A.2 GET /scenarios/{id}/year-stats?year=N returning per-year metrics
(NW, Δ NW, taxable income, taxes, eff. rate, spending, contribs,
investment growth). YearScrubber + YearStatsPanel render the
right-hand sidebar; URL ?year= preserves selection.
1.A.3 FanChart gains optional `milestones` prop (lib/milestone.ts maps
life_event.kind → emoji) + selectedYear marker line.
Stream B — goals + progress
1.B.1 New goals_eval module: target_nw_by_year / never_run_out /
target_real_income probability evaluation. Wired into POST
/simulate (exact, per-path) and GET /scenarios/{id}/projection
(approximated from persisted fan via percentile interpolation).
GoalsSection renders pass/fail badges.
1.B.2 GET /scenarios/{id}/progress overlays AccountSnapshot totals on
the projection fan; ProgressPage shows variance side-panel.
Stream C — income + cashflow
1.C.1 New IncomeStream model + alembic 0003 + CRUD endpoints. Engine
aggregates streams into per-year inflows + taxable arrays;
income tax routes through the jurisdiction tax engine.
IncomeStreamsSection on Plan tab.
1.C.2 GET /scenarios/{id}/cashflow?year=N returns sources/sinks for
an ECharts Sankey (sums conserve). CashflowTab body.
Stream D — settings
1.D.1 SettingsTab + sub-nav (Milestones/Rates/Dividends/Bonds/Tax/
Metrics/Other/Notes); placeholder cards for unbuilt sub-pages.
1.D.2 LifeEventsSection relocated to /scenarios/:id/settings.
1.D.3 RatesSettings (Fixed/Historical/Advanced segmented + per-asset
cards). SimulateRequest gains rates_mode, inflation_pct,
stocks/bonds growth + dividend, stocks_allocation. New
build_fixed_paths() in simulator. Real-return arithmetic
verified against (1+g+d)/(1+i)−1 ≈ 5.4%.
1.D.4 NotesSettings — markdown textarea, save-on-blur, stored in
scenario.config_json.notes.
Backend: 238 pytest pass (+19 new), mypy + ruff clean.
Frontend: typecheck + 7 unit tests + production build clean.
Roadmap for Wave 2-N is documented in the implementation plan.
2026-05-10 12:49:44 +00:00
|
|
|
<Route path="rates" element={<RatesSettings />} />
|
|
|
|
|
<Route
|
|
|
|
|
path="dividends"
|
|
|
|
|
element={<PlaceholderTab feature="Dividends override matrix" wave={2} />}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="bonds"
|
|
|
|
|
element={<PlaceholderTab feature="Bond allocation editor" wave={2} />}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="tax"
|
|
|
|
|
element={<PlaceholderTab feature="Tax mode toggle + per-jurisdiction overrides" wave={2} />}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="metrics"
|
|
|
|
|
element={<PlaceholderTab feature="Right-sidebar metric picker" wave={2} />}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="other"
|
|
|
|
|
element={<PlaceholderTab feature="Currency / horizon / seed defaults" wave={2} />}
|
|
|
|
|
/>
|
|
|
|
|
<Route path="notes" element={<NotesSettings />} />
|
|
|
|
|
</Route>
|
|
|
|
|
</Route>
|
2026-05-09 22:14:51 +00:00
|
|
|
<Route path="/compare" element={<Compare />} />
|
frontend: What-If page with fan chart driven by /simulate
New /what-if route. Sticky form on the left (jurisdiction, strategy,
glide, NW seed, spending, savings, horizon, optional floor for
vpw_floor, MC paths) submits to POST /simulate; results panel renders
summary stats + the new FanChart.
FanChart component layers seven series:
- p10 invisible baseline (line, transparent)
- p10→p25 stacked area (low opacity)
- p25→p75 stacked area (IQR, mid opacity)
- p75→p90 stacked area (low opacity)
- p50 solid median line (drawn last, prominent)
- p10 + p90 dashed lines on top of the bands
Stacking deltas keeps the band fills clean — plotting raw quantiles
each as their own area would overlap badly. Reusable by scenario
detail in the next chunk (same ProjectionPoint[] shape).
5 tests pass (was 2). 470 KB gzipped (ECharts).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 22:08:00 +00:00
|
|
|
<Route path="/what-if" element={<WhatIf />} />
|
2026-05-09 21:53:59 +00:00
|
|
|
</Routes>
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
frontend: What-If page with fan chart driven by /simulate
New /what-if route. Sticky form on the left (jurisdiction, strategy,
glide, NW seed, spending, savings, horizon, optional floor for
vpw_floor, MC paths) submits to POST /simulate; results panel renders
summary stats + the new FanChart.
FanChart component layers seven series:
- p10 invisible baseline (line, transparent)
- p10→p25 stacked area (low opacity)
- p25→p75 stacked area (IQR, mid opacity)
- p75→p90 stacked area (low opacity)
- p50 solid median line (drawn last, prominent)
- p10 + p90 dashed lines on top of the bands
Stacking deltas keeps the band fills clean — plotting raw quantiles
each as their own area would overlap badly. Reusable by scenario
detail in the next chunk (same ProjectionPoint[] shape).
5 tests pass (was 2). 470 KB gzipped (ECharts).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 22:08:00 +00:00
|
|
|
|
|
|
|
|
function NavTab({ to, children }: { to: string; children: React.ReactNode }) {
|
|
|
|
|
return (
|
|
|
|
|
<NavLink
|
|
|
|
|
to={to}
|
|
|
|
|
end
|
|
|
|
|
className={({ isActive }) =>
|
|
|
|
|
`${isActive ? 'text-slate-900 font-medium' : 'text-slate-500 hover:text-slate-800'}`
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</NavLink>
|
|
|
|
|
);
|
|
|
|
|
}
|