Productize decision assistant archive and context cockpit batch

This commit is contained in:
Codex
2026-05-14 23:21:21 +08:00
parent 89f285ace3
commit 78ee9b3e54
47 changed files with 4400 additions and 317 deletions
+105 -6
View File
@@ -173,46 +173,75 @@ class ApiServerRoutingTests(unittest.TestCase):
)
digest_archive_status, digest_archive_payload = route_request(
"/trading/alerts/digest-history-archive",
{"instrument_id": ["1"], "limit": ["5"], "offset": ["0"], "preferred_history_kind": ["reminder"]},
{"instrument_id": ["1"], "limit": ["5"], "offset": ["0"], "preferred_history_kind": ["reminder"], "query_text": ["candidate"]},
dependencies={
"trading_alert_operator_digest_history_archive": lambda **kwargs: {
"status": "active",
"instrument_id": kwargs["instrument_id"],
"limit": kwargs["limit"],
"filters": {"preferred_history_kind": kwargs["preferred_history_kind"]},
"filters": {"preferred_history_kind": kwargs["preferred_history_kind"], "query_text": kwargs["query_text"]},
}
},
)
digest_export_status, digest_export_payload = route_request(
"/trading/alerts/digest-history-archive/export",
{"instrument_id": ["1"], "query_text": ["candidate"], "left_id": ["4"], "right_id": ["3"]},
dependencies={
"trading_alert_operator_digest_history_export": lambda **kwargs: {
"status": "active",
"export_kind": "digest_archive_bundle",
"query_text": kwargs["query_text"],
"left_id": kwargs["left_id"],
"right_id": kwargs["right_id"],
}
},
)
digest_compare_status, digest_compare_payload = route_request(
"/trading/alerts/digest-history-archive/compare",
{"instrument_id": ["1"], "left_id": ["4"], "right_id": ["3"]},
{"instrument_id": ["1"], "left_id": ["4"], "right_id": ["3"], "query_text": ["candidate"]},
dependencies={
"trading_alert_operator_digest_history_compare": lambda **kwargs: {
"status": "active",
"left_id": kwargs["left_id"],
"right_id": kwargs["right_id"],
"query_text": kwargs["query_text"],
}
},
)
workflow_archive_status, workflow_archive_payload = route_request(
"/trading/workflow-archive",
{"instrument_id": ["1"], "current_session": ["LONDON"], "next_phase": ["intraday"]},
{"instrument_id": ["1"], "current_session": ["LONDON"], "next_phase": ["intraday"], "query_text": ["handoff"]},
dependencies={
"trading_workflow_archive": lambda **kwargs: {
"status": "active",
"instrument_id": kwargs["instrument_id"],
"filters": {"current_session": kwargs["current_session"], "next_phase": kwargs["next_phase"]},
"filters": {"current_session": kwargs["current_session"], "next_phase": kwargs["next_phase"], "query_text": kwargs["query_text"]},
}
},
)
workflow_export_status, workflow_export_payload = route_request(
"/trading/workflow-archive/export",
{"instrument_id": ["1"], "query_text": ["handoff"], "left_id": ["9"], "right_id": ["8"]},
dependencies={
"trading_workflow_archive_export": lambda **kwargs: {
"status": "active",
"export_kind": "workflow_archive_bundle",
"query_text": kwargs["query_text"],
"left_id": kwargs["left_id"],
"right_id": kwargs["right_id"],
"handoff_bundle_summary": {"status": "active"},
}
},
)
workflow_compare_status, workflow_compare_payload = route_request(
"/trading/workflow-archive/compare",
{"instrument_id": ["1"], "left_id": ["9"], "right_id": ["8"]},
{"instrument_id": ["1"], "left_id": ["9"], "right_id": ["8"], "query_text": ["handoff"]},
dependencies={
"trading_workflow_archive_compare": lambda **kwargs: {
"status": "active",
"left_id": kwargs["left_id"],
"right_id": kwargs["right_id"],
"query_text": kwargs["query_text"],
}
},
)
@@ -248,17 +277,87 @@ class ApiServerRoutingTests(unittest.TestCase):
self.assertEqual(digest_archive_status, 200)
self.assertEqual(digest_archive_payload["limit"], 5)
self.assertEqual(digest_archive_payload["filters"]["preferred_history_kind"], "reminder")
self.assertEqual(digest_archive_payload["filters"]["query_text"], "candidate")
self.assertEqual(digest_export_status, 200)
self.assertEqual(digest_export_payload["export_kind"], "digest_archive_bundle")
self.assertEqual(digest_export_payload["left_id"], 4)
self.assertEqual(digest_compare_status, 200)
self.assertEqual(digest_compare_payload["left_id"], 4)
self.assertEqual(digest_compare_payload["query_text"], "candidate")
self.assertEqual(workflow_archive_status, 200)
self.assertEqual(workflow_archive_payload["filters"]["next_phase"], "intraday")
self.assertEqual(workflow_archive_payload["filters"]["query_text"], "handoff")
self.assertEqual(workflow_export_status, 200)
self.assertEqual(workflow_export_payload["export_kind"], "workflow_archive_bundle")
self.assertEqual(workflow_export_payload["handoff_bundle_summary"]["status"], "active")
self.assertEqual(workflow_compare_status, 200)
self.assertEqual(workflow_compare_payload["right_id"], 8)
self.assertEqual(workflow_compare_payload["query_text"], "handoff")
self.assertEqual(focus_get_status, 200)
self.assertEqual(focus_get_payload["focus_candidate_id"], 18)
self.assertEqual(focus_post_status, 200)
self.assertEqual(focus_post_payload["profile_key"], "desk_a")
def test_routes_decision_assistant_profile_management(self) -> None:
create_status, create_payload = route_request(
"/trading/alerts/digest-preferences/profiles",
{},
method="POST",
body={
"instrument_id": 1,
"profile_key": "desk_b",
"profile_label": "Desk B",
"digest_mode": "high_value_only",
},
dependencies={
"trading_alert_operator_digest_profiles_create": lambda **kwargs: {
"status": "created",
"instrument_id": kwargs["instrument_id"],
"profile_key": kwargs["profile_key"],
"profile_label": kwargs["profile_label"],
}
},
)
rename_status, rename_payload = route_request(
"/trading/alerts/digest-preferences/profiles/desk_b/rename",
{},
method="POST",
body={
"instrument_id": 1,
"profile_label": "Desk Bravo",
},
dependencies={
"trading_alert_operator_digest_profiles_rename": lambda **kwargs: {
"status": "renamed",
"instrument_id": kwargs["instrument_id"],
"profile_key": kwargs["profile_key"],
"profile_label": kwargs["profile_label"],
}
},
)
delete_status, delete_payload = route_request(
"/trading/alerts/digest-preferences/profiles/desk_b/delete",
{},
method="POST",
body={"instrument_id": 1},
dependencies={
"trading_alert_operator_digest_profiles_delete": lambda **kwargs: {
"status": "deleted",
"instrument_id": kwargs["instrument_id"],
"profile_key": kwargs["profile_key"],
}
},
)
self.assertEqual(create_status, 200)
self.assertEqual(create_payload["status"], "created")
self.assertEqual(create_payload["profile_key"], "desk_b")
self.assertEqual(rename_status, 200)
self.assertEqual(rename_payload["profile_key"], "desk_b")
self.assertEqual(rename_payload["profile_label"], "Desk Bravo")
self.assertEqual(delete_status, 200)
self.assertEqual(delete_payload["status"], "deleted")
def test_routes_maintenance_summary(self) -> None:
status, payload = route_request(
"/trading/maintenance-summary",
+37
View File
@@ -104,8 +104,11 @@ class ApiStaticServerTests(unittest.TestCase):
"/trading/alerts/quality-summary",
"/trading/alerts/operator-digest",
"/trading/alerts/digest-preferences",
"/trading/alerts/digest-preferences/profiles",
"/trading/alerts/digest-history-archive",
"/trading/alerts/digest-history-archive/export",
"/trading/workflow-archive",
"/trading/workflow-archive/export",
"/trading/assistant-focus",
"/trading/runtime-incidents",
"/trading/maintenance-summary",
@@ -130,6 +133,40 @@ class ApiStaticServerTests(unittest.TestCase):
):
self.assertNotIn(forbidden, app_js)
def test_dashboard_exposes_workflow_archive_operations_surface(self) -> None:
app_file = _resolve_static_file("/static/app.js")
self.assertIsNotNone(app_file)
app_js = app_file.read_text(encoding="utf-8")
for expected in (
"workflow operations",
"workflow drift",
"Day Group:",
"Session Group:",
"operation snapshots",
"drift next prompt",
"Search Text",
"Export Archive Results",
"Export Archive Compare",
"Export Workflow Results",
"Export Workflow Compare",
"Replay Bridge",
"Note Bridge",
"Handoff Bridge",
"Clear Workflow Bridge",
"provider health cards",
"Open Visual Bridge",
"Open Target",
"Open Embed",
"archive trend",
"archive baseline",
"Pin Baseline",
"Compare To Baseline",
"workflow long-range",
"workflow baseline",
):
self.assertIn(expected, app_js)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,92 @@
import unittest
from src.services.operator.archive_long_range_projection import (
build_digest_archive_baseline_summary,
build_digest_archive_long_range_summary,
build_workflow_archive_baseline_summary,
build_workflow_archive_long_range_summary,
)
class ArchiveLongRangeProjectionTests(unittest.TestCase):
def test_digest_long_range_and_baseline_summary(self) -> None:
items = [
{
"snapshot_id": 5,
"created_ts": "2026-05-14T09:00:00+00:00",
"top_summary": "Latest digest",
"high_value_reminder_count": 2,
},
{
"snapshot_id": 4,
"created_ts": "2026-05-10T09:00:00+00:00",
"top_summary": "Weekly digest",
"high_value_reminder_count": 1,
},
{
"snapshot_id": 1,
"created_ts": "2026-04-20T09:00:00+00:00",
"top_summary": "Older digest",
"high_value_reminder_count": 0,
},
]
trend = build_digest_archive_long_range_summary(items)
baseline = build_digest_archive_baseline_summary(items)
self.assertEqual(trend["status"], "active")
self.assertEqual(trend["recent_7d_count"], 2)
self.assertEqual(trend["recent_30d_count"], 3)
self.assertGreaterEqual(trend["covered_day_span"], 20)
self.assertTrue(trend["next_review_prompt"])
self.assertEqual(baseline["status"], "active")
self.assertEqual(baseline["latest_snapshot_id"], 5)
self.assertEqual(baseline["baseline_snapshot_id"], 1)
self.assertTrue(baseline["compare_ready"])
def test_workflow_long_range_and_baseline_summary(self) -> None:
items = [
{
"snapshot_id": 9,
"created_ts": "2026-05-14T09:00:00+00:00",
"current_session": "LONDON",
"next_phase": "intraday",
"ready_for_handoff_count": 2,
},
{
"snapshot_id": 8,
"created_ts": "2026-05-08T09:00:00+00:00",
"current_session": "NY_AM",
"next_phase": "post_trade",
"ready_for_handoff_count": 1,
},
{
"snapshot_id": 3,
"created_ts": "2026-04-18T09:00:00+00:00",
"current_session": "ASIA_PM",
"next_phase": "pre_market",
"ready_for_handoff_count": 0,
},
]
trend = build_workflow_archive_long_range_summary(items)
baseline = build_workflow_archive_baseline_summary(items)
self.assertEqual(trend["status"], "active")
self.assertEqual(trend["recent_7d_count"], 2)
self.assertEqual(trend["recent_30d_count"], 3)
self.assertEqual(trend["distinct_session_count"], 3)
self.assertEqual(baseline["status"], "active")
self.assertEqual(baseline["latest_snapshot_id"], 9)
self.assertEqual(baseline["baseline_snapshot_id"], 3)
self.assertTrue(baseline["suggested_compare_prompt"])
def test_empty_items_return_idle(self) -> None:
self.assertEqual(build_digest_archive_long_range_summary([])["status"], "idle")
self.assertEqual(build_digest_archive_baseline_summary([])["status"], "idle")
self.assertEqual(build_workflow_archive_long_range_summary([])["status"], "idle")
self.assertEqual(build_workflow_archive_baseline_summary([])["status"], "idle")
if __name__ == "__main__":
unittest.main()
+24 -27
View File
@@ -8,8 +8,6 @@ from scripts import check_local_demo_readiness, check_trading_delivery_readiness
ROOT = Path(__file__).resolve().parents[2]
README = ROOT / "README.md"
RUNBOOK = ROOT / "RUNBOOK.md"
TRADING_PLAN_DOC = ROOT / "docs" / "AI_ICT_实盘交易分析系统开发计划_v1.md"
ICT_BOOK_DOC = ROOT / "docs" / "ICT交易全书_出版终校版.md"
TRAINING_PUBLIC_DOC = ROOT / "docs" / "training_console_public_surface_v1.md"
TRAINING_PACKAGE_SMOKE_DOC = ROOT / "docs" / "examples" / "training_case_package_smoke_m6.md"
TRADING_INDEX_HTML = ROOT / "src" / "web" / "index.html"
@@ -18,15 +16,20 @@ TRAINING_HTML = ROOT / "src" / "web" / "training.html"
TRAINING_JS = ROOT / "src" / "web" / "training.js"
OLD_TRAINING_DOC = ROOT / "docs" / "training_case_engine_m1_m6_product_readme.md"
NONEXISTENT_VISUAL_PLAN_NAME = "AI_ICT_" + "视觉盯盘与主动告警增强方案_v1.md"
ARCHIVED_DORMANT_DOCS = {
"AI_ICT_下一阶段开发计划_v1.md",
"AI_ICT_下一阶段开发任务清单_v1.md",
"AI_ICT_TradingAgents融合实施方案_v1.md",
"AI_ICT_方案一系统边界图与模块融合图_v1.md",
ACTIVE_TOP_LEVEL_DOCS = {
"AI_ICT_分析助手主线增强开发计划_2026-05-14_v1.md",
"AI_ICT_全自动开发执行流程_v1.md",
"AI_ICT_实盘交易分析决策助手下一阶段开发方案_v1.md",
"AI_ICT_实盘交易分析系统开发计划_v1.md",
"AI_ICT_实盘辅助决策系统开发方案_v2.md",
"AI_ICT_自动化执行层_P15-PXX闭环任务清单_v1.md",
"AI_ICT_需求文档剩余功能与延期项清单_2026-05-14_v1.md",
"ICT交易全书_出版终校版.md",
"training_console_public_surface_v1.md",
}
OPTIONAL_DORMANT_DOCS = {
"AI_ICT_repair_bridge_protocol_v1.md",
"AI_ICT_research_council脚手架与数据表草案_v1.md",
ARCHIVED_DOCS = {
"AI_ICT_TradingAgents_P0-P6合理性审查与优化结论_v1.md",
"AI_ICT_TradingAgents_P0自动任务清单_v1.md",
"AI_ICT_TradingAgents_P1自动任务清单_v1.md",
@@ -35,44 +38,38 @@ OPTIONAL_DORMANT_DOCS = {
"AI_ICT_TradingAgents_P4自动任务清单_v1.md",
"AI_ICT_TradingAgents_P5自动任务清单_v1.md",
"AI_ICT_TradingAgents_P6自动任务清单_v1.md",
"AI_ICT_TradingAgents融合实施方案_v1.md",
"AI_ICT_TradingAgents融合实施方案_最终版.md",
"AI_ICT_下一阶段开发任务清单_v1.md",
"AI_ICT_下一阶段开发计划_v1.md",
"AI_ICT_repair_bridge_protocol_v1.md",
"AI_ICT_research_council脚手架与数据表草案_v1.md",
"AI_ICT_外部repair_agent接入说明_v1.md",
"AI_ICT_实盘交易分析决策助手未完成需求清单_2026-05-14_v1.md",
"AI_ICT_方案一系统边界图与模块融合图_v1.md",
"AI_ICT_盯盘告警开发任务清单_v1.md",
"AI_ICT_盯盘告警技术设计稿_v1.md",
"AI_ICT_盯盘告警架构图_v1.md",
"AI_ICT_自动化执行层_P15-PXX闭环任务清单_v1.md",
"AI_ICT_自动化执行层_P7监控成熟度与晋级路由_v1.md",
"AI_ICT_自动化流程使用教程_v1.md",
}
class DocumentationConsistencyTests(unittest.TestCase):
def test_docs_inventory_is_current_and_old_training_product_doc_is_gone(self) -> None:
def test_docs_inventory_is_current_and_archived_docs_are_not_top_level(self) -> None:
top_level_docs = {
path.name
for path in (ROOT / "docs").iterdir()
if path.is_file()
}
self.assertEqual(
top_level_docs,
{
"AI_ICT_全自动开发执行流程_v1.md",
"AI_ICT_实盘交易分析决策助手下一阶段开发方案_v1.md",
"AI_ICT_实盘交易分析决策助手未完成需求清单_2026-05-14_v1.md",
"AI_ICT_实盘交易分析系统开发计划_v1.md",
"AI_ICT_实盘辅助决策系统开发方案_v2.md",
"ICT交易全书_出版终校版.md",
"training_console_public_surface_v1.md",
}
| OPTIONAL_DORMANT_DOCS,
)
self.assertTrue(TRAINING_PACKAGE_SMOKE_DOC.is_file())
self.assertEqual(top_level_docs, ACTIVE_TOP_LEVEL_DOCS)
archive_docs = {
path.name
for path in (ROOT / "docs" / "archive").iterdir()
if path.is_file()
}
self.assertTrue(ARCHIVED_DORMANT_DOCS <= archive_docs)
self.assertTrue(ARCHIVED_DOCS <= archive_docs)
self.assertFalse(OLD_TRAINING_DOC.exists())
def test_readme_and_runbook_use_current_training_entry_and_not_old_doc(self) -> None:
@@ -191,7 +191,23 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
self.assertIn("workflow_history_archive", summary["workflow_artifact_summary"])
self.assertIn("workflow_history_archive_count", summary["workflow_artifact_summary"])
self.assertIn("workflow_archive_compare", summary["workflow_artifact_summary"])
self.assertIn("workflow_archive_operations_summary", summary["workflow_artifact_summary"])
self.assertIn("workflow_archive_multi_day_drift_review", summary["workflow_artifact_summary"])
self.assertIn("workflow_archive_bridge_summary", summary["workflow_artifact_summary"])
self.assertIn("workflow_persistence_status", summary["workflow_artifact_summary"])
self.assertEqual(summary["workflow_artifact_summary"]["workflow_archive_operations_summary"]["status"], "active")
self.assertEqual(
summary["workflow_artifact_summary"]["workflow_archive_multi_day_drift_review"]["status"],
"insufficient_history",
)
self.assertEqual(summary["workflow_artifact_summary"]["workflow_archive_bridge_summary"]["status"], "active")
self.assertIn("long_range_trend_summary", summary["workflow_artifact_summary"]["workflow_history_archive"])
self.assertIn("baseline_pin_summary", summary["workflow_artifact_summary"]["workflow_history_archive"])
self.assertEqual(summary["workflow_artifact_summary"]["workflow_history_archive"]["long_range_trend_summary"]["status"], "active")
self.assertEqual(summary["workflow_artifact_summary"]["workflow_history_archive"]["baseline_pin_summary"]["status"], "insufficient_history")
self.assertIn("daily_groups", summary["workflow_artifact_summary"]["workflow_history_archive"])
self.assertIn("session_groups", summary["workflow_artifact_summary"]["workflow_history_archive"])
self.assertIn("bridge_summary", summary["workflow_artifact_summary"]["workflow_history_archive"])
self.assertIn("assistant_loop_queue", summary)
self.assertIn("assistant_loop_summary", summary)
self.assertTrue(summary["assistant_loop_queue"])
@@ -223,6 +239,10 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
self.assertEqual(summary["decision_timeline"][0]["title"], "Current Packet")
self.assertEqual(summary["evidence_chain"][0]["title"], "Decision Gate")
self.assertEqual(summary["evidence_chain"][1]["title"], "Launch Mode")
self.assertTrue(any(item["title"] == "Focus Candidate" for item in summary["evidence_chain"]))
focus_evidence = next(item for item in summary["evidence_chain"] if item["title"] == "Focus Candidate")
self.assertTrue(focus_evidence["focus_related"])
self.assertEqual(focus_evidence["focus_candidate_id"], 18)
self.assertEqual(summary["explanation_drilldown"][0]["title"], "Decision Logic")
self.assertEqual(summary["reason_citations"][0]["source_path"], "decision_packet.why_allowed[0]")
self.assertEqual(summary["learning_rollup"]["status"], "active")
@@ -230,6 +250,9 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
self.assertEqual(summary["review_entrypoints"]["status"], "active")
self.assertEqual(summary["review_entrypoints"]["focus_candidate_id"], 18)
self.assertGreaterEqual(summary["review_entrypoints"]["focus_note_count"], 1)
self.assertIn("Candidate #18", summary["review_entrypoints"]["focus_note_prefill"])
self.assertIn("focus-candidate", summary["review_entrypoints"]["focus_note_tags"])
self.assertIn("focus_note_prompt", summary["review_entrypoints"])
self.assertEqual(summary["review_entrypoints"]["actions"][0]["action_id"], "open_operator_notes")
self.assertTrue(summary["review_entrypoints"]["note_templates"])
self.assertIn("review_entry_action_queue", summary["review_entrypoints"])
@@ -368,8 +391,15 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
self.assertIn("digest_history_archive", summary["digest_surface"])
self.assertIn("digest_history_archive_count", summary["digest_surface"])
self.assertIn("digest_archive_compare", summary["digest_surface"])
self.assertIn("focus_related_reminders", summary["digest_surface"])
self.assertGreaterEqual(summary["digest_surface"]["focus_related_reminder_count"], 1)
self.assertIsNotNone(summary["digest_surface"]["next_focus_related_reminder"])
self.assertIn("digest_persistence_status", summary["digest_surface"])
self.assertIn("next_digest_history_archive_review", summary["digest_surface"])
self.assertIn("long_range_trend_summary", summary["digest_surface"]["digest_history_archive"])
self.assertIn("baseline_pin_summary", summary["digest_surface"]["digest_history_archive"])
self.assertEqual(summary["digest_surface"]["digest_history_archive"]["long_range_trend_summary"]["status"], "active")
self.assertEqual(summary["digest_surface"]["digest_history_archive"]["baseline_pin_summary"]["status"], "insufficient_history")
self.assertIn("next_high_value_change", summary["digest_surface"])
self.assertIn("digest_review_back_actions", summary["digest_surface"])
self.assertIn("digest_review_back_action_count", summary["digest_surface"])
@@ -427,6 +457,7 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
self.assertTrue(summary["digest_history_preview"])
self.assertEqual(summary["digest_history_preview"][0]["kind"], "reminder")
self.assertEqual(summary["personalized_reminders"][0]["severity"], "high")
self.assertTrue(any(item.get("focus_related") for item in summary["personalized_reminders"]))
self.assertIn("importance_score", summary["personalized_reminders"][0])
self.assertIn("importance_template", summary["personalized_reminders"][0])
self.assertIn("high_value", summary["personalized_reminders"][0])
@@ -434,6 +465,9 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
self.assertEqual(summary["setup_learning_rollup"]["top_lessons"][0]["lesson_type"], "journal_pattern")
self.assertEqual(summary["setup_learning_rollup"]["session_code"], "LONDON")
self.assertEqual(summary["setup_learning_rollup"]["session_code_source"], "focus_candidate")
self.assertEqual(summary["setup_learning_rollup"]["focus_candidate_id"], 18)
self.assertEqual(summary["setup_learning_rollup"]["focus_replay_scope"], "candidate_scoped")
self.assertIn("focus_replay_prompt", summary["setup_learning_rollup"])
self.assertIn("replay_statistics", summary["setup_learning_rollup"])
self.assertIn("learning_attribution_summary", summary["setup_learning_rollup"])
self.assertIn("failure_reason_attribution", summary["setup_learning_rollup"])
@@ -591,6 +625,8 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
self.assertTrue(summary["external_context_summary"]["announcement_titles"])
self.assertIn("macro_news_calendar", summary["external_context_summary"])
self.assertGreaterEqual(summary["external_context_summary"]["macro_news_calendar_count"], 3)
self.assertIn("macro_news_provider_inventory", summary["external_context_summary"])
self.assertGreaterEqual(summary["external_context_summary"]["macro_news_provider_count"], 1)
self.assertIn("event_window_summary", summary["external_context_summary"])
self.assertIn("event_window_status", summary["external_context_summary"])
self.assertIn("event_window_prompt", summary["external_context_summary"])
@@ -598,11 +634,24 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
self.assertTrue(summary["external_context_summary"]["exchange_anomaly_window"])
self.assertIn("window_type", summary["external_context_summary"]["macro_news_calendar"][0])
self.assertIn("chart_readonly_sources", summary["external_context_summary"])
self.assertIn("chart_provider_registry", summary["external_context_summary"])
self.assertGreaterEqual(summary["external_context_summary"]["chart_provider_registry_count"], 3)
self.assertIn("chart_stale_source_evidence", summary["external_context_summary"])
self.assertGreaterEqual(summary["external_context_summary"]["chart_readonly_source_count"], 1)
self.assertEqual(summary["external_context_summary"]["chart_stale_source_count"], 0)
self.assertEqual(summary["external_context_summary"]["chart_adapter_mode"], "live_reference")
self.assertEqual(summary["external_context_summary"]["chart_primary_mode"], "internal_review_packet_html")
self.assertIn("exchange_context_sources", summary["external_context_summary"])
self.assertGreaterEqual(summary["external_context_summary"]["exchange_context_source_count"], 3)
self.assertIn("provider_health_cards", summary["external_context_summary"])
self.assertGreaterEqual(summary["external_context_summary"]["provider_health_card_count"], 3)
self.assertIn("provider_health_summary", summary["external_context_summary"])
self.assertEqual(summary["external_context_summary"]["provider_health_summary"]["status"], "active")
self.assertIn("visual_evidence_bridge_summary", summary["external_context_summary"])
self.assertEqual(summary["external_context_summary"]["visual_evidence_bridge_summary"]["status"], "active")
self.assertIn("visual_reference_cards", summary["external_context_summary"])
self.assertGreaterEqual(summary["external_context_summary"]["visual_reference_card_count"], 3)
self.assertIsNotNone(summary["external_context_summary"]["visual_reference_next_prompt"])
self.assertIn("live", summary["external_context_summary"]["chart_context_prompt"].lower())
self.assertIn("CPI", summary["external_context_summary"]["headline"])
self.assertTrue(summary["no_trade_reason_items"])
+2 -1
View File
@@ -140,7 +140,7 @@ class RunUntilBlockedTests(unittest.TestCase):
changed_files=[
"scripts/run_until_blocked.py",
"tests/unit/test_run_until_blocked.py",
"docs/AI_ICT_自动化流程使用教程_v1.md",
"docs/archive/AI_ICT_自动化流程使用教程_v1.md",
"SESSION.md",
"TASKS.md",
],
@@ -1455,3 +1455,4 @@ class RunUntilBlockedTests(unittest.TestCase):
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,88 @@
import unittest
from src.services.operator.workflow_archive_projection import build_workflow_archive_operations_view
class WorkflowArchiveProjectionTests(unittest.TestCase):
def test_builds_daily_session_and_drift_views(self) -> None:
items = [
{
"snapshot_id": 3,
"created_ts": "2026-05-14T09:30:00+00:00",
"current_session": "LONDON",
"next_phase": "intraday",
"next_artifact_kind": "intraday_digest",
"artifact_count": 4,
"ready_for_handoff_count": 2,
"needs_update_count": 1,
"pending_capture_count": 1,
"workflow_body": "London plan ready.",
"next_artifact_prompt": "Check intraday follow-through.",
"phase_briefs": [
{"phase": "pre_market", "archive_body": "Macro and session prep done."},
{"phase": "intraday", "archive_body": "Watch the first liquidity sweep."},
],
},
{
"snapshot_id": 2,
"created_ts": "2026-05-13T15:45:00+00:00",
"current_session": "NY_AM",
"next_phase": "post_trade",
"next_artifact_kind": "post_trade_recap",
"artifact_count": 2,
"ready_for_handoff_count": 1,
"needs_update_count": 1,
"pending_capture_count": 0,
"workflow_body": "NY recap pending.",
"next_artifact_prompt": "Write post-trade recap.",
"phase_briefs": [
{"phase": "pre_market", "archive_body": "Prep was lighter."},
{"phase": "intraday", "archive_body": "NY sweep already happened."},
],
},
{
"snapshot_id": 1,
"created_ts": "2026-05-13T12:15:00+00:00",
"current_session": "LONDON",
"next_phase": "intraday",
"next_artifact_kind": "intraday_digest",
"artifact_count": 3,
"ready_for_handoff_count": 1,
"needs_update_count": 1,
"pending_capture_count": 1,
"workflow_body": "Earlier London handoff.",
"next_artifact_prompt": "Re-check the London setup.",
"phase_briefs": [
{"phase": "pre_market", "archive_body": "Prep baseline."},
{"phase": "intraday", "archive_body": "Initial sweep watch."},
],
},
]
result = build_workflow_archive_operations_view(items)
self.assertEqual(result["daily_group_count"], 2)
self.assertEqual(result["session_group_count"], 2)
self.assertEqual(result["operations_summary"]["status"], "active")
self.assertEqual(result["operations_summary"]["latest_group_date"], "2026-05-14")
self.assertEqual(result["operations_summary"]["most_active_session"], "LONDON")
self.assertEqual(result["multi_day_drift_review"]["status"], "active")
self.assertEqual(result["multi_day_drift_review"]["drift_status"], "drift_detected")
self.assertEqual(result["bridge_summary"]["status"], "active")
self.assertEqual(result["bridge_summary"]["latest_snapshot_id"], 3)
self.assertGreaterEqual(result["multi_day_drift_review"]["changed_phase_count"], 1)
self.assertTrue(result["daily_groups"][0]["phase_names"])
self.assertEqual(result["session_groups"][0]["session_code"], "LONDON")
def test_handles_empty_archive(self) -> None:
result = build_workflow_archive_operations_view([])
self.assertEqual(result["daily_group_count"], 0)
self.assertEqual(result["session_group_count"], 0)
self.assertEqual(result["operations_summary"]["status"], "idle")
self.assertEqual(result["multi_day_drift_review"]["status"], "insufficient_history")
self.assertEqual(result["bridge_summary"]["status"], "idle")
if __name__ == "__main__":
unittest.main()