Compare commits
2 Commits
886ffa39ef
...
c5e912d4ed
| Author | SHA1 | Date | |
|---|---|---|---|
| c5e912d4ed | |||
| 3dc79aa425 |
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user