From eb2ab0afcfd0fe0a121d03f5751bf540bf63576e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=91=E6=9D=B0?= Date: Tue, 14 Apr 2026 02:10:31 +0800 Subject: [PATCH] Preserve diagnostics in live-mode failure payloads The previous hardening pass still dropped source diagnostics and data-quality context once live-mode serialized a dual-lane failure. Keep those fields when a structured CombinedSignalFailure reaches the websocket layer so consumers can distinguish provider mismatch, stale data, and other degraded cases even when no final signal exists. Constraint: Follow-on fix after 63858bf should stay minimal and not reopen unrelated executor/calendar work Rejected: Fold this into a larger amend of the prior commit | history is already shared and the delta is a single behavioral correction Confidence: high Scope-risk: narrow Reversibility: clean Directive: When failure exceptions carry structured diagnostics, live serializers must preserve them instead of flattening to a generic message Tested: python -m pytest web_dashboard/backend/tests/test_executors.py web_dashboard/backend/tests/test_services_migration.py web_dashboard/backend/tests/test_api_smoke.py orchestrator/tests/test_market_calendar.py orchestrator/tests/test_live_mode.py orchestrator/tests/test_application_service.py orchestrator/tests/test_quant_runner.py orchestrator/tests/test_llm_runner.py -q Tested: python -m compileall orchestrator web_dashboard/backend Tested: npm run build (web_dashboard/frontend) Not-tested: real websocket consumers against provider-backed failure paths --- orchestrator/live_mode.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/orchestrator/live_mode.py b/orchestrator/live_mode.py index 3d6d8480..e7cb8517 100644 --- a/orchestrator/live_mode.py +++ b/orchestrator/live_mode.py @@ -59,9 +59,11 @@ class LiveMode: @staticmethod def _serialize_error(*, ticker: str, date: str, exc: Exception) -> dict: - reason_codes = [] - if isinstance(exc, ValueError) and "both quant and llm signals are None" in str(exc): + reason_codes = list(getattr(exc, "reason_codes", ()) or ()) + if not reason_codes and isinstance(exc, ValueError) and "both quant and llm signals are None" in str(exc): reason_codes.append(ReasonCode.BOTH_SIGNALS_UNAVAILABLE.value) + source_diagnostics = dict(getattr(exc, "source_diagnostics", {}) or {}) + data_quality = getattr(exc, "data_quality", None) return { "contract_version": CONTRACT_VERSION, "ticker": ticker, @@ -76,9 +78,9 @@ class LiveMode: "degradation": { "degraded": bool(reason_codes), "reason_codes": reason_codes, - "source_diagnostics": {}, + "source_diagnostics": source_diagnostics, }, - "data_quality": None, + "data_quality": data_quality, } async def run_once(self, tickers: List[str], date: Optional[str] = None) -> List[dict]: