From 00bd2539fa1d9a6c23ef659aa873c2069f33bd43 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Wed, 4 Mar 2026 19:34:39 +0800 Subject: [PATCH] feat: introduce PolyWeather web map application with FastAPI backend and Leaflet frontend. --- web/app.py | 47 +++----------- web/static/app.js | 101 +++-------------------------- web/static/index.html | 9 +-- web/static/style.css | 147 ------------------------------------------ 4 files changed, 19 insertions(+), 285 deletions(-) diff --git a/web/app.py b/web/app.py index c563ac7f..60d1e0a7 100644 --- a/web/app.py +++ b/web/app.py @@ -26,11 +26,6 @@ from src.utils.config_loader import load_config from src.data_collection.weather_sources import WeatherDataCollector from src.data_collection.city_risk_profiles import CITY_RISK_PROFILES from src.analysis.deb_algorithm import calculate_dynamic_weights, get_deb_accuracy -from src.data_collection.polymarket_client import ( - fetch_weather_markets, - get_city_markets, - compute_divergence, -) # ────────────────────────────────────────────────────────── # Setup @@ -76,6 +71,7 @@ ALIASES = { # ────────────────────────────────────────────────────────── _cache: Dict[str, Dict] = {} CACHE_TTL = 300 +CACHE_TTL_ANKARA = 60 # Ankara measurement updates frequent, narrower cache def _sf(v) -> Optional[float]: @@ -91,12 +87,15 @@ def _sf(v) -> Optional[float]: # ────────────────────────────────────────────────────────── # Core Analysis (replicates bot_listener logic → JSON) # ────────────────────────────────────────────────────────── -def _analyze(city: str) -> Dict[str, Any]: +def _analyze(city: str, force_refresh: bool = False) -> Dict[str, Any]: """Fetch, analyse, and return structured weather data for one city.""" # Check cache - cached = _cache.get(city) - if cached and _time.time() - cached["t"] < CACHE_TTL: - return cached["d"] + ttl = CACHE_TTL_ANKARA if city.lower() == "ankara" else CACHE_TTL + + if not force_refresh: + cached = _cache.get(city) + if cached and _time.time() - cached["t"] < ttl: + return cached["d"] info = CITIES[city] lat, lon, is_f = info["lat"], info["lon"], info["f"] @@ -495,32 +494,6 @@ def _analyze(city: str) -> Dict[str, Any]: "updated_at": datetime.now(timezone.utc).isoformat(), } - # ── 16. Polymarket odds (web-only, non-blocking) ── - try: - proxy = _config.get("proxy") - fetch_weather_markets(proxy=proxy, timeout=10) - # We don't filter by target_date here, we want ALL active contracts for the city - city_mkts = get_city_markets(city) - if city_mkts: - result["polymarket"] = { - "markets": [ - { - "question": m["question"], - "yes_price": m["yes_price"], - "volume": m.get("volume"), - "threshold": m.get("threshold"), - "threshold_unit": m.get("threshold_unit"), - "url": m.get("url", ""), - } - for m in city_mkts[:5] - ], - "divergence": compute_divergence( - city_mkts, probabilities, sym, use_fahrenheit=info.get("f", False) - ), - } - except Exception as e: - logger.debug(f"Polymarket data skipped for {city}: {e}") - _cache[city] = {"t": _time.time(), "d": result} return result @@ -556,13 +529,13 @@ async def list_cities(): @app.get("/api/city/{name}") -async def city_detail(name: str): +async def city_detail(name: str, force_refresh: bool = False): """Return full weather analysis for a single city.""" name = name.lower().strip().replace("-", " ") name = ALIASES.get(name, name) if name not in CITIES: raise HTTPException(404, detail=f"Unknown city: {name}") - return _analyze(name) + return _analyze(name, force_refresh=force_refresh) @app.get("/api/history/{name}") diff --git a/web/static/app.js b/web/static/app.js index a65d1519..a8d0126f 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -220,9 +220,11 @@ async function fetchCities() { } } -async function fetchCityDetail(cityName) { +async function fetchCityDetail(cityName, force = false) { const urlName = cityName.replace(/\s/g, "-"); - const res = await fetch(`/api/city/${encodeURIComponent(urlName)}`); + const res = await fetch( + `/api/city/${encodeURIComponent(urlName)}?force_refresh=${force}`, + ); if (!res.ok) throw new Error(`HTTP ${res.status}`); return await res.json(); } @@ -230,13 +232,13 @@ async function fetchCityDetail(cityName) { // ────────────────────────────────────────────────────────── // Load & Render City Detail // ────────────────────────────────────────────────────────── -async function loadCityDetail(cityName) { +async function loadCityDetail(cityName, force = false) { selectedCity = cityName; selectedForecastDate = null; // Reset selection for new city setActiveCityItem(cityName); setSelectedMarker(cityName); - if (cityDataCache[cityName]) { + if (!force && cityDataCache[cityName]) { renderPanel(cityDataCache[cityName]); const cData = cityDataCache[cityName]; if (cData.lat != null && cData.lon != null) { @@ -252,7 +254,7 @@ async function loadCityDetail(cityName) { showLoading(true); try { - const data = await fetchCityDetail(cityName); + const data = await fetchCityDetail(cityName, force); cityDataCache[cityName] = data; saveCache(); renderPanel(data); @@ -326,8 +328,6 @@ function renderPanel(data) { renderForecast(data); // AI renderAI(data); - // Market Odds - renderMarket(data); // Risk renderRisk(data); } @@ -925,77 +925,6 @@ function renderAI(data) { container.innerHTML = text; } -function renderMarket(data) { - const widget = document.getElementById("marketFloatingWidget"); - const container = document.getElementById("marketOdds"); - if ( - !data.polymarket || - !data.polymarket.markets || - data.polymarket.markets.length === 0 - ) { - widget.classList.add("hidden"); - return; - } - - widget.classList.remove("hidden"); - const { markets, divergence } = data.polymarket; - let html = ""; - - markets.forEach((mkt) => { - // Find matching divergence signal - const divSignal = divergence.find((d) => d.question === mkt.question); - - let probText = - mkt.yes_price != null ? `${Math.round(mkt.yes_price * 100)}¢` : "N/A"; - let noText = - mkt.yes_price != null - ? `${Math.round((1 - mkt.yes_price) * 100)}¢` - : "N/A"; - - html += ` -