diff --git a/docs/dashboard-overview-api.md b/docs/dashboard-overview-api.md index 3c07c38cf..e8e001e5b 100644 --- a/docs/dashboard-overview-api.md +++ b/docs/dashboard-overview-api.md @@ -29,7 +29,7 @@ - red/yellow/green 状态变化输出 `market..status`。 - change item 的 quality 取 current/previous 两份快照中较差的一侧;任一侧 partial 会降低整个 what_changed 块,任一侧 unavailable 不生成可靠变化项。 - 没有第二份有效快照时,返回 `previous_completed_snapshot_unavailable`;部分 region 缺基线时整个块标记为 `partial`,并追加 `previous_completed_snapshot_unavailable:`,避免把“缺少基线”误解为“没有变化”。不会临时拉行情或生成一份“当前”快照冒充基线。 -- 历史详情读取失败,或投影出的 `context_snapshot` 不是 JSON object(例如损坏的 JSON 字符串)时,不再把其余更旧记录提升为 current/latest;对应快照和变化对比保持不可用并返回 limitation。 +- 历史详情读取失败、投影出的 `context_snapshot` 不是 JSON object,或其中已存在的 `market_light_snapshots` 不是 object(例如损坏的 JSON 字符串/数组)时,不再把其余更旧记录提升为 current/latest;对应快照和变化对比保持不可用并返回 limitation。 - 快照校验失败时仍保留其 `trade_date` 的目标位置:最新日期无有效快照时不回退到更旧 current,最近更早日期无有效快照时不越过它继续寻找更旧 previous。同一目标日期存在其他有效重跑快照时,仍可使用该日期的有效版本。 - 外层市场键必须与快照内部 `region` 一致;例如 `cn` 键下的 `region=us` 快照会按无效快照处理,不能进入 A 股的 current/previous 或变化项。 - 历史扫描被截断等 market limitations 会同步进入 what_changed;剩余快照不能在来源历史不完整时被标记为 fresh。 diff --git a/src/services/dashboard_overview_service.py b/src/services/dashboard_overview_service.py index 728242991..cc23e71a4 100644 --- a/src/services/dashboard_overview_service.py +++ b/src/services/dashboard_overview_service.py @@ -88,6 +88,10 @@ class DashboardOverviewService: if not isinstance(context_snapshot, dict): detail_failure_count += 1 continue + snapshot_container = context_snapshot.get("market_light_snapshots") + if snapshot_container is not None and not isinstance(snapshot_container, dict): + detail_failure_count += 1 + continue raw_snapshots = self._extract_snapshots(context_snapshot) for region, raw_snapshot in raw_snapshots.items(): raw_trade_date = str(raw_snapshot.get("trade_date") or "").strip() diff --git a/tests/test_dashboard_overview_api.py b/tests/test_dashboard_overview_api.py index e110468e6..6a46d1ccf 100644 --- a/tests/test_dashboard_overview_api.py +++ b/tests/test_dashboard_overview_api.py @@ -296,6 +296,26 @@ def test_malformed_projected_context_does_not_promote_older_snapshots() -> None: assert "latest_completed_snapshot_unavailable" in payload["market"]["meta"]["limitations"] +def test_malformed_nested_snapshot_container_does_not_promote_older_snapshots() -> None: + dependencies = _dependencies() + original_detail = dependencies["history_service"].get_history_detail_by_id.side_effect + + def detail_with_malformed_container(record_id: int) -> dict: + if record_id == 1: + return {"context_snapshot": {"market_light_snapshots": "{invalid-json"}} + return original_detail(record_id) + + dependencies["history_service"].get_history_detail_by_id.side_effect = detail_with_malformed_container + + payload = DashboardOverviewService(**dependencies).get_overview() + + assert payload["market"]["data"]["latest_snapshots"] == {} + assert payload["what_changed"]["data"]["current_trade_dates"] == {} + assert payload["what_changed"]["data"]["items"] == [] + assert "market_review_detail_partial" in payload["market"]["meta"]["limitations"] + assert "latest_completed_snapshot_unavailable" in payload["market"]["meta"]["limitations"] + + def test_invalid_latest_snapshot_does_not_promote_an_older_trade_date() -> None: dependencies = _dependencies() reviews = [