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

173 lines
6.7 KiB
Python

import unittest
from datetime import datetime, timezone
from pathlib import Path
from src.services.research_council import CouncilOptimizationService, build_replay_summary, choose_next_actions
class _FakeResearchCouncilService:
def __init__(self):
self.backfill_calls = []
self.persist_calls = []
self.preview_calls = []
def backfill_lessons(self, **kwargs):
self.backfill_calls.append(kwargs)
return {
"instrument_id": kwargs["instrument_id"],
"totals": {"requested": 2, "created": 1, "updated": 1},
}
def build_and_persist(self, **kwargs):
self.persist_calls.append(kwargs)
return {"id": 101, "instrument_id": kwargs["instrument_id"]}
def get_recent_runs(self, **kwargs):
return [
{
"payload": {
"context": {
"latest_candidate": {
"session_code": "LONDON",
"model_code": "SWEEP_MSS_FVG",
"missing_confirmations": ["mss"],
},
"trust_policy": {"blocking_reasons": ["trust_blocked"]},
"risk_result": {"reasons": ["risk_blocked"]},
"compact_lessons": [{"lesson_key": "journal:1"}],
},
"decision": {
"follow_up_checks": ["mss", "risk_blocked"],
"operator_questions": ["question"],
"disagreement_points": ["debate"],
"committee_confidence": 0.6,
},
}
}
]
def build_preview(self, **kwargs):
self.preview_calls.append(kwargs)
return {
"decision": {
"overall_stance": "observe_only",
"committee_confidence": 0.7,
"follow_up_checks": ["mss"],
"disagreement_points": ["debate"],
},
"context": {"compact_lessons": [{"lesson_key": "journal:1"}]},
}
class _FakeOptimizationRepository:
def __init__(self):
self.rows = []
def upsert_cycle(self, **kwargs):
row = {"id": 301, **kwargs}
self.rows.append(row)
return 301
def fetch_latest_cycle(self, *, instrument_id):
for row in reversed(self.rows):
if row["instrument_id"] == instrument_id:
return dict(row)
return None
def fetch_recent_cycles(self, *, instrument_id, limit=20):
return [dict(row) for row in self.rows if row["instrument_id"] == instrument_id][:limit]
class _FakeTrustService:
def build_summary(self, *, instrument_id):
return {"models": [{"model_code": "SWEEP_MSS_FVG"}]}
class CouncilOptimizationServiceTests(unittest.TestCase):
def test_run_cycle_persists_full_optimization_payload(self) -> None:
repo = _FakeOptimizationRepository()
service = CouncilOptimizationService(
research_council_service=_FakeResearchCouncilService(),
optimization_repository=repo,
trust_service=_FakeTrustService(),
)
payload = service.run_cycle(
instrument_id=1,
timeframe="5m",
model_code="SWEEP_MSS_FVG",
eval_limit=10,
lesson_limit=5,
cycle_key="research_council_opt:test",
now=datetime(2026, 5, 11, tzinfo=timezone.utc),
)
self.assertTrue(payload["passed"])
self.assertEqual(payload["id"], 301)
self.assertEqual(payload["fresh_run_id"], 101)
self.assertEqual(payload["eval_summary"]["run_count"], 1)
self.assertEqual(payload["arena_summary"]["best_model_code"], "SWEEP_MSS_FVG")
self.assertIn("rerun_optimization_cycle_after_next_market_sample", payload["next_actions"])
self.assertIn("research_council_optimization_does_not_change_execution_gate", payload["boundary_notes"])
self.assertEqual(repo.rows[0]["cycle_key"], "research_council_opt:test")
def test_latest_and_recent_cycles_delegate_to_repository(self) -> None:
repo = _FakeOptimizationRepository()
service = CouncilOptimizationService(
research_council_service=_FakeResearchCouncilService(),
optimization_repository=repo,
trust_service=_FakeTrustService(),
)
service.run_cycle(instrument_id=1, cycle_key="cycle:1", now=datetime(2026, 5, 11, tzinfo=timezone.utc))
latest = service.get_latest_cycle(instrument_id=1)
recent = service.get_recent_cycles(instrument_id=1, limit=5)
self.assertEqual(latest["cycle_key"], "cycle:1")
self.assertEqual(len(recent), 1)
def test_choose_next_actions_identifies_sparse_inputs(self) -> None:
actions = choose_next_actions(
lesson_summary={"totals": {"requested": 0}},
replay_summary={"status": "completed", "confirmed_swing_count": 0},
eval_summary={
"run_count": 1,
"lesson_usage_rate": 0.0,
"avg_follow_up_check_count": 0,
"avg_baseline_issue_count": 2,
"disagreement_hint_rate": 0.0,
},
arena_summary={"rows": []},
)
self.assertIn("seed_lessons_from_journal_weekly_review_or_replay", actions)
self.assertIn("increase_lesson_retrieval_or_backfill_coverage", actions)
self.assertIn("review_bull_bear_disagreement_prompts", actions)
def test_p5_confirmed_swing_fixture_closes_replay_follow_up(self) -> None:
replay_file = Path(__file__).resolve().parents[2] / "data" / "replay" / "research_council_confirmed_swings_v1.jsonl"
replay_summary = build_replay_summary(replay_file)
actions = choose_next_actions(
lesson_summary={"totals": {"requested": 3}},
replay_summary=replay_summary,
eval_summary={
"run_count": 5,
"lesson_usage_rate": 1.0,
"avg_follow_up_check_count": 3,
"avg_baseline_issue_count": 2,
"disagreement_hint_rate": 0.5,
},
arena_summary={"rows": [{"model_code": "SWEEP_MSS_FVG", "committee_confidence": 0.7}]},
)
self.assertTrue(replay_summary["confirmed_swing_gate_passed"])
self.assertEqual(replay_summary["confirmed_swing_count"], 2)
self.assertEqual(replay_summary["swing_counts"], {"swing_high": 1, "swing_low": 1})
self.assertNotIn("add_replay_fixture_with_confirmed_swings", actions)
self.assertEqual(actions, ["rerun_optimization_cycle_after_next_market_sample"])
if __name__ == "__main__":
unittest.main()