210 lines
7.4 KiB
Python
210 lines
7.4 KiB
Python
import unittest
|
|
|
|
from src.services.research_council import CouncilContext, CouncilContextBuilder
|
|
|
|
|
|
class _FakeMarketState:
|
|
def to_dict(self):
|
|
return {
|
|
"freshness": {"code": "market_data_fresh"},
|
|
"stream_state": {"code": "market_stream_connected"},
|
|
"data_gap_state": {"code": "market_data_gap_clear"},
|
|
"news_window": {
|
|
"code": "news_window_inactive",
|
|
"is_active": False,
|
|
"provider_name": "fake_news",
|
|
"freshness_code": "news_provider_fresh",
|
|
"active_events": [],
|
|
},
|
|
}
|
|
|
|
|
|
class _FakeMarketStateService:
|
|
def __init__(self):
|
|
self.calls = []
|
|
|
|
def build_market_state(self, *, instrument_id, timeframe, now):
|
|
self.calls.append(
|
|
{
|
|
"instrument_id": instrument_id,
|
|
"timeframe": timeframe,
|
|
"now": now,
|
|
}
|
|
)
|
|
return _FakeMarketState()
|
|
|
|
|
|
class _FakeDecisionService:
|
|
def __init__(self):
|
|
self.calls = []
|
|
|
|
def build_preview(self, *, instrument_id, model_code):
|
|
self.calls.append({"instrument_id": instrument_id, "model_code": model_code})
|
|
return {
|
|
"status": "monitoring",
|
|
"action_recommendation": "restricted_decision_support",
|
|
"risk_result": {"allowed": False, "reasons": ["risk_governor_blocked"]},
|
|
"id": 44,
|
|
"raw": {
|
|
"plan": {"id": 11, "status": "active", "bias": "bullish"},
|
|
"candidate": {"id": 22, "model_code": model_code, "status": "draft"},
|
|
"signal": {"id": 33, "status": "draft"},
|
|
"trust_policy": {
|
|
"classification": "observe",
|
|
"operator_mode": "decision_support_only",
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
class _FakeDecisionRepository:
|
|
def fetch_recent_decision_alerts(self, instrument_id: int, limit: int = 10):
|
|
return [
|
|
{
|
|
"id": 101,
|
|
"instrument_id": instrument_id,
|
|
"alert_type": "candidate_blocked",
|
|
}
|
|
][:limit]
|
|
|
|
|
|
class _FakeWeeklyReviewService:
|
|
def build_summary(self, *, instrument_id):
|
|
return {
|
|
"instrument_id": instrument_id,
|
|
"entry_count": 2,
|
|
"next_week_actions": ["tighten_real_trade_reconciliation"],
|
|
}
|
|
|
|
|
|
class _FakeJournalRepository:
|
|
def fetch_recent_journal_entries(self, *, instrument_id=None, limit: int = 10):
|
|
return [
|
|
{
|
|
"id": 201,
|
|
"instrument_id": instrument_id,
|
|
"session_code": "LONDON",
|
|
}
|
|
][:limit]
|
|
|
|
|
|
class _FakeVisualWatchService:
|
|
def __init__(self):
|
|
self.calls = []
|
|
|
|
def fetch_reviews_for_packet(self, *, decision_packet_id, limit=10):
|
|
self.calls.append({"decision_packet_id": decision_packet_id, "limit": limit})
|
|
return [
|
|
{
|
|
"id": 301,
|
|
"decision_packet_id": decision_packet_id,
|
|
"visual_state": "aligned",
|
|
"visual_confidence": 0.88,
|
|
"visual_notes": "structure visible",
|
|
"ui_anomalies": [],
|
|
}
|
|
][:limit]
|
|
|
|
|
|
class _FakeMemoryService:
|
|
def __init__(self):
|
|
self.calls = []
|
|
|
|
def fetch_compact_lessons(self, **kwargs):
|
|
self.calls.append(kwargs)
|
|
return [
|
|
{
|
|
"lesson_key": "journal:201",
|
|
"title": "Journal lesson LONDON SWEEP_MSS_FVG",
|
|
"lesson_summary": "Prior London session favored tighter confirmation discipline.",
|
|
"session_code": "LONDON",
|
|
"setup_type": "SWEEP_MSS_FVG",
|
|
"model_code": "SWEEP_MSS_FVG",
|
|
"tags": ["weekly_review", "tighten_real_trade_reconciliation"],
|
|
"priority_score": 0.7,
|
|
"similarity_score": 1.2,
|
|
}
|
|
]
|
|
|
|
|
|
class CouncilContextBuilderTests(unittest.TestCase):
|
|
def test_builder_reuses_existing_ai_ict_outputs(self) -> None:
|
|
market_state_service = _FakeMarketStateService()
|
|
decision_service = _FakeDecisionService()
|
|
builder = CouncilContextBuilder(
|
|
market_state_service=market_state_service,
|
|
decision_service=decision_service,
|
|
decision_repository=_FakeDecisionRepository(),
|
|
weekly_review_service=_FakeWeeklyReviewService(),
|
|
journal_repository=_FakeJournalRepository(),
|
|
visual_watch_service=_FakeVisualWatchService(),
|
|
memory_service=_FakeMemoryService(),
|
|
)
|
|
|
|
context = builder.build_context(
|
|
instrument_id=1,
|
|
timeframe="5m",
|
|
model_code="SWEEP_MSS_FVG",
|
|
)
|
|
|
|
self.assertIsInstance(context, CouncilContext)
|
|
self.assertEqual(context.instrument_id, 1)
|
|
self.assertEqual(context.timeframe, "5m")
|
|
self.assertEqual(context.model_code, "SWEEP_MSS_FVG")
|
|
self.assertEqual(context.latest_trading_plan["id"], 11)
|
|
self.assertEqual(context.latest_candidate["id"], 22)
|
|
self.assertEqual(context.latest_signal["id"], 33)
|
|
self.assertEqual(context.trust_policy["operator_mode"], "decision_support_only")
|
|
self.assertEqual(context.recent_alerts[0]["alert_type"], "candidate_blocked")
|
|
self.assertEqual(context.recent_journal_entries[0]["session_code"], "LONDON")
|
|
self.assertEqual(context.weekly_review_summary["entry_count"], 2)
|
|
self.assertEqual(context.visual_reviews[0]["visual_state"], "aligned")
|
|
self.assertEqual(context.compact_lessons[0]["lesson_key"], "journal:201")
|
|
self.assertEqual(context.source_snapshot_meta["trading_plan_id"], 11)
|
|
self.assertEqual(context.source_snapshot_meta["candidate_id"], 22)
|
|
self.assertEqual(context.source_snapshot_meta["signal_id"], 33)
|
|
self.assertEqual(context.source_snapshot_meta["decision_packet_id"], 44)
|
|
self.assertEqual(context.source_snapshot_meta["visual_review_count"], 1)
|
|
self.assertEqual(context.source_snapshot_meta["lesson_count"], 1)
|
|
|
|
self.assertEqual(len(market_state_service.calls), 1)
|
|
self.assertEqual(len(decision_service.calls), 1)
|
|
|
|
def test_builder_handles_sparse_preview_payloads(self) -> None:
|
|
class _SparseDecisionService:
|
|
def build_preview(self, *, instrument_id, model_code):
|
|
return {}
|
|
|
|
builder = CouncilContextBuilder(
|
|
market_state_service=_FakeMarketStateService(),
|
|
decision_service=_SparseDecisionService(),
|
|
decision_repository=_FakeDecisionRepository(),
|
|
weekly_review_service=_FakeWeeklyReviewService(),
|
|
journal_repository=_FakeJournalRepository(),
|
|
visual_watch_service=_FakeVisualWatchService(),
|
|
memory_service=_FakeMemoryService(),
|
|
)
|
|
|
|
context = builder.build_context(instrument_id=7)
|
|
|
|
self.assertEqual(context.latest_trading_plan, {})
|
|
self.assertEqual(context.latest_candidate, {})
|
|
self.assertEqual(context.latest_signal, {})
|
|
self.assertEqual(context.risk_result, {})
|
|
self.assertEqual(context.trust_policy, {})
|
|
|
|
|
|
__all__ = [
|
|
"_FakeDecisionRepository",
|
|
"_FakeDecisionService",
|
|
"_FakeJournalRepository",
|
|
"_FakeMarketStateService",
|
|
"_FakeMemoryService",
|
|
"_FakeVisualWatchService",
|
|
"_FakeWeeklyReviewService",
|
|
]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|