Productize decision assistant archive and context cockpit batch
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
@@ -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"])
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user