fix: scope dashboard snapshot failures

This commit is contained in:
ZhuLinsen
2026-08-30 16:54:57 +08:00
parent fc49613608
commit ca36ab561d
2 changed files with 118 additions and 19 deletions

View File

@@ -3,8 +3,8 @@
from __future__ import annotations
from datetime import date, datetime
from typing import Any, Dict, List, Optional, Set, Tuple
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from src.config import Config, get_config
from src.repositories.portfolio_repo import PortfolioRepository
@@ -61,7 +61,9 @@ class DashboardOverviewService:
str,
Dict[str, Optional[Dict[str, Any]]],
] = {}
regions_with_unknown_trade_date: Set[str] = set()
snapshot_candidate_ranks_by_region: Dict[str, Dict[str, int]] = {}
global_detail_failure_ranks: List[int] = []
unknown_trade_date_failure_ranks_by_region: Dict[str, List[int]] = {}
latest_reviews: List[Dict[str, Any]] = []
invalid_snapshot_count = 0
detail_failure_count = 0
@@ -83,47 +85,58 @@ class DashboardOverviewService:
review_count = int(result.get("total") or 0)
if len(latest_reviews) < 5:
latest_reviews.extend(reviews[: 5 - len(latest_reviews)])
for review in reviews:
for review_index, review in enumerate(reviews):
review_rank = scanned_count + review_index
context_snapshot = review.get("context_snapshot")
if not isinstance(context_snapshot, dict):
detail_failure_count += 1
global_detail_failure_ranks.append(review_rank)
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
global_detail_failure_ranks.append(review_rank)
continue
for raw_region, raw_snapshot in (snapshot_container or {}).items():
region = str(raw_region).strip().lower()
if region and not isinstance(raw_snapshot, dict):
invalid_snapshot_count += 1
regions_with_unknown_trade_date.add(region)
unknown_trade_date_failure_ranks_by_region.setdefault(region, []).append(
review_rank
)
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()
try:
canonical_trade_date = date.fromisoformat(raw_trade_date).isoformat()
canonical_trade_date = self._canonical_trade_date(raw_trade_date)
except Exception:
invalid_snapshot_count += 1
regions_with_unknown_trade_date.add(region)
unknown_trade_date_failure_ranks_by_region.setdefault(region, []).append(
review_rank
)
continue
region_ranks = snapshot_candidate_ranks_by_region.setdefault(region, {})
try:
snapshot = MarketLightSnapshot.model_validate(raw_snapshot).model_dump()
except Exception:
invalid_snapshot_count += 1
snapshot_candidates_by_region.setdefault(region, {}).setdefault(
canonical_trade_date,
None,
)
region_candidates = snapshot_candidates_by_region.setdefault(region, {})
if canonical_trade_date not in region_candidates:
region_candidates[canonical_trade_date] = None
region_ranks[canonical_trade_date] = review_rank
continue
snapshot_region = str(snapshot.get("region") or "").strip().lower()
region_candidates = snapshot_candidates_by_region.setdefault(region, {})
if snapshot_region != region:
invalid_snapshot_count += 1
region_candidates.setdefault(canonical_trade_date, None)
if canonical_trade_date not in region_candidates:
region_candidates[canonical_trade_date] = None
region_ranks[canonical_trade_date] = review_rank
continue
snapshot["trade_date"] = canonical_trade_date
if region_candidates.get(canonical_trade_date) is None:
region_candidates[canonical_trade_date] = snapshot
region_ranks[canonical_trade_date] = review_rank
scanned_count += len(reviews)
if scanned_count >= review_count:
break
@@ -147,17 +160,25 @@ class DashboardOverviewService:
unavailable_current_regions: List[str] = []
for region, candidates in snapshot_candidates_by_region.items():
ordered_dates = sorted(candidates, reverse=True)
if detail_failure_count or region in regions_with_unknown_trade_date:
unavailable_current_regions.append(region)
continue
current = candidates[ordered_dates[0]] if ordered_dates else None
if current is None:
candidate_ranks = snapshot_candidate_ranks_by_region.get(region, {})
failure_ranks = [
*global_detail_failure_ranks,
*unknown_trade_date_failure_ranks_by_region.get(region, []),
]
current_date = ordered_dates[0] if ordered_dates else None
current = candidates[current_date] if current_date else None
current_rank = candidate_ranks.get(current_date, scanned_count) if current_date else scanned_count
if current is None or any(rank < current_rank for rank in failure_ranks):
unavailable_current_regions.append(region)
continue
selected = [current]
if len(ordered_dates) > 1:
previous = candidates[ordered_dates[1]]
if previous is not None:
previous_date = ordered_dates[1]
previous = candidates[previous_date]
previous_rank = candidate_ranks.get(previous_date, scanned_count)
if previous is not None and not any(
rank < previous_rank for rank in failure_ranks
):
selected.append(previous)
snapshots_by_region[region] = selected
@@ -203,6 +224,14 @@ class DashboardOverviewService:
source_limitations=limitations,
)
@staticmethod
def _canonical_trade_date(raw_trade_date: str) -> str:
"""Parse supported ISO date forms consistently on Python 3.10+."""
raw = str(raw_trade_date or "").strip()
compact = len(raw) == 8 and raw.isascii() and raw.isdigit()
date_format = "%Y%m%d" if compact else "%Y-%m-%d"
return datetime.strptime(raw, date_format).date().isoformat()
def _personal_block(self) -> Dict[str, Any]:
data = {
"watchlist_count": None,

View File

@@ -384,6 +384,76 @@ def test_malformed_nested_snapshot_container_does_not_promote_older_snapshots()
assert "latest_completed_snapshot_unavailable" in payload["market"]["meta"]["limitations"]
def test_older_malformed_detail_does_not_hide_valid_current_and_baseline() -> None:
dependencies = _dependencies()
reviews = [
_review(1, "2026-08-29T09:00:00+08:00"),
_review(2, "2026-08-28T09:00:00+08:00"),
_review(3, "2026-08-27T09:00:00+08:00"),
]
dependencies["history_service"].get_history_list.side_effect = lambda **kwargs: (
_history_page(dependencies["history_service"], reviews, kwargs)
if kwargs.get("report_type") == "market_review"
else {"items": [], "total": 0}
)
def detail(record_id: int) -> dict:
if record_id == 3:
return {"context_snapshot": "{invalid-json"}
trade_date = "2026-08-29" if record_id == 1 else "2026-08-28"
return {
"context_snapshot": {
"market_light_snapshots": {
"cn": _snapshot("cn", trade_date, 60 if record_id == 1 else 50, "yellow")
}
}
}
dependencies["history_service"].get_history_detail_by_id.side_effect = detail
payload = DashboardOverviewService(**dependencies).get_overview()
assert payload["market"]["data"]["latest_snapshots"]["cn"]["trade_date"] == "2026-08-29"
assert payload["what_changed"]["data"]["current_trade_dates"] == {"cn": "2026-08-29"}
assert payload["what_changed"]["data"]["previous_trade_dates"] == {"cn": "2026-08-28"}
assert "market_review_detail_partial" in payload["market"]["meta"]["limitations"]
def test_malformed_detail_between_candidates_blocks_only_the_baseline() -> None:
dependencies = _dependencies()
reviews = [
_review(1, "2026-08-29T09:00:00+08:00"),
_review(2, "2026-08-28T09:00:00+08:00"),
_review(3, "2026-08-27T09:00:00+08:00"),
]
dependencies["history_service"].get_history_list.side_effect = lambda **kwargs: (
_history_page(dependencies["history_service"], reviews, kwargs)
if kwargs.get("report_type") == "market_review"
else {"items": [], "total": 0}
)
def detail(record_id: int) -> dict:
if record_id == 2:
return {"context_snapshot": "{invalid-json"}
trade_date = "2026-08-29" if record_id == 1 else "2026-08-27"
return {
"context_snapshot": {
"market_light_snapshots": {
"cn": _snapshot("cn", trade_date, 60 if record_id == 1 else 40, "yellow")
}
}
}
dependencies["history_service"].get_history_detail_by_id.side_effect = detail
payload = DashboardOverviewService(**dependencies).get_overview()
assert payload["market"]["data"]["latest_snapshots"]["cn"]["trade_date"] == "2026-08-29"
assert payload["what_changed"]["data"]["current_trade_dates"] == {"cn": "2026-08-29"}
assert payload["what_changed"]["data"]["previous_trade_dates"] == {}
assert "previous_completed_snapshot_unavailable:cn" in payload["what_changed"]["meta"]["limitations"]
def test_malformed_region_snapshot_does_not_promote_older_region_snapshot() -> None:
dependencies = _dependencies()
original_detail = dependencies["history_service"].get_history_detail_by_id.side_effect