Files
PolyWeather/web/services/city_payloads.py
T
2569718930@qq.com e9cc9a5eb1 修复机构落地页构建失败并恢复 Polymarket 市场扫描
InstitutionalLandingPage.tsx 补充 "use client" 指令以通过 Next.js 15 构建。
polymarket_readonly.py 集成 WebSocket 报价缓存加速价格获取。
city_payloads.py 复用 Polymarket 层构建真实市场扫描数据替代空返回。

Constraint: 市场扫描需在无 Polymarket 价格 UI 的前提下提供数据
Tested: npm run build 通过
2026-05-25 00:41:56 +08:00

221 lines
9.0 KiB
Python

"""City payload builders for API-facing response shapes."""
from __future__ import annotations
from typing import Any, Dict, Optional
from web.core import _is_excluded_model_name
TURKISH_MGM_CITIES = {"ankara", "istanbul"}
_polymarket_layer = None
def _get_polymarket_layer():
global _polymarket_layer
if _polymarket_layer is None:
from src.data_collection.polymarket_readonly import PolymarketReadOnlyLayer
_polymarket_layer = PolymarketReadOnlyLayer()
return _polymarket_layer
def _top_probability_bucket(distribution: Any) -> Optional[Dict[str, Any]]:
if not isinstance(distribution, list):
return None
candidates = [row for row in distribution if isinstance(row, dict)]
if not candidates:
return None
return max(candidates, key=lambda row: float(row.get("probability") or -1.0))
def build_city_summary_payload(data: Dict[str, Any]) -> Dict[str, Any]:
return {
"name": data.get("name"),
"display_name": data.get("display_name"),
"icao": data.get("risk", {}).get("icao"),
"utc_offset_seconds": data.get("utc_offset_seconds"),
"local_time": data.get("local_time"),
"temp_symbol": data.get("temp_symbol"),
"current": {
"temp": data.get("current", {}).get("temp"),
"obs_time": data.get("current", {}).get("obs_time"),
"settlement_source": data.get("current", {}).get("settlement_source"),
"settlement_source_label": data.get("current", {}).get(
"settlement_source_label"
),
},
"deb": {"prediction": data.get("deb", {}).get("prediction")},
"deviation_monitor": data.get("deviation_monitor") or {},
"risk": {
"level": data.get("risk", {}).get("level"),
"warning": data.get("risk", {}).get("warning"),
},
"updated_at": data.get("updated_at"),
}
def build_city_market_scan_payload(
data: Dict[str, Any],
market_slug: Optional[str] = None,
target_date: Optional[str] = None,
lite: bool = False,
scan_filters: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
local_date = str(data.get("local_date") or "").strip()
requested_date = str(target_date or "").strip()
selected_date = requested_date or local_date
try:
layer = _get_polymarket_layer()
probabilities = data.get("probabilities") or {}
distribution = probabilities.get("distribution") or []
top_bucket = _top_probability_bucket(distribution)
model_probability = (
float(top_bucket.get("probability"))
if (isinstance(top_bucket, dict) and top_bucket.get("probability") is not None)
else None
)
scan = layer.build_market_scan(
city=data.get("name"),
target_date=selected_date,
temperature_bucket=top_bucket,
model_probability=model_probability,
probability_distribution=distribution,
temp_symbol=str(data.get("temp_symbol") or ""),
forced_market_slug=market_slug,
include_related_buckets=not lite,
scan_filters=scan_filters,
)
return {
"market_scan": scan,
"selected_date": selected_date,
"fetched_at": data.get("updated_at"),
}
except Exception:
import traceback
traceback.print_exc()
return {
"market_scan": {"available": False},
"selected_date": selected_date,
"fetched_at": data.get("updated_at"),
}
def build_city_detail_payload(
data: Dict[str, Any],
market_slug: Optional[str] = None,
target_date: Optional[str] = None,
) -> Dict[str, Any]:
market_payload = build_city_market_scan_payload(
data,
market_slug=market_slug,
target_date=target_date,
)
market_scan = market_payload.get("market_scan")
return {
"city": data.get("name"),
"fetched_at": data.get("updated_at"),
"overview": {
"name": data.get("name"),
"display_name": data.get("display_name"),
"icao": data.get("risk", {}).get("icao"),
"airport": data.get("risk", {}).get("airport"),
"lat": data.get("lat"),
"lon": data.get("lon"),
"local_time": data.get("local_time"),
"local_date": data.get("local_date"),
"temp_symbol": data.get("temp_symbol"),
"current_temp": data.get("current", {}).get("temp"),
"settlement_source": data.get("current", {}).get("settlement_source"),
"settlement_source_label": data.get("current", {}).get(
"settlement_source_label"
),
"settlement_station": data.get("settlement_station") or {},
"deb_prediction": data.get("deb", {}).get("prediction"),
"risk_level": data.get("risk", {}).get("level"),
"risk_warning": data.get("risk", {}).get("warning"),
"updated_at": data.get("updated_at"),
},
"official": {
"available": bool(data.get("current", {}).get("temp") is not None),
"metar": {
"observation_time": data.get("airport_current", {}).get("obs_time"),
"obs_age_min": data.get("airport_current", {}).get("obs_age_min"),
"report_time": data.get("airport_current", {}).get("report_time"),
"receipt_time": data.get("airport_current", {}).get("receipt_time"),
"raw_metar": data.get("airport_current", {}).get("raw_metar"),
"current": data.get("airport_current") or {},
},
"taf": data.get("taf") or {},
"weather_gov": {},
"mgm": data.get("mgm") or {},
"mgm_nearby": data.get("mgm_nearby") or [],
"nearby_source": data.get("nearby_source")
or (
"mgm"
if str(data.get("name") or "").lower() in TURKISH_MGM_CITIES
else "metar_cluster"
),
"airport_primary": data.get("airport_primary") or {},
"airport_primary_today_obs": data.get("airport_primary_today_obs") or [],
"official_nearby": data.get("official_nearby") or [],
"official_network_source": data.get("official_network_source"),
"official_network_status": data.get("official_network_status") or {},
"network_lead_signal": data.get("network_lead_signal") or {},
"network_spread_signal": data.get("network_spread_signal") or {},
"center_station_candidate": data.get("center_station_candidate"),
"airport_vs_network_delta": data.get("airport_vs_network_delta"),
},
"timeseries": {
"metar_recent_obs": data.get("metar_recent_obs") or [],
"metar_today_obs": data.get("metar_today_obs") or [],
"settlement_today_obs": data.get("settlement_today_obs") or [],
"hourly": data.get("hourly") or {},
"mgm_hourly": (data.get("mgm") or {}).get("hourly", []),
"forecast_daily": (data.get("forecast") or {}).get("daily", []),
},
"models": {
k: v
for k, v in (data.get("multi_model") or {}).items()
if not _is_excluded_model_name(k)
},
"deb": data.get("deb") or {},
"multi_model_daily": data.get("multi_model_daily") or {},
"probabilities": data.get("probabilities") or {"mu": None, "distribution": []},
"dynamic_commentary": data.get("dynamic_commentary")
or {"summary": "", "notes": []},
"intraday_meteorology": data.get("intraday_meteorology")
or _build_intraday_meteorology(data),
"vertical_profile_signal": data.get("vertical_profile_signal") or {},
"taf": data.get("taf") or {},
"market_scan": market_scan,
"risk": data.get("risk"),
"settlement_station": data.get("settlement_station") or {},
"airport_primary": data.get("airport_primary") or {},
"official_nearby": data.get("official_nearby") or [],
"official_network_source": data.get("official_network_source"),
"official_network_status": data.get("official_network_status") or {},
"network_lead_signal": data.get("network_lead_signal") or {},
"network_spread_signal": data.get("network_spread_signal") or {},
"center_station_candidate": data.get("center_station_candidate"),
"airport_vs_network_delta": data.get("airport_vs_network_delta"),
"airport_current": data.get("airport_current") or {},
"amos": data.get("amos") or {},
"nearby_source": data.get("nearby_source")
or (
"mgm"
if str(data.get("name") or "").lower() in TURKISH_MGM_CITIES
else "metar_cluster"
),
"ai_analysis": data.get("ai_analysis") or "",
"errors": {},
}
def _build_intraday_meteorology(data: Dict[str, Any]) -> Dict[str, Any]:
from web.analysis_service import _build_intraday_meteorology as build_intraday
return build_intraday(data)