fix: use categorical regime fundamentals
Deploy / lint (push) Successful in 8s
Deploy / test (push) Successful in 1m13s
Deploy / deploy (push) Successful in 37s

This commit is contained in:
2026-07-15 10:13:22 +02:00
parent ced066dc01
commit f714782fa4
7 changed files with 257 additions and 58 deletions
+82
View File
@@ -7,6 +7,7 @@ import json
from datetime import date, timedelta
import pytest
from pydantic import ValidationError as PydanticValidationError
from sqlalchemy import select
from app.models.regime_snapshot import RegimeSnapshot
@@ -110,11 +111,52 @@ def test_fundamentals_never_replay_before_effective_date_and_expire():
assert _fundamental_scores_asof(overrides, config, date(2026, 8, 22))[:2] == (None, None)
def test_capex_score_is_derived_from_company_categories():
names = DEFAULT_CONFIG["tickers"]["hyperscalers"]
assert rms._score_capex_states(
dict.fromkeys(names, "holding"), names
) == 0.0
assert rms._score_capex_states(
{names[0]: "cutting", **dict.fromkeys(names[1:], "holding")}, names
) == 25.0
assert rms._score_capex_states(
{names[0]: "cutting", names[1]: "holding", names[2]: "holding", names[3]: "unknown"},
names,
) == 33.3
assert rms._score_capex_states(
{names[0]: "cutting", names[1]: "holding", names[2]: "unknown", names[3]: "unknown"},
names,
) is None
def test_fundamental_api_rejects_numeric_ordinal_overrides():
with pytest.raises(PydanticValidationError):
market_router.RegimeFundamentalsUpdate(f3_score=75)
@pytest.mark.asyncio
async def test_legacy_numeric_fundamentals_do_not_leak_into_v2(monkeypatch):
async def fake_value(_db, _key):
return json.dumps({"f1_score": 75.0, "f3_score": 75.0, "source": "manual"})
monkeypatch.setattr(rms.settings_store, "get_value", fake_value)
result = await rms.get_fundamental_overrides(object())
assert result["methodology"] == "v2"
assert result["f1_score"] is None
assert result["f3_score"] is None
assert result["good_news_stock_down"] == "mixed"
@pytest.mark.asyncio
async def test_unlock_does_not_redate_a_fundamental_observation(monkeypatch):
stored = {
"methodology": "v2",
"f1_score": 100.0,
"f3_score": 0.0,
"capex": dict.fromkeys(DEFAULT_CONFIG["tickers"]["hyperscalers"], "cutting"),
"good_news_stock_down": "no",
"locked": True,
"source": "manual",
"fetched_at": "2026-06-01T10:00:00+00:00",
@@ -139,6 +181,46 @@ async def test_unlock_does_not_redate_a_fundamental_observation(monkeypatch):
assert saved == result
@pytest.mark.asyncio
async def test_manual_fundamentals_are_categorical_and_derived(monkeypatch):
names = DEFAULT_CONFIG["tickers"]["hyperscalers"]
current = {
"methodology": "v2",
"f1_score": None,
"f3_score": None,
"capex": dict.fromkeys(names, "unknown"),
"good_news_stock_down": "mixed",
"locked": False,
"reasoning": "old reasoning",
"fetched_at": None,
"effective_date": None,
"source": "default",
}
saved: dict = {}
async def fake_get(_db):
return dict(current)
async def fake_update(_db, _key, value):
saved.update(json.loads(value))
monkeypatch.setattr(rms, "get_fundamental_overrides", fake_get)
monkeypatch.setattr(rms, "update_setting", fake_update)
capex = {names[0]: "cutting", **dict.fromkeys(names[1:], "holding")}
result = await rms.set_fundamental_overrides(
object(), capex=capex, good_news_stock_down="mixed"
)
assert result["f1_score"] == 25.0
assert result["f3_score"] is None
assert result["good_news_stock_down"] == "mixed"
assert result["source"] == "manual"
assert result["locked"] is True
assert result["reasoning"] is None
assert saved == result
@pytest.mark.asyncio
async def test_prior_v2_snapshot_is_immutable_without_explicit_rebuild(db_session):
snapshot_date = date(2026, 6, 26)