Files
ai-exchange/tests/unit/test_shadow_paper_sample_collection_service.py
2026-05-21 17:13:55 +08:00

154 lines
6.9 KiB
Python

import unittest
from datetime import datetime, timezone
from src.services.readiness.shadow_paper_sample_collection_service import ShadowPaperSampleCollectionService
class ShadowPaperSampleCollectionServiceTests(unittest.TestCase):
def test_records_daily_observation_from_latest_candidate_and_packet(self) -> None:
now = datetime(2026, 5, 17, 12, 0, tzinfo=timezone.utc)
repository = _ObservationRepository()
service = ShadowPaperSampleCollectionService(repository=repository)
payload = service.record_daily_observation(
instrument_id=1,
timeframe="1m",
operator_agreement="agree",
operator_note="aligned with operator review",
created_by="alice",
runtime_status={"current_blockers": []},
now=now,
)
self.assertEqual(payload["status"], "recorded")
self.assertEqual(payload["observation_status"], "operator_feedback_recorded")
self.assertEqual(payload["system_recommendation"], "wait_for_operator_confirmation")
self.assertEqual(payload["operator_agreement"], "agree")
self.assertEqual(payload["candidate_id"], 7)
self.assertEqual(payload["decision_packet_id"], 9)
self.assertIn("keep_daily_shadow_observation_running", payload["next_collection_actions"])
self.assertIn("build_shadow_paper_ticket_for_latest_candidate", payload["next_collection_actions"])
self.assertEqual(repository.upsert_call["created_by"], "alice")
self.assertEqual(repository.upsert_call["reason_codes"][0], "operator_feedback_recorded")
def test_runtime_blocker_records_blocked_observation_without_sample_credit_action(self) -> None:
now = datetime(2026, 5, 17, 12, 0, tzinfo=timezone.utc)
service = ShadowPaperSampleCollectionService(repository=_ObservationRepository())
payload = service.record_daily_observation(
instrument_id=1,
runtime_status={"current_blockers": ["runtime_task_stalled:market_state"]},
now=now,
)
self.assertEqual(payload["observation_status"], "runtime_blocked")
self.assertEqual(payload["system_recommendation"], "pause_runtime_blocked")
self.assertEqual(payload["next_collection_actions"], ["clear_runtime_blockers_before_counting_shadow_day"])
def test_readonly_rest_fallback_blocker_still_counts_observation(self) -> None:
now = datetime(2026, 5, 17, 12, 0, tzinfo=timezone.utc)
repository = _ObservationRepository()
service = ShadowPaperSampleCollectionService(repository=repository)
payload = service.record_daily_observation(
instrument_id=1,
runtime_status={
"current_blockers": ["market_stream_degraded:BTC-USDT:market_stream_connecting"],
"latest_market_state_by_symbol": {
"BTC-USDT": {
"freshness": {"code": "market_data_fresh"},
"data_gap_state": {"code": "market_data_gap_clear"},
}
},
},
now=now,
)
self.assertEqual(payload["observation_status"], "candidate_observed")
self.assertEqual(payload["system_recommendation"], "wait_for_operator_confirmation")
self.assertIn("build_shadow_paper_ticket_for_latest_candidate", payload["next_collection_actions"])
self.assertEqual(repository.upsert_call["payload"]["runtime_blockers"], [])
self.assertEqual(
repository.upsert_call["payload"]["suppressed_runtime_blockers"],
["market_stream_degraded:BTC-USDT:market_stream_connecting"],
)
def test_blocked_candidate_waits_before_paper_ticket_action(self) -> None:
now = datetime(2026, 5, 17, 12, 0, tzinfo=timezone.utc)
service = ShadowPaperSampleCollectionService(repository=_ObservationRepository(candidate_status="blocked"))
payload = service.record_daily_observation(
instrument_id=1,
runtime_status={"current_blockers": []},
now=now,
)
self.assertIn("wait_for_executable_candidate_before_paper_ticket", payload["next_collection_actions"])
self.assertNotIn("build_shadow_paper_ticket_for_latest_candidate", payload["next_collection_actions"])
def test_records_explicit_no_trade_and_need_more_evidence_feedback(self) -> None:
now = datetime(2026, 5, 17, 12, 0, tzinfo=timezone.utc)
for agreement in ["no_trade", "need_more_evidence"]:
with self.subTest(agreement=agreement):
repository = _ObservationRepository(candidate_status="blocked")
service = ShadowPaperSampleCollectionService(repository=repository)
payload = service.record_daily_observation(
instrument_id=1,
operator_agreement=agreement,
operator_note=f"{agreement} from Trader Cockpit",
runtime_status={"current_blockers": []},
now=now,
)
self.assertEqual(payload["observation_status"], "operator_feedback_recorded")
self.assertEqual(payload["operator_agreement"], agreement)
self.assertEqual(repository.upsert_call["operator_agreement"], agreement)
class _ObservationRepository:
def __init__(self, *, candidate_status: str = "executable") -> None:
self.upsert_call = None
self.candidate_status = candidate_status
def fetch_latest_candidate(self, **kwargs):
return {
"id": 7,
"model_code": "SWEEP_MSS_FVG",
"status": self.candidate_status,
"session_code": "NY_AM",
"confirmation_state": {"reason_codes": ["mss_confirmed"]},
"created_ts": kwargs["since"],
}
def fetch_latest_decision_packet(self, **kwargs):
return {
"id": 9,
"status": "actionable",
"action_recommendation": "confirm_ticket",
"payload": {"decision": {"reason_codes": ["candidate_actionable"]}},
"packet_ts": kwargs["since"],
}
def upsert_daily_observation(self, **kwargs):
self.upsert_call = kwargs
return {
"id": 12,
"observation_date": kwargs["observation_date"],
"timeframe": kwargs["timeframe"],
"session_code": kwargs["session_code"],
"observation_status": kwargs["observation_status"],
"system_recommendation": kwargs["system_recommendation"],
"operator_agreement": kwargs["operator_agreement"],
"candidate_id": kwargs["candidate_id"],
"decision_packet_id": kwargs["decision_packet_id"],
"paper_ticket_id": kwargs["paper_ticket_id"],
"paper_execution_id": kwargs["paper_execution_id"],
"reason_codes": kwargs["reason_codes"],
"observed_ts": kwargs["observed_ts"],
}
if __name__ == "__main__":
unittest.main()