engine+ui: tax drains the portfolio + Wealthfolio-seeded NW default
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Two fixes:

(1) Simulator: portfolio drain is now `w + tax(w)`, not just `w`.
    The pre-2026-05-10 engine recorded tax in tax_hist but never
    subtracted it from the portfolio, so changing jurisdiction only
    moved the median_lifetime_tax cell — the fan chart, success
    rate, and ending percentiles were identical for UK vs Cyprus
    vs Malaysia. (The PLAYBOOK_VIKTOR.md memo from 2026-04-26
    explicitly noted this: "Success rate is regime-independent…
    tax doesn't drain the portfolio in this simulator.")

    Mental model now: spending_target is what the user takes home;
    the tax bill is an additional drag on the same pool. Higher-tax
    jurisdictions therefore drain faster and lower the success
    rate, which is the user's intuition. Trinity 4% effectively
    becomes "4% take-home + tax overhead". 188 tests still pass —
    most use Malaysia (0%) or hit the regime-independent code paths.

(2) /what-if and /scenarios/new now pre-fill nw_seed_gbp from
    GET /networth on first mount (when the wealthfolio_sync mirror
    has data), so opening the form starts from the user's real
    portfolio total instead of the £1.5M placeholder. Once the user
    edits the field, subsequent NW refetches don't clobber it
    (nwAutoFilled latch).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-05-10 00:21:14 +00:00
parent f781afe3fa
commit b40defacf0
3 changed files with 36 additions and 5 deletions

View file

@ -223,7 +223,17 @@ def simulate(
w = max(0.0, min(w, float(portfolio[p])))
tax_breakdown = regime_at(y).compute_tax(bucket_split(w, y))
t = float(tax_breakdown.total)
portfolio[p] = portfolio[p] - w
# Drain BOTH withdrawal AND tax from the portfolio. Mental
# model: `w` is what the user takes home to spend; the tax
# is an additional drag that comes out of the same pool. A
# high-tax jurisdiction therefore drains the portfolio
# faster and lowers the success rate, matching user intuition
# (Cyprus non-dom should beat UK on outcome, not just on a
# summary stat). Pre-2026-05-10 the engine recorded `t` but
# didn't subtract it, so jurisdiction only changed the
# `median_lifetime_tax_gbp` cell while the fan chart and
# success rate were identical across regimes.
portfolio[p] = max(0.0, portfolio[p] - w - t)
withdrawal_hist[p, y] = w
tax_hist[p, y] = t
last_withdrawal[p] = w