monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x138ba2210> @pytest.mark.unit def test_periodic_router_persists_decision_so_bus_handler_converges(monkeypatch): """Bug 175b5b8e49db5cc51935827e355ad010ebc4cb24 — the periodic router and the LIVE bus handler must converge on ONE decision per level onset. The seam is ``TideReasoningDecisions``: the periodic router (this code path) writes via ``store_reasoning``, and ``_load_level_event_decision`` reads it back when the bus event fires. With a stubbed reasoner answering LONG, the bus handler's read of the SAME (trading_day, event_et, kind) MUST return the same LONG.""" from rtrader.services import market_state_etf as mse from rtrader.services.signal_sim_engine import LEVEL_SUPPORT_BREAK, LEVEL_SUPPORT_RECLAIM monkeypatch.setattr(mse, "_reasoning_execute_enabled", lambda: True) monkeypatch.setattr(mse, "_decision_cutoff_time", lambda: _time(11, 0)) monkeypatch.setattr(mse, "confluence_score", lambda *a, **k: 3) monkeypatch.setattr(mse, "account_short_enabled", lambda nick: True) # Stub the LIVE reasoner — same seam the bus handler's reason_live_level_events calls. from rtrader.services import tide_etf_reasoning as TER monkeypatch.setattr(TER, "reason_for_level_event", lambda day, ev, **k: { "decision": "SHORT", "confidence": 0.7, "scale": 1.2, "rationale": "(stub short)", "story": "stub", "pattern": "SUPPORT-BREAK", "parse_failed": False}) # Stub the Mongo path so store_reasoning writes to the in-memory FakeDB the bus handler reads. db = _FakeDB() monkeypatch.setattr(CEL, "_open_db", lambda: db) monkeypatch.setattr(CEL, "_close_db", lambda d: None) # (1) PERIODIC ROUTER consult — origin='backtest' (this is the SIM path). out = CEL._sleeve_consolidated_level_event( DAY, datetime(2026, 7, 1, 10, 16, 0, tzinfo=ET), "down", "yuchao", signal_source=LEVEL_SUPPORT_BREAK, origin="backtest", dbf=lambda: db) assert out["source"] == "llm" and out["decision"] == "SHORT" and out["leg"] == "bear" # (2) BUS HANDLER read — _load_level_event_decision reads the same TideReasoningDecisions row. # live_only=True → returns ONLY origin='live' rows. The backtest row is structurally excluded; # the bus handler's own consult (reason_live_level_events) writes the LIVE row independently. live_rows = db["TideReasoningDecisions"].find( {"trading_day": DAY.isoformat(), "kind": "support_break", "et": "10:16", "$or": [{"origin": "live"}, {"origin": {"$exists": False}}]}) > assert live_rows == [], "backtest row must NOT leak through the live-only filter" E AssertionError: backtest row must NOT leak through the live-only filter E assert == [] E E Full diff: E - [] E + tests/services/test_combined_engine_live.py:2763: AssertionError