From aab4477ab3b49b27aeb576d65e70135f49cf7d64 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Tue, 10 Mar 2026 04:45:40 +0800 Subject: [PATCH] feat: Implement the PolyWeather dashboard including frontend components, data collection, analysis, and API endpoints. --- .env.example | 13 + docs/API_ZH.md | 15 +- frontend/app/api/city/[name]/detail/route.ts | 47 + frontend/app/page.tsx | 4 +- frontend/components/dashboard/CitySidebar.tsx | 10 +- .../components/dashboard/Dashboard.module.css | 141 ++ frontend/components/dashboard/DetailPanel.tsx | 181 ++- .../dashboard/FutureForecastModal.tsx | 134 +- frontend/components/dashboard/GuideModal.tsx | 95 +- frontend/components/dashboard/HeaderBar.tsx | 68 +- .../components/dashboard/HistoryModal.tsx | 63 +- .../components/dashboard/PanelSections.tsx | 81 +- .../dashboard/PolyWeatherDashboard.tsx | 12 +- frontend/hooks/useDashboardStore.tsx | 126 +- frontend/hooks/useI18n.tsx | 57 + frontend/hooks/useLeafletMap.ts | 129 +- frontend/lib/dashboard-utils.ts | 580 +++++--- frontend/lib/i18n.ts | 235 ++++ src/analysis/deb_algorithm.py | 60 +- src/analysis/settlement_rounding.py | 20 + src/analysis/trend_engine.py | 15 +- src/data_collection/polymarket_readonly.py | 1176 +++++++++++++++++ src/data_collection/weather_sources.py | 30 +- web/app.py | 67 +- 24 files changed, 2835 insertions(+), 524 deletions(-) create mode 100644 frontend/app/api/city/[name]/detail/route.ts create mode 100644 frontend/hooks/useI18n.tsx create mode 100644 frontend/lib/i18n.ts create mode 100644 src/analysis/settlement_rounding.py create mode 100644 src/data_collection/polymarket_readonly.py diff --git a/.env.example b/.env.example index 2725fbe1..57fe23d9 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,16 @@ HTTP_PROXY=http://127.0.0.1:7890 LOG_LEVEL=INFO ENV=production POLYWEATHER_MAP_URL=https://polyweather-pro.vercel.app/ + +# Polymarket P0 Read-Only Market Layer +POLYMARKET_MARKET_SCAN_ENABLED=true +POLYMARKET_GAMMA_URL=https://gamma-api.polymarket.com +POLYMARKET_CLOB_URL=https://clob.polymarket.com +POLYMARKET_CHAIN_ID=137 +POLYMARKET_HTTP_TIMEOUT_SEC=8 +POLYMARKET_MARKET_CACHE_TTL_SEC=180 +POLYMARKET_PRICE_CACHE_TTL_SEC=10 +POLYMARKET_DISCOVERY_PAGES=6 +POLYMARKET_DISCOVERY_LIMIT=200 +POLYMARKET_SIGNAL_MIN_LIQUIDITY=500 +POLYMARKET_SIGNAL_EDGE_PCT=2 diff --git a/docs/API_ZH.md b/docs/API_ZH.md index f0cbb0f9..281518eb 100644 --- a/docs/API_ZH.md +++ b/docs/API_ZH.md @@ -137,7 +137,10 @@ - **URL**: `/api/city/{name}/detail` - **Method**: `GET` -- **用途**: 面向后续商业化聚合视图的单请求聚合接口。 +- **用途**: 商业化聚合视图单请求接口(已接入 P0 只读价格层)。 +- **可选参数**: + - `force_refresh=true|false` + - `market_slug=`(调试优先直查;传入后优先按 slug 精确定位市场,跳过自动发现) **当前结构** @@ -153,7 +156,15 @@ **说明** - 当前生产前端主链路仍以 `/api/city/{name}` + `/api/history/{name}` 为主。 -- `/api/city/{name}/detail` 已提供聚合结构,供后续产品层扩展接入。 +- `/api/city/{name}/detail` 当前已包含 Polymarket P0 只读字段: + - `primary_market` + - `selected_condition_id` + - `yes_token` / `no_token` + - `yes_buy` / `yes_sell` / `no_buy` / `no_sell` + - `market_price`(优先 midpoint) + - `edge_percent`(`(model_probability - market_price) * 100`) + - `signal_label`(`BUY YES` / `BUY NO` / `MONITOR`) + - `websocket.asset_ids` / `websocket.condition_ids`(仅用于订阅标识,P0 不下单) --- diff --git a/frontend/app/api/city/[name]/detail/route.ts b/frontend/app/api/city/[name]/detail/route.ts new file mode 100644 index 00000000..2b30015f --- /dev/null +++ b/frontend/app/api/city/[name]/detail/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from "next/server"; + +const API_BASE = process.env.POLYWEATHER_API_BASE_URL; + +export async function GET( + req: NextRequest, + context: { params: Promise<{ name: string }> }, +) { + if (!API_BASE) { + return NextResponse.json( + { error: "POLYWEATHER_API_BASE_URL is not configured" }, + { status: 500 }, + ); + } + + const { name } = await context.params; + const forceRefresh = req.nextUrl.searchParams.get("force_refresh") ?? "false"; + const marketSlug = req.nextUrl.searchParams.get("market_slug"); + const searchParams = new URLSearchParams({ + force_refresh: forceRefresh, + }); + if (marketSlug) { + searchParams.set("market_slug", marketSlug); + } + const url = `${API_BASE}/api/city/${encodeURIComponent(name)}/detail?${searchParams.toString()}`; + + try { + const res = await fetch(url, { + headers: { Accept: "application/json" }, + cache: "no-store", + }); + if (!res.ok) { + const raw = await res.text(); + return NextResponse.json( + { error: `Backend returned ${res.status}`, detail: raw.slice(0, 300) }, + { status: 502 }, + ); + } + const data = await res.json(); + return NextResponse.json(data); + } catch (error) { + return NextResponse.json( + { error: "Failed to fetch city detail aggregate", detail: String(error) }, + { status: 500 }, + ); + } +} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index bac0ba32..af0bebb0 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -2,9 +2,9 @@ import type { Metadata } from "next"; import { DashboardEntry } from "@/components/dashboard/DashboardEntry"; export const metadata: Metadata = { - title: "PolyWeather - 天气衍生品智能地图", + title: "PolyWeather - Global Weather Intelligence Map", description: - "PolyWeather 天气衍生品智能地图,聚合 METAR、MGM、DEB、多模型预报与历史对账分析。", + "PolyWeather dashboard with METAR, MGM, DEB fusion forecast, multi-model comparison, and history reconciliation.", }; export default function HomePage() { diff --git a/frontend/components/dashboard/CitySidebar.tsx b/frontend/components/dashboard/CitySidebar.tsx index 31f3b824..4b5c440f 100644 --- a/frontend/components/dashboard/CitySidebar.tsx +++ b/frontend/components/dashboard/CitySidebar.tsx @@ -2,9 +2,11 @@ import clsx from "clsx"; import { useDashboardStore } from "@/hooks/useDashboardStore"; +import { useI18n } from "@/hooks/useI18n"; export function CitySidebar() { const store = useDashboardStore(); + const { t } = useI18n(); const sortedCities = [...store.cities].sort((a, b) => { const order = { high: 0, medium: 1, low: 2 }; return ( @@ -16,7 +18,7 @@ export function CitySidebar() { return (