examples: simulator response gains examples_overlay block
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Adds an informational `examples_overlay` field to SimulateResult, populated from `summary_for_country` for the scenario's target country. Never affects simulation paths — lookup failures are caught and logged, yielding overlay=None. Wired into both /simulate and /compare; the shared session in /compare is used sequentially because AsyncSession is not safe for concurrent use. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
249991557b
commit
9b32247fea
3 changed files with 146 additions and 8 deletions
|
|
@ -6,6 +6,7 @@ point, the point is the endpoint produces a valid response shape.
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest_asyncio
|
||||
|
|
@ -14,6 +15,7 @@ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
|
|||
|
||||
from fire_planner.api.dependencies import get_session
|
||||
from fire_planner.app import app
|
||||
from fire_planner.db import FireExample
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
|
|
@ -165,3 +167,48 @@ async def test_compare_rejects_single_scenario(client: AsyncClient) -> None:
|
|||
},
|
||||
)
|
||||
assert resp.status_code == 422 # pydantic validation
|
||||
|
||||
|
||||
async def test_simulate_response_includes_examples_overlay(
|
||||
client: AsyncClient, session: AsyncSession) -> None:
|
||||
"""When examples exist for the scenario's target country, the
|
||||
simulator response surfaces them as `examples_overlay` — purely
|
||||
informational, never affects the simulation path."""
|
||||
for i, amount in enumerate([300_000, 400_000, 500_000], start=1):
|
||||
session.add(FireExample(
|
||||
reddit_id=f"th{i:02d}",
|
||||
source_sub="ExpatFIRE",
|
||||
post_url=f"https://reddit.com/r/ExpatFIRE/comments/th{i:02d}",
|
||||
post_date=date(2026, 1, 1),
|
||||
post_title=f"Thailand FIRE {i}",
|
||||
country="Thailand",
|
||||
portfolio_gbp=Decimal(str(amount)),
|
||||
annual_exp_gbp=Decimal("24000"),
|
||||
fi_status="FIRE",
|
||||
llm_model="qwen3-8b",
|
||||
))
|
||||
await session.commit()
|
||||
|
||||
resp = await client.post(
|
||||
"/simulate",
|
||||
json={
|
||||
"jurisdiction": "thailand",
|
||||
"strategy": "trinity",
|
||||
"leave_uk_year": 0,
|
||||
"glide_path": "static_60_40",
|
||||
"spending_gbp": "60000",
|
||||
"nw_seed_gbp": "1500000",
|
||||
"horizon_years": 30,
|
||||
"n_paths": 100,
|
||||
"seed": 42,
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
body = resp.json()
|
||||
assert "examples_overlay" in body
|
||||
overlay = body["examples_overlay"]
|
||||
assert overlay is not None
|
||||
assert overlay["country"] == "Thailand"
|
||||
assert overlay["count"] == 3
|
||||
assert Decimal(overlay["portfolio_gbp_median"]) == Decimal("400000.00")
|
||||
assert len(overlay["sample_links"]) == 3
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue