Files
PolyWeather/web/scan_terminal_payloads.py
T
2026-06-01 06:25:27 +08:00

129 lines
3.9 KiB
Python

from __future__ import annotations
import hashlib
import json
from datetime import datetime
from typing import Any, Dict, List, Optional
SCAN_PAYLOAD_FULL_RUNWAY_HISTORY_ROWS = 9
SCAN_PAYLOAD_DEFERRED_RUNWAY_POINTS = 12
def _compact_runway_history_points(
history: Any,
*,
max_points: int,
) -> Dict[str, List[Dict[str, Any]]]:
if not isinstance(history, dict) or max_points <= 0:
return {}
compacted: Dict[str, List[Dict[str, Any]]] = {}
for runway, points in history.items():
if not isinstance(points, list) or not points:
continue
compacted[str(runway)] = points[-max_points:]
return compacted
def compact_ranked_scan_rows_for_payload(
rows: List[Dict[str, Any]],
*,
full_history_rows: int = SCAN_PAYLOAD_FULL_RUNWAY_HISTORY_ROWS,
deferred_runway_points: int = SCAN_PAYLOAD_DEFERRED_RUNWAY_POINTS,
) -> List[Dict[str, Any]]:
compacted_rows: List[Dict[str, Any]] = []
for index, row in enumerate(rows):
if index < full_history_rows:
compacted_rows.append(row)
continue
history = row.get("runway_plate_history")
if not isinstance(history, dict) or not history:
compacted_rows.append(row)
continue
next_row = dict(row)
next_row["runway_plate_history"] = _compact_runway_history_points(
history,
max_points=deferred_runway_points,
)
compacted_rows.append(next_row)
return compacted_rows
def build_scan_terminal_snapshot_id(
filters: Dict[str, Any],
rows: List[Dict[str, Any]],
summary: Dict[str, Any],
top_signal: Optional[Dict[str, Any]],
) -> str:
seed_payload = {
"filters": filters,
"summary": {
"candidate_total": summary.get("candidate_total"),
"tradable_market_count": summary.get("tradable_market_count"),
"avg_edge_percent": summary.get("avg_edge_percent"),
},
"top_signal": {
"id": (top_signal or {}).get("id"),
"edge_percent": (top_signal or {}).get("edge_percent"),
"final_score": (top_signal or {}).get("final_score"),
},
"rows": [
{
"id": row.get("id"),
"edge_percent": row.get("edge_percent"),
"final_score": row.get("final_score"),
}
for row in rows[:10]
],
}
digest = hashlib.md5(
json.dumps(seed_payload, ensure_ascii=True, sort_keys=True).encode("utf-8")
).hexdigest()
return f"scan-{digest[:10]}"
def build_stale_scan_terminal_payload(
*,
filters: Dict[str, Any],
success_payload: Dict[str, Any],
error_message: str,
failed_at: Optional[str],
) -> Dict[str, Any]:
payload = dict(success_payload)
payload["status"] = "stale"
payload["stale"] = True
payload["stale_reason"] = error_message
payload["last_success_at"] = success_payload.get("generated_at")
payload["last_failed_at"] = failed_at
payload["filters"] = filters
return payload
def build_failed_scan_terminal_payload(
*,
filters: Dict[str, Any],
error_message: str,
failed_at: Optional[str] = None,
) -> Dict[str, Any]:
return {
"generated_at": datetime.utcnow().isoformat() + "Z",
"snapshot_id": None,
"status": "failed",
"stale": False,
"stale_reason": error_message,
"last_success_at": None,
"last_failed_at": failed_at or (datetime.utcnow().isoformat() + "Z"),
"filters": filters,
"summary": {
"recommended_count": 0,
"visible_count": 0,
"candidate_total": 0,
"avg_edge_percent": None,
"avg_primary_confidence": None,
"tradable_market_count": 0,
"total_volume": 0.0,
"resolved_market_type": "maxtemp",
},
"top_signal": None,
"rows": [],
}