Compare commits

...

2 Commits

Author SHA1 Message Date
c5e912d4ed chore(deps): update dependency react-hook-form to v7.68.0
All checks were successful
Lint / Lint and Typecheck (push) Successful in 45s
Lint / Lint and Typecheck (pull_request) Successful in 40s
2025-12-07 01:46:01 +01:00
3dc79aa425 fix MC test
All checks were successful
Lint / Lint and Typecheck (push) Successful in 41s
2025-12-07 01:43:26 +01:00
3 changed files with 31 additions and 12 deletions

14
pnpm-lock.yaml generated
View File

@@ -14,7 +14,7 @@ importers:
dependencies: dependencies:
'@hookform/resolvers': '@hookform/resolvers':
specifier: ^5.0.1 specifier: ^5.0.1
version: 5.2.2(react-hook-form@7.67.0(react@19.2.1)) version: 5.2.2(react-hook-form@7.68.0(react@19.2.1))
'@radix-ui/react-accordion': '@radix-ui/react-accordion':
specifier: ^1.2.8 specifier: ^1.2.8
version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
@@ -80,7 +80,7 @@ importers:
version: 19.2.1(react@19.2.1) version: 19.2.1(react@19.2.1)
react-hook-form: react-hook-form:
specifier: ^7.56.1 specifier: ^7.56.1
version: 7.67.0(react@19.2.1) version: 7.68.0(react@19.2.1)
recharts: recharts:
specifier: ^2.15.3 specifier: ^2.15.3
version: 2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) version: 2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
@@ -3296,8 +3296,8 @@ packages:
peerDependencies: peerDependencies:
react: ^19.2.1 react: ^19.2.1
react-hook-form@7.67.0: react-hook-form@7.68.0:
resolution: {integrity: sha512-E55EOwKJHHIT/I6J9DmQbCWToAYSw9nN5R57MZw9rMtjh+YQreMDxRLfdjfxQbiJ3/qbg3Z02wGzBX4M+5fMtQ==} resolution: {integrity: sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19 react: ^16.8.0 || ^17 || ^18 || ^19
@@ -4208,10 +4208,10 @@ snapshots:
'@floating-ui/utils@0.2.10': {} '@floating-ui/utils@0.2.10': {}
'@hookform/resolvers@5.2.2(react-hook-form@7.67.0(react@19.2.1))': '@hookform/resolvers@5.2.2(react-hook-form@7.68.0(react@19.2.1))':
dependencies: dependencies:
'@standard-schema/utils': 0.3.0 '@standard-schema/utils': 0.3.0
react-hook-form: 7.67.0(react@19.2.1) react-hook-form: 7.68.0(react@19.2.1)
'@humanfs/core@0.19.1': {} '@humanfs/core@0.19.1': {}
@@ -6985,7 +6985,7 @@ snapshots:
react: 19.2.1 react: 19.2.1
scheduler: 0.27.0 scheduler: 0.27.0
react-hook-form@7.67.0(react@19.2.1): react-hook-form@7.68.0(react@19.2.1):
dependencies: dependencies:
react: 19.2.1 react: 19.2.1

View File

@@ -333,9 +333,18 @@ export default function FireCalculatorForm({
// Sort to find percentiles // Sort to find percentiles
balancesForYear.sort((a, b) => a - b); balancesForYear.sort((a, b) => a - b);
const p10 = balancesForYear[Math.floor(numSimulations * 0.4)]; const pickPercentile = (fraction: number) => {
const p50 = balancesForYear[Math.floor(numSimulations * 0.5)]; const clampedIndex = Math.min(
const p90 = balancesForYear[Math.floor(numSimulations * 0.6)]; balancesForYear.length - 1,
Math.max(0, Math.floor((balancesForYear.length - 1) * fraction)),
);
return balancesForYear[clampedIndex];
};
// For Monte Carlo, we present a narrow middle band (40th-60th) to show typical outcomes
const p10 = pickPercentile(0.4);
const p50 = pickPercentile(0.5);
const p90 = pickPercentile(0.6);
// Calculate other metrics (using deterministic logic for "untouched" etc for simplicity, or p50) // Calculate other metrics (using deterministic logic for "untouched" etc for simplicity, or p50)
// We need to reconstruct the "standard" fields for compatibility with the chart // We need to reconstruct the "standard" fields for compatibility with the chart
@@ -437,13 +446,23 @@ export default function FireCalculatorForm({
}); });
}; };
const isMonteCarlo = form.watch('simulationMode') === 'monte-carlo'; const simulationModeValue = form.watch('simulationMode');
const isMonteCarlo = simulationModeValue === 'monte-carlo';
const chartData = const chartData =
result?.yearlyData.map((row) => ({ result?.yearlyData.map((row) => ({
...row, ...row,
mcRange: (row.balanceP90 ?? 0) - (row.balanceP10 ?? 0), mcRange: (row.balanceP90 ?? 0) - (row.balanceP10 ?? 0),
})) ?? []; })) ?? [];
// Ensure we always have a fresh calculation when switching simulation modes (or on first render)
useEffect(() => {
if (!result) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
form.handleSubmit(onSubmit)();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [simulationModeValue]);
const projectionChartConfig: ChartConfig = { const projectionChartConfig: ChartConfig = {
year: { year: {
label: 'Year', label: 'Year',

View File

@@ -126,7 +126,7 @@ describe('FireCalculatorForm', () => {
await screen.findByText('Financial Projection'); await screen.findByText('Financial Projection');
const bandLegend = await screen.findByTestId('mc-band-legend'); const bandLegend = await screen.findByTestId('mc-band-legend');
expect(bandLegend).toHaveTextContent('10th-90th percentile'); expect(bandLegend).toHaveTextContent('40th-60th percentile');
}); });
it('handles withdrawal strategy selection', async () => { it('handles withdrawal strategy selection', async () => {