fix: use categorical regime fundamentals
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user