fix(regime): reseed stored history on a sensor change, and stop faking an observation
Two review findings on 46ace50.
[P1] Raising HY_OAS_WINDOW_DAYS to 700 only reached newly computed rows. A
routine run recomputes the latest trading date alone, and `rebuilding` was keyed
on "no v3 snapshot exists at all", which is false once the cutover has run --
so every row already written kept the credit gap the wider window exists to
close, indefinitely.
Adds SENSOR_REVISION: stamped into each snapshot, absent on pre-marker rows
(read as 1), and a stored revision below the current one triggers exactly one
reseed. Deliberately not METHODOLOGY, which would partition the history API and
discard the cached event study -- neither warranted, since the study recomputes
its Warning series from source rather than reading snapshots and so cannot be
staled by a reseed.
The reseed is bounded by REBUILD_LOOKBACK_DAYS in calendar days rather than a
session count, because the binding constraint is the OAS fetch: each replayed
row needs W3's 20-business-day lookback inside HY_OAS_WINDOW_DAYS. Replaying by
session count would have left the oldest stored rows unrepaired -- the exact
rows the fix targets. At 672 days the replay covers ~464 sessions, W3's oldest
requirement lands on the first fetched OAS day, and the ~400-session series the
cutover wrote is fully covered. A test asserts that relationship so the two
constants cannot drift back into recreating the gap.
[P2] With nothing ever collected, current_observation returned available=true
and the default placeholders -- "unknown" for every hyperscaler, "mixed" for the
reaction -- so the card announced a reading that never happened. Those are the
absence of an observation, not an observation of absence. Gated on `observed`
(non-null fetched_at, the one field every path writing real content stamps),
which blanks the content and drives a proper empty state naming where an admin
collects one. This was a regression from 46ace50; fundamental_overlay never had
it, since no observation means no effective date means pending.
Also renames the leftover v2 identifiers in the touched paths
(rewrite_existing_v2, latest_v2).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -279,6 +279,37 @@ def test_live_observation_is_visible_before_its_effective_date():
|
||||
assert current_observation(overrides, config, date(2026, 8, 22))["available"] is False
|
||||
|
||||
|
||||
def test_an_uncollected_observation_is_not_reported_as_collected():
|
||||
"""The default override is placeholders, not a reading.
|
||||
|
||||
``capex`` defaults to "unknown" for every hyperscaler and the reaction to
|
||||
"mixed". Surfacing those as an observation made the card claim a read that
|
||||
never happened.
|
||||
"""
|
||||
names = DEFAULT_CONFIG["tickers"]["hyperscalers"]
|
||||
nothing_collected = {
|
||||
"f1_score": None,
|
||||
"f3_score": None,
|
||||
"capex": {name: "unknown" for name in names},
|
||||
"good_news_stock_down": "mixed",
|
||||
"reasoning": None,
|
||||
"fetched_at": None,
|
||||
"effective_date": None,
|
||||
"source": "default",
|
||||
}
|
||||
|
||||
blank = current_observation(nothing_collected, DEFAULT_CONFIG, date(2026, 8, 7))
|
||||
assert blank["observed"] is False
|
||||
assert blank["available"] is False
|
||||
assert blank["capex"] is None
|
||||
assert blank["good_news_stock_down"] is None
|
||||
assert blank["reasoning"] is None
|
||||
|
||||
# One real observation flips it, placeholders and all.
|
||||
collected = {**nothing_collected, "fetched_at": "2026-08-07T10:00:00+00:00", "source": "gemini"}
|
||||
assert current_observation(collected, DEFAULT_CONFIG, date(2026, 8, 7))["observed"] is True
|
||||
|
||||
|
||||
def test_fundamentals_do_not_move_the_warning_score():
|
||||
"""The v3 complaint: a maxed-out LLM read must not silently do nothing.
|
||||
|
||||
@@ -462,11 +493,11 @@ async def test_prior_snapshot_is_immutable_without_explicit_rebuild(db_session):
|
||||
changed["state"] = {"score": 90.0, "band": "breaking"}
|
||||
|
||||
written, _ = await rms._upsert_snapshot(
|
||||
db_session, first, rewrite_existing_v2=True
|
||||
db_session, first, rewrite_existing=True
|
||||
)
|
||||
await db_session.flush()
|
||||
rewritten, persisted = await rms._upsert_snapshot(
|
||||
db_session, changed, rewrite_existing_v2=False
|
||||
db_session, changed, rewrite_existing=False
|
||||
)
|
||||
row = (
|
||||
await db_session.execute(
|
||||
@@ -509,10 +540,10 @@ async def test_routine_can_refresh_latest_trading_session_after_civil_day_rolls(
|
||||
return {}, {}
|
||||
|
||||
async def fake_latest(_db):
|
||||
return object(), {"methodology": "v3"}
|
||||
return object(), {"methodology": "v3", "sensor_revision": rms.SENSOR_REVISION}
|
||||
|
||||
async def fake_upsert(_db, result, *, rewrite_existing_v2):
|
||||
rewrites.append(rewrite_existing_v2)
|
||||
async def fake_upsert(_db, result, *, rewrite_existing):
|
||||
rewrites.append(rewrite_existing)
|
||||
return True, result
|
||||
|
||||
class FakeDB:
|
||||
@@ -533,6 +564,91 @@ async def test_routine_can_refresh_latest_trading_session_after_civil_day_rolls(
|
||||
assert rewrites == [True]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("stored", "expect_reseed"),
|
||||
[
|
||||
({"methodology": "v3"}, True), # written before the marker existed
|
||||
({"methodology": "v3", "sensor_revision": 1}, True),
|
||||
({"methodology": "v3", "sensor_revision": rms.SENSOR_REVISION}, False),
|
||||
],
|
||||
)
|
||||
async def test_a_stale_sensor_revision_reseeds_stored_history(
|
||||
monkeypatch, stored, expect_reseed
|
||||
):
|
||||
"""Widening the OAS window has to reach rows that are already stored.
|
||||
|
||||
Routine runs recompute only the latest date, so without this trigger every
|
||||
older row would keep the credit gap the wider window exists to close.
|
||||
"""
|
||||
sessions = [date.today() - timedelta(days=offset) for offset in reversed(range(10))]
|
||||
prices = {symbol: [(day, 100.0) for day in sessions] for symbol in ("SMH", "QQQ", "SPY")}
|
||||
written: list[date] = []
|
||||
revisions: list[int] = []
|
||||
|
||||
async def fake_config(_db):
|
||||
return copy.deepcopy(DEFAULT_CONFIG)
|
||||
|
||||
async def fake_overrides(_db):
|
||||
return {"locked": True, "fetched_at": None, "effective_date": None}
|
||||
|
||||
async def fake_prices(_config, _start, _end):
|
||||
return prices
|
||||
|
||||
async def fake_fred(_series_id, _start, _end):
|
||||
return None
|
||||
|
||||
async def fake_breadth(_db, _symbols, window, min_tickers):
|
||||
return {}, {}
|
||||
|
||||
async def fake_latest(_db):
|
||||
return object(), stored
|
||||
|
||||
async def fake_upsert(_db, result, *, rewrite_existing):
|
||||
written.append(date.fromisoformat(result["date"]))
|
||||
revisions.append(result["sensor_revision"])
|
||||
# Every replayed row must be rewritable, or a reseed writes one row.
|
||||
assert rewrite_existing is True
|
||||
return True, result
|
||||
|
||||
class FakeDB:
|
||||
async def commit(self):
|
||||
return None
|
||||
|
||||
for name, value in (
|
||||
("get_regime_config", fake_config),
|
||||
("get_fundamental_overrides", fake_overrides),
|
||||
("_fetch_prices", fake_prices),
|
||||
("_fetch_fred_series", fake_fred),
|
||||
("_latest_snapshot_row", fake_latest),
|
||||
("_upsert_snapshot", fake_upsert),
|
||||
):
|
||||
monkeypatch.setattr(rms, name, value)
|
||||
monkeypatch.setattr(rms.breadth_service, "compute_breadth_details", fake_breadth)
|
||||
|
||||
await rms.update_regime_monitor(FakeDB())
|
||||
|
||||
if expect_reseed:
|
||||
assert written == sessions, "a reseed must replay the whole stored span"
|
||||
else:
|
||||
assert written == [sessions[-1]], "a current revision must not reseed"
|
||||
assert set(revisions) == {rms.SENSOR_REVISION}
|
||||
|
||||
|
||||
def test_the_rebuild_span_stays_inside_the_oas_window():
|
||||
"""The reseed must not replay rows it cannot compute credit for.
|
||||
|
||||
Each replayed row needs W3's lookback inside the fetched OAS window; if the
|
||||
replay reached further back than the fetch, the reseed would recreate the
|
||||
very gap it exists to close.
|
||||
"""
|
||||
replay_calendar_days = rms.REBUILD_LOOKBACK_DAYS
|
||||
w3_lookback_calendar = rms.W3_OAS_LOOKBACK * 7 / 5 # business days -> calendar
|
||||
assert replay_calendar_days + w3_lookback_calendar <= rms.HY_OAS_WINDOW_DAYS
|
||||
# ...and still covers the 400-session series the v3 cutover wrote.
|
||||
assert replay_calendar_days >= 400 * 365 / 252
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_manual_llm_refresh_recomputes_latest_regime_snapshot(monkeypatch):
|
||||
calls: list[str] = []
|
||||
|
||||
Reference in New Issue
Block a user