from __future__ import annotations import hashlib import json from datetime import datetime from typing import Any from zoneinfo import ZoneInfo from backend.features.screener.cross_section import finalize_factor_rows from backend.features.screener.engine import FACTOR_MINIMUM_COVERAGE from backend.features.screener.technical import build_technical_rows SHANGHAI = ZoneInfo("Asia/Shanghai") def build_factor_snapshot( trade_date: str, inputs: dict[str, Any], coverage: dict[str, float], sources: list[str], ) -> dict[str, Any]: ready = { dataset: coverage.get(dataset, 0) >= minimum for dataset, minimum in FACTOR_MINIMUM_COVERAGE.items() } ready["limit_events"] = coverage.get("limit_events", 0) >= 1 rows = finalize_factor_rows(build_technical_rows(trade_date, inputs, ready), ready) canonical = json.dumps( {"trade_date": trade_date, "coverage": coverage, "rows": rows}, ensure_ascii=False, separators=(",", ":"), sort_keys=True, ) version = hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:16] return { "trade_date": trade_date, "version": version, "observed_at": datetime.now(SHANGHAI).isoformat(timespec="seconds"), "coverage": coverage, "sources": sorted(set(sources)), "rows": rows, }