Files
ai-exchange/tests/unit/test_research_council_context_builder.py
T
2026-05-11 23:37:47 +08:00

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()