fire-planner: life-event spending bumps now reflected in fan + auto-
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
refresh on scenario edits Two fixes for the user's report that adding a £100k life-event spend didn't change the chart: Engine (simulator.py) - New `extra_outflows` param. cashflow_adjustments still drains the portfolio at start-of-year as before, but the simulator now ALSO records the spending in `withdrawal_hist[p, y]` so the chart's red median-withdrawal trace shows the bump. Without this, the £100k silently came out of the portfolio but the user-facing withdrawal trace stayed at the strategy's flat 4% draw. - simulate.py wires extra_outflows = essential + discretionary category outflows from life events. UX (ScenarioDetail.tsx) - New auto-refresh: when life events / income streams / flex rules change for a scenario, the page fires `/simulate` automatically with 2,000 paths and uses the result as the primary fan/year-stats source. The persisted MC run is only consulted as a fallback for scenarios with no overrides. - Fan chart title gains a "live preview · Xs · Ny" pill while a sim is current, and "re-running…" while a fresh one is in flight. - Removed the now-redundant "Live preview run" duplicate card lower down — the main chart IS the live preview. - Year-stats badge row reads from sim.data when available so changes propagate immediately to NW / Δ NW / Spending / Taxes. 247 pytest pass (+1 new); mypy + ruff clean; frontend typecheck/test/ build green.
This commit is contained in:
parent
f9084d1a15
commit
eb0dd3ddbf
4 changed files with 199 additions and 49 deletions
|
|
@ -111,6 +111,7 @@ def _project(req: SimulateRequest, paths: np.ndarray) -> tuple[SimulationResult,
|
|||
|
||||
cashflow_adjustments = None
|
||||
discretionary_outflows = None
|
||||
extra_outflows = None
|
||||
if req.life_events:
|
||||
engine_events = [
|
||||
EventInput(
|
||||
|
|
@ -126,6 +127,12 @@ def _project(req: SimulateRequest, paths: np.ndarray) -> tuple[SimulationResult,
|
|||
cashflow_adjustments = events_to_cashflow_array(engine_events, req.horizon_years)
|
||||
category_outflows = events_to_category_outflows(engine_events, req.horizon_years)
|
||||
discretionary_outflows = category_outflows.get("discretionary")
|
||||
# extra_outflows feeds the withdrawal-trace display: total of
|
||||
# essential + discretionary spending events surfaces alongside
|
||||
# the strategy's draw on the chart.
|
||||
essential = category_outflows.get("essential")
|
||||
if essential is not None and discretionary_outflows is not None:
|
||||
extra_outflows = essential + discretionary_outflows
|
||||
|
||||
engine_flex = [
|
||||
EngineFlexRule(
|
||||
|
|
@ -175,6 +182,7 @@ def _project(req: SimulateRequest, paths: np.ndarray) -> tuple[SimulationResult,
|
|||
income_inflows=income_inflows,
|
||||
income_taxable=income_taxable,
|
||||
discretionary_outflows=discretionary_outflows,
|
||||
extra_outflows=extra_outflows,
|
||||
flex_rules=engine_flex,
|
||||
)
|
||||
elapsed = time.perf_counter() - started
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue