113 lines
3.7 KiB
Python
113 lines
3.7 KiB
Python
"""Cartesian scenario builder + strategy/regime factory."""
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from fire_planner.scenarios import (
|
|
DEFAULT_GLIDES,
|
|
DEFAULT_JURISDICTIONS,
|
|
DEFAULT_LEAVE_YEARS,
|
|
DEFAULT_STRATEGIES,
|
|
ScenarioSpec,
|
|
build_regime_schedule,
|
|
build_strategy,
|
|
cartesian_scenarios,
|
|
)
|
|
from fire_planner.strategies.guyton_klinger import GuytonKlingerStrategy
|
|
from fire_planner.strategies.trinity import TrinityStrategy
|
|
from fire_planner.strategies.vpw import VpwStrategy, VpwWithFloorStrategy
|
|
from fire_planner.tax.bulgaria import BulgariaTaxRegime
|
|
from fire_planner.tax.cyprus import CyprusTaxRegime
|
|
from fire_planner.tax.uae import UaeTaxRegime
|
|
from fire_planner.tax.uk import UkTaxRegime
|
|
|
|
|
|
def test_default_cartesian_count_is_120() -> None:
|
|
specs = cartesian_scenarios(spending_gbp=Decimal("100000"), nw_seed_gbp=Decimal("1000000"))
|
|
expected = (len(DEFAULT_JURISDICTIONS) * len(DEFAULT_STRATEGIES) * len(DEFAULT_LEAVE_YEARS) *
|
|
len(DEFAULT_GLIDES))
|
|
assert expected == 120
|
|
assert len(specs) == 120
|
|
|
|
|
|
def test_external_id_format() -> None:
|
|
spec = ScenarioSpec(
|
|
jurisdiction="cyprus",
|
|
strategy="vpw",
|
|
leave_uk_year=3,
|
|
glide_path="rising",
|
|
spending_gbp=Decimal("100000"),
|
|
nw_seed_gbp=Decimal("1000000"),
|
|
)
|
|
assert spec.external_id == "cyprus-vpw-leave-y3-glide-rising"
|
|
|
|
|
|
def test_cartesian_unique_external_ids() -> None:
|
|
specs = cartesian_scenarios(spending_gbp=Decimal("100000"), nw_seed_gbp=Decimal("1000000"))
|
|
ids = [s.external_id for s in specs]
|
|
assert len(ids) == len(set(ids))
|
|
|
|
|
|
def test_build_strategy_dispatch() -> None:
|
|
assert isinstance(build_strategy("trinity"), TrinityStrategy)
|
|
assert isinstance(build_strategy("guyton_klinger"), GuytonKlingerStrategy)
|
|
assert isinstance(build_strategy("vpw"), VpwStrategy)
|
|
|
|
|
|
def test_build_strategy_vpw_floor_requires_floor() -> None:
|
|
s = build_strategy("vpw_floor", floor=40_000.0)
|
|
assert isinstance(s, VpwWithFloorStrategy)
|
|
assert s.floor == 40_000.0
|
|
|
|
|
|
def test_build_strategy_vpw_floor_missing_floor_raises() -> None:
|
|
with pytest.raises(ValueError):
|
|
build_strategy("vpw_floor")
|
|
|
|
|
|
def test_build_strategy_unknown_raises() -> None:
|
|
with pytest.raises(KeyError):
|
|
build_strategy("walmart")
|
|
|
|
|
|
def test_build_regime_schedule_uae() -> None:
|
|
fn = build_regime_schedule("uae", leave_uk_year=2)
|
|
assert isinstance(fn(0), UkTaxRegime)
|
|
assert isinstance(fn(1), UkTaxRegime)
|
|
assert isinstance(fn(2), UaeTaxRegime)
|
|
assert isinstance(fn(50), UaeTaxRegime)
|
|
|
|
|
|
def test_build_regime_schedule_uk_constant() -> None:
|
|
fn = build_regime_schedule("uk", leave_uk_year=3)
|
|
# All years should resolve to UK
|
|
assert isinstance(fn(0), UkTaxRegime)
|
|
assert isinstance(fn(50), UkTaxRegime)
|
|
|
|
|
|
def test_build_regime_schedule_cyprus_switches_at_leave_year() -> None:
|
|
fn = build_regime_schedule("cyprus", leave_uk_year=3)
|
|
assert isinstance(fn(0), UkTaxRegime)
|
|
assert isinstance(fn(2), UkTaxRegime)
|
|
assert isinstance(fn(3), CyprusTaxRegime)
|
|
assert isinstance(fn(50), CyprusTaxRegime)
|
|
|
|
|
|
def test_build_regime_schedule_bulgaria() -> None:
|
|
fn = build_regime_schedule("bulgaria", leave_uk_year=1)
|
|
assert isinstance(fn(0), UkTaxRegime)
|
|
assert isinstance(fn(1), BulgariaTaxRegime)
|
|
|
|
|
|
def test_build_regime_schedule_unknown_raises() -> None:
|
|
with pytest.raises(KeyError):
|
|
build_regime_schedule("madeupistan", leave_uk_year=3)
|
|
|
|
|
|
def test_cartesian_unknown_glide_raises() -> None:
|
|
with pytest.raises(KeyError):
|
|
cartesian_scenarios(
|
|
spending_gbp=Decimal("100000"),
|
|
nw_seed_gbp=Decimal("1000000"),
|
|
glides=("staircase", ),
|
|
)
|