diff --git a/.env.example b/.env.example index 3ac5b2b3..2541c9bd 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,13 @@ UVICORN_WORKERS=1 UID=1000 GID=1000 POLYWEATHER_STATE_STORAGE_MODE=sqlite +# Realtime chart event store. Production should use Redis Stream; local/single-process +# development can set POLYWEATHER_EVENT_STORE=sqlite. +POLYWEATHER_EVENT_STORE=redis +POLYWEATHER_REDIS_URL=redis://polyweather_redis:6379/0 +POLYWEATHER_REDIS_STREAM_KEY=stream:city_observation +POLYWEATHER_REDIS_STREAM_MAXLEN=50000 +POLYWEATHER_REDIS_REQUIRED=true # Backend CORS allowlist. Add your Vercel production/preview domains when # NEXT_PUBLIC_POLYWEATHER_API_BASE_URL points browsers directly at this backend. WEB_CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000,https://polyweather.top,https://www.polyweather.top,https://api.polyweather.top diff --git a/CHANGELOG.md b/CHANGELOG.md index 67743409..e2a15a32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 1.8.1 - 2026-05-28 + +### 文档与发布 +- README / README_ZH 改用 `frontend/public/static/web.png` 与 `frontend/public/static/tel.png` 作为产品截图,并移除旧 `docs/images` README 截图引用。 +- 同步版本源到 `1.8.1`,刷新 API、Supabase、技术债、PolygonScan 验证等文档标题版本。 +- 更新前端、实时事件、数据源、模型栈与服务文档,补齐 Redis Stream + SSE Patch、DEB hourly consensus、城市当地时间图表、legacy 高斯图表叠加、跑道/CoWIN 曲线和中英文 Telegram 推送口径。 + +### 当前线上口径确认 +- 生产实时层为“HTTP snapshot + SSE patch + replayable event store”;前端只消费 `/api/events`,不直接连接 Redis。 +- `deb_hourly_consensus.v1` 是峰值窗口与 DEB 曲线展示的优先小时路径;DEB 不作为实测来源。 +- AMSC/AMOS 跑道曲线和香港 CoWIN 6087 参考站曲线按城市当地时间展示,结算跑道高亮,辅助跑道弱化。 + + ## 1.8.0 - 2026-05-27 ### 新增与重构 diff --git a/README.md b/README.md index 3e4c06d7..bb733fad 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,19 @@ Public docs center: `/docs/intro` on the main site (bilingual product documentat ## Product Screenshots -### Global Dashboard +### Realtime Terminal -![PolyWeather global dashboard](docs/images/demo_map.png) +![PolyWeather realtime terminal](frontend/public/static/web.png) -### City Analysis (Ankara) +### Telegram Runway Alerts -![PolyWeather Ankara analysis](docs/images/demo_ankara.png) +![PolyWeather Telegram runway alerts](frontend/public/static/tel.png) ## Star History [![Star History Chart](https://api.star-history.com/svg?repos=yangyuan-zhen/PolyWeather&type=Date)](https://star-history.com/#yangyuan-zhen/PolyWeather&Date) -## Product Status (2026-05-23) +## Product Status (2026-05-28) - Subscription live: `Pro Monthly 10 USDC`. - Points system live: earn via group chat, welcome bonus (+20), first-message-of-day bonus (+2), weekly participation rewards. @@ -31,12 +31,21 @@ Public docs center: `/docs/intro` on the main site (bilingual product documentat - Auto-reconciliation live: event listener + periodic confirm loop. - Ops dashboard live: `/ops` for memberships, leaderboard, manual point grants, and payment incident triage. - Lightweight observability live: `/healthz`, `/api/system/status`, `/metrics`. +- Realtime terminal live: visible city charts subscribe through `/api/events?cities=...&since_revision=...`, receive `city_observation_patch.v1` SSE patches, and replay short gaps from Redis Stream in production or SQLite fallback in local/single-node mode. +- Chart refresh is observation-driven: live patches merge into the current chart without a loading overlay; only visible charts run a 60s no-patch fallback, and returning from a background browser tab triggers a foreground catch-up refresh. +- Temperature charts default to All Day, keep an optional Peak window derived from the DEB hourly path, and render all timestamps in the selected city's local time. +- The chart core has been split into focused logic/canvas/state modules; Recharts now receives explicit measured dimensions to avoid 0x0 rendering and disappearing curves. +- DEB hourly consensus (`deb_hourly_consensus.v1`) is now the preferred hourly forecast path for peak-window detection and chart overlays; DEB remains a forecast curve, never an observation source. +- Legacy Gaussian probability is rendered as horizontal probability bands and a `mu` reference line on the chart, rather than as a fake time-series curve. +- Settlement runway curves are visible by default for AMSC/AMOS cities; the configured settlement runway is highlighted and auxiliary runways are shown as secondary context. +- Hong Kong uses CoWIN station `6087` (Po Leung Kuk Choi Kai Yau School) as the 1-minute reference-station curve, with HKO 10-minute observations kept as the official meteorological layer. +- Telegram airport/runway pushes are bilingual by default and use settlement-endpoint runway temperatures for slope/current/summary copy. - Runtime state, cache, and core offline training/backfill flows now use SQLite as the primary path; legacy JSON/JSONL files remain only for migration, export, and explicit fallback input. - EMOS/CRPS calibration is wired and trainable, but production should stay on `legacy` or `emos_shadow`; `emos_primary` is only for candidates that pass local offline evaluation and manual rollout. - Intraday analysis is now positioned as a professional meteorology read: headline, confidence, base/upside/downside paths, next observation point, evidence chain, failure modes, and confirmation rules. - Intraday modal now blocks stale cached detail during refresh, so users do not briefly trade off old city/date data before full detail arrives. -- City decision cards now include the AI airport read: METAR, DEB, model cluster, and the AI expected-high center are resolved before mapping the result to temperature buckets. -- AI airport reads now use in-page memory cache, browser `localStorage`, and backend short-TTL cache; returning from another dashboard tab restores existing stream text or final results before any new request is needed. +- Terminal city cards now combine settlement observations, DEB hourly consensus, model cluster context, calibrated probability, and market-bucket mapping without blocking the chart on AI text generation. +- Terminal data uses page memory cache, browser `localStorage`, backend short-TTL cache, SSE patch replay, and foreground refresh so returning from another tab restores the latest visible chart state quickly. - Market bucket matching now uses the full `all_buckets` surface and strict exact / range / or-higher / or-lower direction checks, reducing bad matches to unreasonable tail buckets. - The card label “model-market difference” means `model probability - market-implied probability`; positive values indicate weather probability above market pricing, while negative values indicate the YES is already priced more fully. - Calibrated model probability is now the primary probability panel. It shows the active production probability engine (legacy Gaussian or EMOS), while model consensus remains a secondary reference. @@ -62,8 +71,9 @@ See: [AGPL-3.0 & Commercial Boundary](docs/OPEN_CORE_POLICY.md) - Aggregates observations and forecasts for 51 monitored cities. - Uses DEB (Dynamic Error Balancing) to blend multi-model highs. +- Builds a DEB-weighted hourly consensus path for peak-window logic and chart display. - Generates settlement-oriented calibrated probability buckets (`mu` + bucket distribution) via legacy Gaussian or EMOS/CRPS calibration. -- Adds city decision cards that combine AI airport reads, expected-high centers, full market-bucket mapping, and model-market difference in one view. +- Adds city decision cards that combine live observations, expected-high centers, full market-bucket mapping, and model-market difference in one view. - Reuses one analysis core across web dashboard and Telegram bot. - Adds payment audit trails, replay tooling, and incident visibility in ops. - Adds peak-window-oriented intraday analysis with meteorology headline, path buckets, evidence chain, invalidation rules, and confirmation rules. @@ -88,7 +98,10 @@ flowchart LR WX --> AMOS["AMOS runway sensors (Korea)"] WX --> HKO["HKO / CWA / NOAA / Official settlement sources"] - API --> ANA["DEB + Trend + Probability + Market Scan"] + API --> ANA["DEB + Hourly Consensus + Probability + Market Scan"] + API --> SSE["SSE /api/events"] + WX --> SSE + SSE --> EVENT["Redis Stream / SQLite Event Log"] ANA --> PAY["Payment State (Intent + Event + Confirm Loop)"] ANA --> STATE["SQLite runtime state"] ``` @@ -125,6 +138,8 @@ npm run dev - `TAF` is used as an airport-side confirmation layer, not as the main temperature model. - Calibrated probability uses legacy Gaussian (default) or EMOS/CRPS when evaluated; model vote counts remain an explanatory consensus line, not the final probability. - Browser extension remains a lightweight monitoring + basic-bias product, while the site holds the full analysis experience. +- Realtime terminal charts use SSE patches plus replayable event storage; full HTTP detail remains the authoritative snapshot. +- Chart observations are shown in the city's local time, not the browser timezone. ## Runtime Data (Recommended on VPS) @@ -134,8 +149,14 @@ Use external runtime storage to avoid SQLite/git conflicts: POLYWEATHER_RUNTIME_DATA_DIR=/var/lib/polyweather POLYWEATHER_DB_PATH=/var/lib/polyweather/polyweather.db POLYWEATHER_STATE_STORAGE_MODE=sqlite +POLYWEATHER_EVENT_STORE=redis +POLYWEATHER_REDIS_URL=redis://polyweather_redis:6379/0 +POLYWEATHER_REDIS_STREAM_MAXLEN=50000 +POLYWEATHER_REDIS_REQUIRED=true ``` +For local development or a strict single-process fallback, keep `POLYWEATHER_EVENT_STORE=sqlite`. + ## EMOS Local Training Do not run full EMOS retraining on a small VPS. The VPS should collect data and load approved calibration files; training should run on a local/dev machine using a copied production SQLite database: @@ -214,5 +235,5 @@ curl http://127.0.0.1:8000/api/payments/runtime ## Version -- Version: `v1.8.0` -- Last Updated: `2026-05-23` +- Version: `v1.8.1` +- Last Updated: `2026-05-28` diff --git a/README_ZH.md b/README_ZH.md index decfb821..3cd2b69d 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -6,15 +6,15 @@ ## 产品截图 -### 全球看板 +### 实时终端 -![PolyWeather 全球地图看板](docs/images/demo_map.png) +![PolyWeather 实时终端](frontend/public/static/web.png) -### 城市分析(Ankara) +### Telegram 跑道推送 -![PolyWeather Ankara 分析页](docs/images/demo_ankara.png) +![PolyWeather Telegram 跑道推送](frontend/public/static/tel.png) -## 当前产品状态(2026-05-23) +## 当前产品状态(2026-05-28) - 已上线订阅制:`Pro 月付 10 USDC`。 - 已上线积分体系:群内发言赚分 + 首次发言欢迎奖励 (+20) + 每日首条消息奖励 (+2) + 每周全员参与奖。 @@ -26,6 +26,15 @@ - 已上线轻量运营后台:`/ops`(会员、周榜、补分、支付异常单)。 - 已上线轻量可观测性:`/healthz`、`/api/system/status`、`/metrics`。 - 已补最小外部监控栈:Prometheus + Alertmanager + Grafana + Telegram 告警 relay。 +- 实时终端已切换到可重放事件流:可见城市图表通过 `/api/events?cities=...&since_revision=...` 订阅 `city_observation_patch.v1`,生产环境使用 Redis Stream 做短窗口 replay,本地/单进程可回退 SQLite event log。 +- 图表刷新由实测事件驱动:SSE patch 直接合并到当前曲线,不弹 loading 遮罩;只有可见图表启用 60 秒无 patch 兜底,浏览器后台返回前台时会主动补齐最新 detail。 +- 城市图表默认展示“全天”,可选“高温”窗口由 DEB hourly path 推导;所有图表横轴都按城市当地时间展示,不按用户浏览器时区。 +- 核心图表组件已拆分为逻辑、状态与 canvas 渲染模块;Recharts 使用 `ResizeObserver` 后的明确宽高,规避 0x0 渲染和长时间挂页后曲线消失。 +- DEB hourly consensus(`deb_hourly_consensus.v1`)已作为峰值窗口和图表 DEB 曲线的优先小时路径;DEB 仍然是预测曲线,不作为实测来源。 +- legacy 高斯概率在图表上展示为概率温度带和 `mu` 参考线,不再伪装成一条时间序列曲线。 +- AMSC/AMOS 城市的结算跑道曲线默认展示并高亮,辅助跑道作为弱化曲线保留;釜山单跑道只展示 `SR/SL` 结算跑道,不再重复显示 AMOS 聚合线。 +- 香港默认展示 CoWIN `6087`(保良局陈守仁小学)1 分钟参考站曲线,HKO 10 分钟实测保留为官方气象层。 +- Telegram 机场/跑道推送默认中英文双语,并统一使用结算端点跑道温度计算当前值、15 分钟趋势和文案。 - 运行态状态、缓存与核心离线训练/回填链路已完成 SQLite 主路径收口;legacy JSON/JSONL 仅保留给迁移、导出与显式回退输入。 - EMOS/CRPS 校准链路已接通,但生产主概率保持 `legacy` 或 `emos_shadow`;`emos_primary` 只在本地离线评估通过并手动灰度后启用。 - 官方增强站网已统一接入: @@ -37,15 +46,15 @@ - `CWA`(台湾) - 东京现已接入羽田 `JMA AMeDAS` 10 分钟温度作为官方增强层。 - 已支持 Dashboard 定向预热 worker / cron 路径,运行态在 `/api/system/status` 与 `/ops` 可见。 -- `/ops` 现已展示缓存桶数量、summary cache hit/miss 与 prewarm heartbeat。 +- `/ops` 现已展示缓存桶数量、summary cache hit/miss 与运行态 heartbeat。 - 今日日内分析已改为“专业气象判断台”:顶部先给气象主判断、置信度、基准/上修/下修路径、下一观测点,再展示证据链、失效条件、确认条件和模型层。 - 日内分析弹窗在 full detail / market detail 同步完成前会锁住旧内容并显示刷新状态,避免用户短暂看到上一轮缓存数据后误判。 -- 城市决策卡已接入 AI 机场报文解读:先用 METAR、DEB、多模型集群和 AI 最高温中枢判断天气路径,再映射到温度桶。 -- AI 机场报文解读现在同时使用页面内存缓存、浏览器 `localStorage` 和后端短 TTL 缓存;从其他选项卡切回决策卡时会优先恢复已有流式内容或最终结果。 +- 终端城市卡已改为结构化实况 + DEB hourly consensus + 多模型集群 + 校准概率 + 市场温度桶,不再让图表等待 AI 文案生成。 +- 终端数据同时使用页面内存缓存、浏览器 `localStorage`、后端短 TTL 缓存、SSE patch replay 和前台恢复刷新;从其他选项卡切回时会优先恢复最新可见图表状态。 - 市场温度桶匹配已改为完整 `all_buckets` 映射,按 exact / range / or higher / or lower 方向严格匹配,避免把天气中枢错配到不合理尾部桶。 - 决策卡中的“模型-市场差”口径为 `模型概率 - 市场隐含概率`,正值表示天气概率高于市场报价,负值表示市场已经更充分计价。 - 概率区已改为”校准模型概率”;默认展示生产概率引擎输出(legacy 高斯或 EMOS),模型共识作为辅助参考。 -- 今日日内结构解读已支持可选 Groq 改写层,失败时自动回退规则文案。 +- 今日日内结构解读以规则与结构化信号为主,AI 文案只作为可降级辅助层,不替代实测、DEB、TAF 或结算逻辑。 - 前端设计系统全面重构:统一 CSS token 体系、消除 !important 滥用(134→49)、合并断点(18→10)、数百处硬编码颜色迁移至 CSS 变量、添加 ARIA 无障碍属性和键盘导航。完整审查记录见 `docs/frontend-ui-design-review.md`。 ## 许可证与商用边界(重要) @@ -62,8 +71,9 @@ - 聚合 51 个监控城市的实测与预报数据。 - DEB(Dynamic Error Balancing)融合多模型最高温。 +- 构建 DEB 加权小时共识曲线,用于峰值窗口判断和图表默认 DEB 展示。 - 输出结算导向校准概率分布(`mu` + 温度桶),通过 legacy 高斯或 EMOS/CRPS 校准引擎。 -- 地图城市决策卡把 AI 机场报文解读、最高温中枢、完整市场温度桶和模型-市场差放在同一张卡中展示。 +- 地图城市决策卡把结构化实况、最高温中枢、完整市场温度桶和模型-市场差放在同一张卡中展示。 - Web 仪表盘与 Telegram Bot 复用同一分析内核。 - 支付链路具备事件重放、SQLite 审计事件与 RPC 容灾能力。 - 官方增强层与跑道级传感器支持按国家 provider 统一接入(含韩国 AMOS 首尔/釜山跑道实测),不替代机场主站、METAR 或明确官方结算站。 @@ -85,7 +95,10 @@ flowchart LR WX --> OM["Open-Meteo"] WX --> HKO["HKO / CWA / NOAA 等官方结算源"] - API --> ANA["DEB + 趋势 + 概率 + 市场扫描"] + API --> ANA["DEB + 小时共识 + 概率 + 市场扫描"] + API --> SSE["SSE /api/events"] + WX --> SSE + SSE --> EVENT["Redis Stream / SQLite Event Log"] ANA --> PAY["支付状态(Intent + Event + Confirm Loop)"] ANA --> STATE["SQLite runtime state
legacy files only for migration/export fallback"] ``` @@ -121,8 +134,14 @@ npm run dev POLYWEATHER_RUNTIME_DATA_DIR=/var/lib/polyweather POLYWEATHER_DB_PATH=/var/lib/polyweather/polyweather.db POLYWEATHER_STATE_STORAGE_MODE=sqlite +POLYWEATHER_EVENT_STORE=redis +POLYWEATHER_REDIS_URL=redis://polyweather_redis:6379/0 +POLYWEATHER_REDIS_STREAM_MAXLEN=50000 +POLYWEATHER_REDIS_REQUIRED=true ``` +本地开发或严格单进程兜底可使用 `POLYWEATHER_EVENT_STORE=sqlite`。 + ## EMOS 本地训练流程 低配 VPS 只负责采集、服务和加载已通过评估的参数,不建议在 VPS 上跑 EMOS 全量训练。训练前先从 VPS 拉 SQLite 副本到本地: @@ -230,5 +249,5 @@ POLYWEATHER_OPS_ADMIN_EMAILS=yhrsc30@gmail.com ## 当前版本 -- 版本:`v1.8.0` -- 文档最后更新:`2026-05-23` +- 版本:`v1.8.1` +- 文档最后更新:`2026-05-28` diff --git a/VERSION b/VERSION index 27f9cd32..a8fdfda1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.0 +1.8.1 diff --git a/docs/AIRPORT_REALTIME_SOURCES.md b/docs/AIRPORT_REALTIME_SOURCES.md index bb9a8910..30eed830 100644 --- a/docs/AIRPORT_REALTIME_SOURCES.md +++ b/docs/AIRPORT_REALTIME_SOURCES.md @@ -1,11 +1,23 @@ # 机场高频实时数据源 +最后更新:`2026-05-28` + ## 已接入城市 | 城市 | 机场 | ICAO/站点 | 数据源 | 频率 | 类型 | 费用 | |------|------|-----------|--------|------|------|------| | 首尔 | 仁川国际 | RKSI | AMOS (`global.amo.go.kr`) | 1 分钟 | 跑道对温度(2对) | 免费 | | 釜山 | 金海国际 | RKPK | AMOS (`global.amo.go.kr`) | 1 分钟 | 跑道对温度(1对) | 免费 | +| 香港 | CoWIN 6087 | 6087 | CoWIN (`cowin.hku.hk`) | 1 分钟 | 参考站温度(保良局陈守仁小学) | 免费 | +| 香港 | HKO | HKO | HKO 官方 CSV (`data.weather.gov.hk`) | 10 分钟 | 官方气象站温度 | 免费 | +| 台北 | 松山/中央气象署 | 466920 | CWA 开放数据 | 10 分钟 | 官方站点温度 | 免费 | +| 北京 | 首都机场 | ZBAA | AMSC AWOS | 1 分钟 | 跑道端点气温 | 免费 | +| 上海 | 浦东机场 | ZSPD | AMSC AWOS | 1 分钟 | 跑道端点气温 | 免费 | +| 广州 | 白云机场 | ZGGG | AMSC AWOS | 1 分钟 | 跑道端点气温 | 免费 | +| 成都 | 双流机场 | ZUUU | AMSC AWOS | 1 分钟 | 跑道端点气温 | 免费 | +| 重庆 | 江北机场 | ZUCK | AMSC AWOS | 1 分钟 | 跑道端点气温 | 免费 | +| 武汉 | 天河机场 | ZHHH | AMSC AWOS | 1 分钟 | 跑道端点气温 | 免费 | +| 青岛 | 胶东机场 | ZSQD | AMSC AWOS | 1 分钟 | 跑道端点气温 | 免费 | | 东京 | 羽田 | RJTT | JMA AMeDAS (`jma.go.jp`) | 10 分钟 | 机场站点实时温度 | 免费 | | 安卡拉 | Esenboğa | 17128 | MGM (`servis.mgm.gov.tr`) | 5-15 分钟 | 机场站点实时温度 | 免费 | | 伊斯坦布尔 | 伊斯坦布尔机场 | 17058 | MGM (`servis.mgm.gov.tr`) | 5-15 分钟 | 机场站点实时温度 | 免费 | @@ -36,6 +48,14 @@ > 后端通过 `weather_sources.py` 拉取并注入 `airport_primary`,前端市场监控通过 > `resolveMonitorTemperature` 优先读取 `airport_primary.temp` 获得小数精度温度。 +> **CoWIN 6087**: 香港图表默认参考站为 HKU CoWIN `6087`(保良局陈守仁小学)。 +> 该源提供约 1 分钟温度序列,作为 PM 最高温市场的高频参考曲线;HKO 10 分钟数据 +> 仍作为官方气象层保留。后端通过 `cowin_sources.py` 拉取并写入 `cowin_obs`。 + +> **AMSC AWOS**: 中国内地跑道城市读取 AMSC `getWindPlate` 中的 `TDZ_TEMP` / +> `MID_TEMP` / `END_TEMP`。这些字段是跑道观测位置气温,不是道面温度。 +> 结算跑道展示使用配置的结算端点;辅助跑道只作为背景曲线。 + ## 推送机制 - 每城按原生频率独立推送,不捆绑 @@ -44,14 +64,16 @@ - 仅当当前温度距 DEB 预测最高 ≤3°C 时推送 - 确认过峰值后自动停止 -## 前端实时同步与 SSE Patch 机制 +## 前端实时同步与 SSE Patch / Redis Stream 机制 -为了向用户提供秒级实况响应并降低服务器负载,系统已从定时轮询架构全面迁移至 **Server-Sent Events (SSE) 增量更新(SSE Patch)** 架构。 +为了向用户提供接近行情盘的实况响应并降低服务器负载,系统使用 **HTTP snapshot + Server-Sent Events (SSE) Patch + 可重放事件日志** 架构。生产环境推荐 Redis Stream;本地或单进程可回退 SQLite event log。 ### 1. 数据推送链路 (Data Pipeline) 1. **Collector 采集端触发**:在 `weather_sources.py` 中,当高频实况源(如 AMOS, CoWIN, MADIS 等)采集到温度更新或观测时间变更时,会调用 `_emit_temperature_patch_if_changed` 过滤重复值,并异步向 `/api/internal/collector-patch` 发送 POST 报文。 -2. **FastAPI SSE 广播**:FastAPI 后端的 `sse_router.py` 接收到 Patch 后,将其推入 `sse_manager` 进行全局广播,事件被包装为 `city_patch` 增量包,包含自增的全局 `revision` 和最新的 `changes`。 -3. **BFF 代理流**:浏览器前端通过 BFF (Next.js rewrites) 建立与 `/api/events` 的持久连接,从而无需定时轮询。 +2. **标准化事件**:`realtime_patch_schema.py` 将旧 `city_patch` 或新 payload 统一成 `city_observation_patch.v1`。 +3. **事件存储**:生产环境写入 Redis Stream(`stream:city_observation`)并生成全局递增 `revision`;SQLite `observation_patch_events` 保留为本地/兜底 replay。 +4. **FastAPI SSE 广播**:FastAPI 后端的 `sse_router.py` 根据城市订阅集合向匹配连接推送 patch;断线重连时按 `since_revision` replay。 +5. **BFF 代理流**:浏览器前端通过 BFF 建立与 `/api/events` 的持久连接,从而无需固定整图轮询。 ### 2. 前端消费与刷新规则 (Frontend Freshness Rules) - **扫描列表免轮询更新**:`use-scan-terminal-query.ts` 通过 `useSsePatchVersion` 钩子订阅全局 SSE 版本。当有任何城市产生更新时,列表将触发按需重绘,之前固定的 5 分钟 `setInterval` 定时轮询已被彻底禁用。 @@ -62,6 +84,8 @@ 1. 调用轻量级的 `/api/city/{city}/summary` 快速拉取最新实况温度。 2. 调用 `fetchHourlyForecastForCity(city, { ignoreCache: true })` 强刷完整的城市详情数据,确保数据一致性。 - **按需加载与 Stagger 优化**:在加载城市详情时,前端会优先加载 Active 状态的图表,而处于 Background/非活动状态的图表则通过 staggered timer (按槽位索引延迟 300ms~1500ms) 异步获取,以分流请求峰值。 +- **前台恢复补齐**:浏览器标签页长时间在后台时,回来后会主动强刷可见图表 full detail,避免 SSE 被浏览器挂起后曲线落后。 +- **当地时间**:patch 中保留 `city_timezone` / `observed_at_utc`,前端按城市当地时间绘制横轴。 ## 消息模板 @@ -83,6 +107,11 @@ Seoul / Incheon 16:03 | `TELEGRAM_AIRPORT_PUSH_INTERVAL_SEC` | 循环轮询间隔 | `60` | | `TELEGRAM_AIRPORT_PUSH_LANGUAGE` | 机场推送语言覆盖,可选 `both`/`en`/`zh` | `both` | | `KNMI_API_KEY` | KNMI API 密钥(阿姆斯特丹必填) | — | +| `POLYWEATHER_EVENT_STORE` | 实时事件存储,可选 `redis`/`sqlite` | `sqlite` | +| `POLYWEATHER_REDIS_URL` | Redis Stream 连接地址 | `redis://127.0.0.1:6379/0` | +| `POLYWEATHER_REDIS_STREAM_KEY` | Redis Stream key | `stream:city_observation` | +| `POLYWEATHER_REDIS_STREAM_MAXLEN` | Redis Stream 保留长度 | `50000` | +| `POLYWEATHER_REDIS_REQUIRED` | Redis 不可用时是否启动失败 | `true` | ## 未接入城市 diff --git a/docs/API_ZH.md b/docs/API_ZH.md index 30aefc1d..7ed07ff8 100644 --- a/docs/API_ZH.md +++ b/docs/API_ZH.md @@ -1,6 +1,6 @@ -# PolyWeather API 文档(v1.8.0) +# PolyWeather API 文档(v1.8.1) -最后更新:`2026-04-27` +最后更新:`2026-05-28` 本文档描述当前对外可用 API 口径(`web/app.py` + `web/routes.py` + `frontend/app/api/*`)。 @@ -17,7 +17,9 @@ flowchart LR FE["Browser / Dashboard"] --> BFF["Next.js Route Handlers (/api/*)"] BFF --> API["FastAPI (/web/app.py + /web/routes.py)"] API --> WX["Weather Collector"] - API --> ANA["DEB + Trend + Probability + Market Scan"] + API --> ANA["DEB + Hourly Consensus + Probability + Market Scan"] + API --> SSE["Realtime SSE (/api/events)"] + SSE --> EVENT["Redis Stream / SQLite Event Log"] API --> PAY["Payment Intent + Event + Confirm Loops"] API --> OBS["healthz / system status / metrics"] ``` @@ -31,6 +33,27 @@ flowchart LR | `/api/city/{name}/summary` | GET | 轻量摘要 | | `/api/city/{name}/detail` | GET | 聚合详情(含 market_scan) | | `/api/history/{name}` | GET | 历史对账 | +| `/api/events` | GET | SSE 实时观测事件流 | +| `/api/internal/collector-patch` | POST | 采集器内部写入实时观测 patch | + +### `GET /api/events` + +浏览器实时图表入口,使用 `text/event-stream`。 + +参数: + +- `cities=shanghai,hong kong`:可选,逗号分隔城市列表;为空表示订阅全部城市。 +- `since_revision=`:可选,断线重连后从指定 revision 之后 replay。 +- `replay_limit=`:可选,默认 `500`,后端会做上限保护。 + +事件: + +- `connected`:连接建立,包含当前 `latest_revision`。 +- `city_observation_patch.v1`:标准实时观测 patch,包含城市、序列、温度、观测 UTC 时间、城市时区、revision。 +- `resync_required`:replay 窗口不足或事件存储不可用,前端应回到 HTTP snapshot 重建画面。 +- `heartbeat`:保活。 + +生产环境推荐 `POLYWEATHER_EVENT_STORE=redis`,以 Redis Stream 保存短窗口事件并支持多 worker fanout;本地或单进程可使用 SQLite event log。 ### `GET /api/city/{name}/detail` @@ -51,6 +74,7 @@ flowchart LR - `probabilities.engine / calibration_mode / calibration_version` - `probabilities.raw_mu / raw_sigma / calibrated_mu / calibrated_sigma` - `probabilities.shadow_distribution` +- `deb.hourly_consensus / deb.hourly_path.base_source` - `intraday_meteorology.headline / confidence` - `intraday_meteorology.base_case_bucket / upside_bucket / downside_bucket` - `intraday_meteorology.next_observation_time` @@ -90,7 +114,7 @@ flowchart LR #### 2. `probabilities` -概率层基于 legacy 高斯分桶,以 DEB 融合预测 μ 和 ensemble spread σ 生成 1°C 粒度概率分布。 +概率层基于 legacy 高斯分桶,以 DEB 融合预测 `mu` 和 ensemble spread `sigma` 生成 1°C 粒度概率分布。前端图表将 legacy 高斯展示为水平概率温度带和 `mu` 参考线,不把概率分布渲染成时间序列曲线。 概率字段: @@ -99,6 +123,22 @@ flowchart LR - `distribution`:当天合约桶概率分布 - `distribution_all`:包含外围桶的完整分布 +#### 2.1 `deb.hourly_consensus` + +DEB hourly consensus 是当前图表与峰值窗口的优先小时路径。 + +重点字段: + +- `version`:当前为 `deb_hourly_consensus.v1` +- `base_source`:`multi_model_hourly_deb_weights` +- `times` / `temps`:城市当地日的小时路径 +- `model_weights`:DEB 权重折叠后的模型权重 + +说明: + +- 该路径是预测曲线,不是实测来源。 +- 图表默认展示全天;“高温”视图仅根据该路径推导 peak-centric 窗口。 + #### 3. `detail_depth` `detail_depth` 用于区分轻量 detail 与完整 detail。前端如果发现: @@ -255,6 +295,8 @@ flowchart LR - `summary?force_refresh=true`:`Cache-Control: no-store` - 详情接口与支付接口:`no-store` - `METAR` / `TAF` / settlement current 由后端各自维护短 TTL 缓存 +- 实时事件层:`POLYWEATHER_EVENT_STORE=redis` 时使用 Redis Stream;未启用 Redis 时使用 SQLite `observation_patch_events` 作为 replay fallback +- 前端终端图表:HTTP detail 仍是完整 snapshot,SSE patch 只做增量观测追加;长连接断开或后台恢复时前端会按 `since_revision` replay 或强刷 detail - 前端打开今日日内分析时,如果 full detail 或 market scan 正在同步,会先显示刷新锁,不展示可交互的旧内容 - 城市决策卡 AI 解读前端缓存键为 `city + local_date + locale + METAR signature`;signature 优先使用原始 METAR,缺失时回退到报文时间、观测时间和温度 - 城市决策卡 AI 解读使用两层前端缓存:页面内存缓存保存 loading / 流式进度 / 最终 payload,`localStorage` 保存最终成功 payload,默认 TTL 1 小时 diff --git a/docs/CITY_DATA_SOURCES.md b/docs/CITY_DATA_SOURCES.md index 7677300d..2e424491 100644 --- a/docs/CITY_DATA_SOURCES.md +++ b/docs/CITY_DATA_SOURCES.md @@ -1,6 +1,6 @@ # 城市实时数据源总览 -> 最后更新: 2026-05-26 | 51 城市 +> 最后更新: 2026-05-28 | 51 城市 ## 数据源分级 @@ -111,11 +111,30 @@ ankara, istanbul, helsinki, amsterdam, paris - **Tier 4 城市**(仅 METAR):修正效果有限,不建议依赖 +## 实时事件与图表刷新逻辑 + +当前终端图表不是固定整图轮询,而是: + +1. 首屏 / 切换城市时拉取 `/api/city/{city}/detail` 作为完整 snapshot。 +2. 可见图表连接 `/api/events?cities=...&since_revision=...&replay_limit=500`。 +3. 采集器产出 `city_observation_patch.v1` 后写入 Redis Stream(生产)或 SQLite event log(本地/兜底),再通过 SSE 推给浏览器。 +4. 前端把 patch 追加到已有实测序列,不显示 loading 遮罩;只有可见图表 2 分钟无 patch 时才启动 60 秒兜底刷新。 +5. 浏览器从后台切回前台时,前端会立即补一次 full detail,防止长时间挂页后图表落后。 + +频率取决于源头: + +- AMSC / AMOS / CoWIN / MSS:源头约 1 分钟,图表按 1 分钟粒度追加。 +- MADIS:源头约 5 分钟。 +- HKO / CWA / JMA / FMI / KNMI:源头约 10 分钟。 +- METAR-only 城市:按 METAR 可用频率和缓存 TTL,不伪装成 1 分钟实测。 + +所有图表横轴和 tooltip 时间均按城市当地时间展示,不按用户浏览器时区。 + ## 关于网站终端图表的数据曲线展示逻辑 ### 1. 实测数据(默认全开,突出核心) -- **跑道全量展示**:北京、上海、广州、成都、重庆、武汉、首尔等城市的跑道实测数据,默认全量开启,无需手动勾选。 +- **跑道全量展示**:北京、上海、广州、成都、重庆、武汉、青岛、首尔、釜山等城市的跑道实测数据,默认全量开启,无需手动勾选。 - **结算跑道高亮**:系统内置了各大机场的官方结算跑道映射。命中的跑道将被**重点强调**(加粗的青色实线 #009688,线宽 2.8),并标记为“[跑道号] 结算跑道”。具体的跑道映射如下: - 北京:19/01 - 上海:17L/35R @@ -125,15 +144,24 @@ ankara, istanbul, helsinki, amsterdam, paris - 武汉:04/22 - 青岛:16/34 - 首尔:15R/33L + - 釜山:SR/SL - **辅助跑道弱化**:同一机场下的其他非结算跑道,也会同时展示,但采用较细的虚线(线宽 1.2)以作陪衬区分。 -- **其他实测展示**:所有城市的 METAR 报文曲线、官方气象站实测(如 Hong Kong / Lau Fau Shan 的香港天文台曲线)均默认展示。 +- **单跑道机场去重**:釜山只有 `SR/SL` 跑道曲线时,不再额外展示 AMOS 聚合线,避免两条线语义重复。 +- **香港参考曲线**:Hong Kong 默认展示 CoWIN `6087`(保良局陈守仁小学)1 分钟参考站曲线;HKO 10 分钟实测作为官方气象层保留。 +- **其他实测展示**:所有城市的 METAR 报文曲线、官方气象站实测(如 Shenzhen / Lau Fau Shan 的 HKO 自动站、Taipei 的 CWA)均默认展示。 ### 2. 核心预测数据(默认展示) -- **DEB 模型融合**:作为平台核心的高精度智能融合预测曲线,默认始终展示给用户。 +- **DEB 模型融合**:作为平台核心的智能融合预测曲线,默认始终展示给用户。DEB 是预测,不参与“实测接近峰值”的视觉预警计算。 +- **DEB hourly consensus**:图表优先使用 `deb_hourly_consensus.v1` 的小时路径展示 DEB 曲线和推导“高温”窗口;如果缺失才回退旧的 hourly + DEB offset 路径。 ### 3. 多模型原始数据(默认隐藏,按需自选) - **保持整洁**:为了防止图表线缆过于杂乱,各大原始模型(ECMWF, GFS, ICON, GEM 等)的数据曲线在初次加载时**默认隐藏**。 - **特例**:仅针对巴黎(Paris),由于其 AROME HD 是高精度的 15 分钟级临近预报,极具参考价值,因此默认开启。 - **自由交互**:用户可通过图表底部的图例交互按钮,随时自由勾选、叠加或隐藏任意所需的数据曲线。 + +### 4. 高斯概率图层 + +- legacy 高斯概率不会作为时间序列曲线展示。 +- 图表上只渲染概率温度带和 `mu` 参考线,帮助用户判断当前实测距概率中心和高概率区域的关系。 diff --git a/docs/COMMERCIALIZATION.md b/docs/COMMERCIALIZATION.md index 99d89743..bf6e92c6 100644 --- a/docs/COMMERCIALIZATION.md +++ b/docs/COMMERCIALIZATION.md @@ -1,6 +1,6 @@ # 商业化说明(Production) -最后更新:`2026-04-18` +最后更新:`2026-05-28` ## 1. 定位 @@ -8,8 +8,8 @@ PolyWeather 是面向温度结算场景的气象决策层,不是通用天气 核心价值: -- 观测优先(METAR / 机场主站 / 明确官方站点;MGM、NMC、JMA、AMOS 等作为增强层) -- 结算导向(DEB + 校准概率桶) +- 观测优先(METAR / 机场主站 / 明确官方站点;MGM、JMA、AMOS、AMSC、CoWIN、CWA 等作为增强层) +- 结算导向(DEB hourly consensus + 校准概率桶) - 气象判断优先(证据链、失效条件、下一观测点) - 市场映射(行情对照 + 错价雷达),但不把交易建议放在第一层产品承诺 @@ -24,6 +24,8 @@ PolyWeather 是面向温度结算场景的气象决策层,不是通用天气 | 支付自动确认 | 已上线 | Event Loop + Confirm Loop | | 钱包绑定 | 已上线 | 浏览器钱包 + WalletConnect | | 私有频道推送 | 已上线 | 可拆分业务频道 | +| 实时终端 | 已上线 | SSE Patch + Redis Stream replay | +| Telegram 双语推送 | 已上线 | 中英文一起推送,结算跑道端点口径 | ## 3. 权限模型(当前) @@ -33,6 +35,7 @@ PolyWeather 是面向温度结算场景的气象决策层,不是通用天气 - 今日日内深度分析(含高温时段) - 专业气象结论条、证据链、失效条件、确认条件 - 概率分布层(基于 DEB 融合 + 高斯分桶) + - 实时终端多城市图表(跑道/官方站点/DEB/概率带) - 历史对账 + 未来日期分析 - 全平台智能气象推送 @@ -62,6 +65,7 @@ PolyWeather 是面向温度结算场景的气象决策层,不是通用天气 5. 安全边界:敏感配置不进仓库。 6. 数据口径:机场市场按 METAR / 机场主站解释;Wunderground 只可描述为历史页面或参考入口,不描述成“站”。 7. 加载口径:日内分析和右侧详情在 full detail 未补齐前必须显示同步状态,不能把旧缓存伪装成完整付费内容。 +8. 实时口径:终端图表应优先保留已有曲线并无痛合并 patch,不用 loading 遮罩覆盖已可用数据。 ## 7. 后续路线 diff --git a/docs/CONFIGURATION_ZH.md b/docs/CONFIGURATION_ZH.md index c327cd47..f88375f2 100644 --- a/docs/CONFIGURATION_ZH.md +++ b/docs/CONFIGURATION_ZH.md @@ -1,5 +1,7 @@ # 配置与密钥管理(中文) +最后更新:`2026-05-28` + ## 1. 目标 PolyWeather 的环境变量很多,但不是所有变量都属于同一层级。 @@ -79,6 +81,7 @@ PolyWeather 的环境变量很多,但不是所有变量都属于同一层级 - `POLYWEATHER_RUNTIME_DATA_DIR` - `POLYWEATHER_DB_PATH` - `POLYWEATHER_STATE_STORAGE_MODE` +- `POLYWEATHER_EVENT_STORE` 前端: @@ -104,6 +107,8 @@ PolyWeather 的环境变量很多,但不是所有变量都属于同一层级 - `POLYWEATHER_AUTH_REQUIRE_SUBSCRIPTION` - `POLYWEATHER_OPS_ADMIN_EMAILS` - `POLYWEATHER_STATE_STORAGE_MODE` +- `POLYWEATHER_EVENT_STORE` +- `POLYWEATHER_REDIS_REQUIRED` - `POLYWEATHER_PAYMENT_ENABLED` - `POLYGON_WALLET_WATCH_ENABLED` - `TELEGRAM_ALERT_PUSH_ENABLED` @@ -127,6 +132,9 @@ PolyWeather 的环境变量很多,但不是所有变量都属于同一层级 - `TELEGRAM_MARKET_FOCUS_DIGEST_TOP_N` - `POLYWEATHER_PAYMENT_RPC_URLS` - `TAF_CACHE_TTL_SEC` +- `POLYWEATHER_REDIS_URL` +- `POLYWEATHER_REDIS_STREAM_KEY` +- `POLYWEATHER_REDIS_STREAM_MAXLEN` 策略: @@ -215,6 +223,10 @@ TELEGRAM_CHAT_ID=... POLYWEATHER_RUNTIME_DATA_DIR=/var/lib/polyweather POLYWEATHER_DB_PATH=/var/lib/polyweather/polyweather.db POLYWEATHER_STATE_STORAGE_MODE=sqlite +POLYWEATHER_EVENT_STORE=redis +POLYWEATHER_REDIS_URL=redis://polyweather_redis:6379/0 +POLYWEATHER_REDIS_STREAM_MAXLEN=50000 +POLYWEATHER_REDIS_REQUIRED=true UID=1000 GID=1000 POLYWEATHER_AUTH_ENABLED=true @@ -245,6 +257,8 @@ POLYWEATHER_BACKEND_URL=http://polyweather_web:8000 - `POLYWEATHER_RUNTIME_DATA_DIR` 建议放在仓库外,例如 `/var/lib/polyweather`。 - `docker-compose.yml` 会把这个目录同时挂载到容器内的 `/var/lib/polyweather` 和 `/app/data`,兼容现有缓存与 SQLite 路径。 - `POLYWEATHER_STATE_STORAGE_MODE` 当前线上推荐直接使用 `sqlite`。 +- `POLYWEATHER_EVENT_STORE=redis` 表示实时观测 patch 使用 Redis Stream 做短窗口 replay 和多 worker fanout;本地或单进程可改为 `sqlite`。 +- `POLYWEATHER_REDIS_REQUIRED=true` 表示 Redis 不可用时后端启动失败,避免生产环境广播不可 replay 的实时事件;开发环境可设为 `false` 允许回退 SQLite。 - `POLYWEATHER_PAYMENT_RPC_URLS` 支持逗号分隔多个 RPC;如果暂时只用单 RPC,也可以继续只配 `POLYWEATHER_PAYMENT_RPC_URL`。 - 机器人市场监控包含 `关键提醒` 与 `关注清单`:关键提醒逐城判断并受冷却控制,关注清单每轮先扫描完整城市列表,再按全局 Top N 推送;同一轮已经触发关键提醒的城市不会重复出现在关注清单里。 - `TELEGRAM_MARKET_FOCUS_DIGEST_INTERVAL_SEC` 表示主动推送间隔,默认 `1800` 秒(30 分钟)。 diff --git a/docs/FRONTEND_DEPLOYMENT_ZH.md b/docs/FRONTEND_DEPLOYMENT_ZH.md index 8ec96d1c..677631d0 100644 --- a/docs/FRONTEND_DEPLOYMENT_ZH.md +++ b/docs/FRONTEND_DEPLOYMENT_ZH.md @@ -1,5 +1,7 @@ # 前端部署配置(Vercel) +最后更新:`2026-05-28` + 本文只覆盖 `frontend` 目录对应的 Next.js 前端部署。 ## 一、部署目标 @@ -16,6 +18,12 @@ 2. Next `/api/*` -> `POLYWEATHER_API_BASE_URL` 3. FastAPI 后端 -> 分析 / 支付 / 鉴权服务 +实时图表同样走 Next Route Handler / rewrite: + +1. 浏览器 `EventSource` -> `/api/events?cities=...&since_revision=...` +2. Next 转发到 FastAPI `/api/events` +3. FastAPI 从 Redis Stream / SQLite event log replay 后进入 live SSE + ## 二、Vercel 项目设置 在 Vercel 导入 GitHub 仓库后,使用下面的设置: @@ -193,6 +201,7 @@ Vercel 部署前至少确认: 4. 如果启用鉴权,Supabase redirect URL 已包含前端域名 5. `GET /api/payments/config` 返回的是当前最新地址,而不是旧收款合约 6. 如果启用了 `/ops`,确认 `POLYWEATHER_OPS_ADMIN_EMAILS` 已在 Vercel 与后端同时配置 +7. 确认 `/api/events` 没有被 CDN 缓存或压缩成普通 JSON;它必须保持 `text/event-stream` ## 九、常见问题 diff --git a/docs/MODEL_STACK_AND_DEB_ZH.md b/docs/MODEL_STACK_AND_DEB_ZH.md index 0b747239..95e14ac9 100644 --- a/docs/MODEL_STACK_AND_DEB_ZH.md +++ b/docs/MODEL_STACK_AND_DEB_ZH.md @@ -2,7 +2,7 @@ 本文档记录 PolyWeather 当前开放模型接入、区域覆盖差异,以及 DEB 在新增模型后的计权规则。 -最后更新:`2026-05-27` +最后更新:`2026-05-28` ## 1. 接入方式 @@ -172,7 +172,33 @@ raw current_forecasts 当 `weights_info` 出现 `家族去重`,表示当前输入模型数量多于 DEB 实际入模数量,系统已先折叠同家族模型。 -### 5.1 版本化预测与偏差校正 +### 5.1 DEB hourly consensus + +当前图表和峰值窗口优先使用 `deb_hourly_consensus.v1`。 + +入口: + +- `src/analysis/deb_hourly_consensus.py` +- `web/analysis_service.py` +- `src/analysis/trend_engine.py` + +处理逻辑: + +```text +multi_model hourly curves + -> 按 DEB 可用模型和家族去重口径选择候选 + -> 使用 DEB 权重生成逐小时 consensus + -> 输出 deb.hourly_consensus + -> 图表 DEB Forecast 与 peak-window 逻辑优先读取该路径 +``` + +关键口径: + +- `deb_hourly_consensus.v1` 是预测曲线,不是实测曲线。 +- 它只用于日内形状、峰值窗口和 DEB Forecast 展示;视觉预警和“接近峰值”状态必须优先读取实测/跑道/官方站点当前值与今日实测高点。 +- 如果多模型小时路径缺失,系统才回退旧的 `hourly_plus_deb_offset`。 + +### 5.2 版本化预测与偏差校正 DEB 原始融合逻辑不推倒重写,`calculate_dynamic_weights(...)` 仍作为 raw baseline。线上生产入口使用 `calculate_deb_prediction(...)` 包装 raw baseline,并在有足够历史样本时追加城市级 recent signed-bias correction。 @@ -213,6 +239,7 @@ python scripts/backtest_deb_versions.py \ - `multi_model` - `multi_model_daily` - `source_forecasts.open_meteo_multi_model.model_metadata` +- `deb.hourly_consensus` 显示分组: @@ -233,6 +260,13 @@ python scripts/backtest_deb_versions.py \ 区域模型不覆盖时不显示空模型。 +终端图表默认: + +- 展示“全天”视图,按城市当地日 00:00-23:59 和真实观测时间绘制。 +- 可选“高温”视图,窗口由 `deb_hourly_consensus.v1` 的峰值区域推导。 +- DEB Forecast 使用橙色预测曲线;实测/跑道/官方站点曲线独立展示,不把 DEB 当成实测。 +- legacy 高斯概率显示为水平温度带和 `mu` 参考线,不参与时间序列曲线。 + ### 6.1 “来源 Open-Meteo”是什么意思 前端中的 `来源 - Open-Meteo` 表示本次多模型数据通过 Open-Meteo model API 归一化接入。 diff --git a/docs/MONITORING_ZH.md b/docs/MONITORING_ZH.md index a94c3990..166f7fc7 100644 --- a/docs/MONITORING_ZH.md +++ b/docs/MONITORING_ZH.md @@ -1,6 +1,6 @@ # 外部监控与告警说明 -最后更新:`2026-04-18` +最后更新:`2026-05-28` ## 1. 目标 @@ -11,6 +11,7 @@ - Relay 把告警推到运营频道 - Grafana 展示趋势面板 - 巡检脚本补健康检查 +- 关注 `/api/events` 长连接与 realtime event store 是否正常 replay ## 2. 组件 @@ -89,6 +90,14 @@ POLYWEATHER_MONITORING_ALERT_CHAT_IDS= - Source Requests by Outcome - Source Error Rate (15m) +实时事件层建议额外观察: + +- Redis Stream latest revision +- Redis 连接状态 +- SQLite fallback 是否被启用 +- SSE active connection count +- `resync_required` 出现频率 + ## 7. 巡检脚本 手动巡检: @@ -102,6 +111,7 @@ python scripts/check_ops_health.py --base-url http://127.0.0.1:8000 - `/healthz` - `/api/system/status` - `/metrics` +- `/api/events`(手动验证时查看 `connected` / `heartbeat` / replay 事件) 任何一项失败都会非零退出,适合挂到 crontab 或 systemd timer。 @@ -130,6 +140,11 @@ python scripts/check_ops_health.py --base-url http://127.0.0.1:8000 - 多日预报是否只返回当天单卡 - 日内分析 full detail / market scan 是否仍在同步 - 右侧详情面板是否正在用同步占位卡提示用户 +- 实时事件状态: + - event store 类型(Redis / SQLite) + - latest revision + - Redis 是否连通 + - 是否处于 `degraded_from=redis` fallback 这意味着: @@ -180,3 +195,4 @@ npm run build - 更细粒度支付指标 - 按城市/来源拆分的业务 SLA - 按城市拆分的前端补齐耗时与 stale-detail 告警 +- Redis Stream 长度、内存与 replay gap 告警 diff --git a/docs/SERVICES_ZH.md b/docs/SERVICES_ZH.md index 77216818..46dd82fb 100644 --- a/docs/SERVICES_ZH.md +++ b/docs/SERVICES_ZH.md @@ -1,53 +1,63 @@ # 外部服务依赖总览 -最后更新:`2026-05-23` +最后更新:`2026-05-28` -项目调用了 20 个外部服务,按状态分为三类。 +项目调用外部天气、鉴权、支付和实时事件服务。原则是:核心链路必须有明确健康检查;可选数据源不可拖垮已可用城市;实时事件层可从 Redis Stream 降级到 SQLite event log。 ## 核心(必须有,挂了服务不可用) -| 服务 | 用途 | 状态 | -| ---------------------- | --------------------- | ---- | -| Open-Meteo | 52 城天气预报 | ✅ | -| AviationWeather (NOAA) | METAR/TAF 航空观测 | ✅ | -| MADIS (NOAA) | 美国 5 分钟高频观测 | ✅ | -| Supabase | 用户认证 + 订阅 | ✅ | -| Telegram Bot API | Bot 消息 + 群成员检查 | ✅ | -| KNMI | Amsterdam 10 分钟观测 | ✅ |## 国家气象源(特定城市必须) +| 服务 | 用途 | 状态 | +| --- | --- | --- | +| Open-Meteo | 51 城天气预报、多模型小时曲线、DEB hourly consensus 输入 | ✅ | +| AviationWeather (NOAA) | METAR / TAF 航空观测 | ✅ | +| MADIS (NOAA) | 美国机场 5 分钟高频观测 | ✅ | +| Supabase | 用户认证、订阅状态、会员恢复 | ✅ | +| Telegram Bot API | Bot 消息、群成员检查、双语跑道推送 | ✅ | +| Redis | `city_observation_patch.v1` Stream、SSE replay、多 worker fanout | ✅ | +| SQLite | 运行态数据库、支付审计、实时事件 fallback | ✅ | -| 服务 | 城市 | 状态 | -| -------------------- | --------------------- | ----------- | -| JMA (日本) | Tokyo | ✅ | -| KMA + AMOS (韩国) | Seoul, Busan | ✅ | -| AMSC AWOS (中国) | 北京/上海/广州等 6 城 | ✅ | -| MGM (土耳其) | Ankara, Istanbul | ✅ | -| FMI (芬兰) | Helsinki | ✅ | -| HKO (香港) | Hong Kong | ✅ | -| CWA (台湾) | Taipei | ✅ | -| NMC (中国) | 国内城市 fallback | ✅ | -| Singapore MSS | Singapore | ✅ | -| IMGW (波兰) | Warsaw | ⚠️ 未配 key | -| Russia pogodaiklimat | Moscow | ❌ 已移除 | +## 国家气象源(特定城市必须) + +| 服务 | 城市 | 状态 | +| --- | --- | --- | +| JMA AMeDAS | Tokyo | ✅ | +| AMOS (韩国) | Seoul, Busan 跑道传感器 | ✅ | +| AMSC AWOS (中国) | 北京、上海、广州、成都、重庆、武汉、青岛跑道端点气温 | ✅ | +| MGM (土耳其) | Ankara, Istanbul | ✅ | +| FMI (芬兰) | Helsinki | ✅ | +| KNMI (荷兰) | Amsterdam | ✅(需 key) | +| CoWIN 6087 (香港) | Hong Kong 1 分钟参考站 | ✅ | +| HKO (香港) | Hong Kong / Shenzhen / Lau Fau Shan 10 分钟官方气象层 | ✅ | +| CWA (台湾) | Taipei | ✅ | +| Singapore MSS | Singapore | ✅ | +| IMS Lod (以色列) | Tel Aviv | ✅ | +| AEROWEB / AROME HD | Paris | ✅ | +| NMC (中国) | 国内城市 fallback | ✅ | +| IMGW (波兰) | Warsaw | ⚠️ 未配 key | ## 可选 / 已禁用 -| 服务 | 用途 | 状态 | -| -------------- | ------------- | ----------- | +| 服务 | 用途 | 状态 | +| --- | --- | --- | | OpenWeatherMap | 天气 fallback | ⚠️ 未配 key | -| VisualCrossing | 历史天气 | ⚠️ 未配 key | -| Meteoblue | 天气预报 | ❌ 已移除 | -| SynopticData | 美国站点观测 | ⚠️ 未配 key | +| VisualCrossing | 历史天气 | ⚠️ 未配 key | +| SynopticData | 美国站点观测 | ⚠️ 未配 key | +| Meteoblue | 天气预报 | ❌ 已移除 | +| Russia pogodaiklimat | Moscow 历史源 | ❌ 已移除 | +| Groq | AI commentary | ❌ 已移除 | -## AI / 其他 +## AI / 支付 / 前端 -| 服务 | 用途 | 状态 | -| ----------------- | ---------------- | ----------- | +| 服务 | 用途 | 状态 | +| --- | --- | --- | | MiMo (xiaomimimo) | 城市分析 AI 评论 | ✅ 当前使用 | -| DeepSeek | AI fallback | - 备用 | -| Groq | AI commentary | ❌ 已移除 | -| Polygon RPC | 链上支付 | ✅ | -| WalletConnect | 前端钱包连接 | ⚠️ 未配 key | +| DeepSeek | AI fallback | 备用 | +| Polygon RPC | 链上支付、自动确认 | ✅ | +| WalletConnect | 前端钱包连接 | ⚠️ 未配 key 时钱包入口降级 | -## 合计 +## 运维口径 -15 个在用,3 个可选/未配置,3 个已移除。 +- 生产实时事件推荐:`POLYWEATHER_EVENT_STORE=redis` + `POLYWEATHER_REDIS_URL=redis://polyweather_redis:6379/0`。 +- 本地或单进程兜底:`POLYWEATHER_EVENT_STORE=sqlite`。 +- Redis 只负责短窗口 replay 与多 worker fanout,不是长期天气历史库。 +- DEB hourly consensus 依赖 Open-Meteo 多模型小时曲线;若上游限流,图表应保留已有 snapshot 和实测 patch,不把缺失模型误报为实测缺失。 diff --git a/docs/SUPABASE_SETUP_ZH.md b/docs/SUPABASE_SETUP_ZH.md index c90db4f2..f96d6d9a 100644 --- a/docs/SUPABASE_SETUP_ZH.md +++ b/docs/SUPABASE_SETUP_ZH.md @@ -1,4 +1,4 @@ -# Supabase + 登录 + 支付接入说明(v1.8.0) +# Supabase + 登录 + 支付接入说明(v1.8.1) 最后更新:`2026-03-14` diff --git a/docs/TECH_DEBT_ZH.md b/docs/TECH_DEBT_ZH.md index 257973b6..e3f6354b 100644 --- a/docs/TECH_DEBT_ZH.md +++ b/docs/TECH_DEBT_ZH.md @@ -1,4 +1,4 @@ -# 技术债与工程待办(v1.8.0) +# 技术债与工程待办(v1.8.1) 最后更新:`2026-05-10` diff --git a/docs/data-architecture-review.md b/docs/data-architecture-review.md index 595271c1..e4c1f349 100644 --- a/docs/data-architecture-review.md +++ b/docs/data-architecture-review.md @@ -2,7 +2,7 @@ > 审查日期:2026-06 | 视角:系统架构师 | 范围:完整数据采集→分析→API→前端状态 > -> **修复状态:8/8 已完成** +> **修复状态:9/9 已完成;最后校准:2026-05-28** ## 一、数据架构总览 @@ -13,9 +13,8 @@ Open-Meteo (预报+多模型) ─┐ METAR/TAF (航空气象) ─┤ NWS (美国) / MGM (土耳其) ─┤ -JMA/AMOS/NMC/HKO/CWA ─┤ -Wunderground / NOAA ─┤ -Polymarket Gamma/CLOB ─┤ +JMA/AMOS/AMSC/HKO/CWA ─┤ +CoWIN / MADIS / NOAA ─┤ ├─ WeatherDataCollector ├─ dashboard-client.ts │ (内存缓存 + SQLite磁盘缓存) │ (ETag浏览器缓存 + SWR) │ │ @@ -27,8 +26,11 @@ Polymarket Gamma/CLOB ─┤ │ ├─ ThreadPoolExecutor(4) │ (120s TTL) │ └─ AI 增强层 (DeepSeek) │ │ │ - └─ FastAPI routes └─ API代理 (Next.js rewrites) - (36个端点 + ETag 304) + ├─ FastAPI routes └─ API代理 (Next.js rewrites) + │ (HTTP snapshot + ETag 304) + │ + └─ SSE /api/events + (Redis Stream / SQLite replay) ``` ## 二、数据采集层 @@ -43,10 +45,8 @@ Polymarket Gamma/CLOB ─┤ | NWS | 国家预报 | 美国 | 按请求 | | MGM | 国家官方 | 土耳其 | 300s | | ECMWF/GFS/ICON/GEM/JMA | 多模型 NWP | 全球 | 300s | -| HKO/CWA/NOAA/AMOS/NMC | 结算观测 | 特定国家 | 60s (AMOS) / 300s | -| Wunderground | 个人气象站 | 全球备用 | 按请求 | -| Polymarket Gamma | 市场发现 | 所有温度市场 | 60s | -| Polymarket CLOB | 订单簿 | 匹配市场 | 30s | +| HKO/CWA/NOAA/AMOS/AMSC/CoWIN | 结算/参考观测 | 特定国家 | 60s-600s | +| Redis Stream | 实时事件 replay / 多 worker fanout | 后端内部 | event-driven | ### 待改进 @@ -78,7 +78,7 @@ Polymarket Gamma/CLOB ─┤ **已修复:TTL 匹配** — `SCAN_TERMINAL_PAYLOAD_TTL_SEC` 30s → 120s,匹配 ThreadPoolExecutor(4)×60 城的实际重算耗时。 -**已实现:SSE 增量推送(SSE Patch)与按需刷新** — 引入 FastAPI SSE 广播通道 (`/api/events`)。数据采集端更新时自动向 `/api/internal/collector-patch` 推送最新温度;前端扫描终端订阅该流,不再执行固定的 5 分钟定时轮询,而是根据 Patch 变化即时更新列表;当前选中图表基于 `useLatestPatch` 实现 1 分钟级温度的增量合并与实时曲线绘制。 +**已实现:SSE 增量推送(SSE Patch)与可重放事件日志** — 引入 FastAPI SSE 广播通道 (`/api/events`)。数据采集端更新时自动向 `/api/internal/collector-patch` 推送最新温度;后端标准化为 `city_observation_patch.v1`,写入 Redis Stream(生产)或 SQLite event log(兜底)后再广播。前端扫描终端订阅该流,不再执行固定的 5 分钟定时轮询,而是根据 Patch 变化即时更新列表;当前选中图表基于 `useLatestPatch` 实现 1 分钟级温度的增量合并与实时曲线绘制。 | # | 问题 | 优先级 | |---|------|------| @@ -94,7 +94,7 @@ Polymarket Gamma/CLOB ─┤ **已修复:扫描数据复用** — `preloadCityFromRow()` 从扫描终端行预填充城市详情缓存,选城市后详情面板立即显示。 -**已实现:SSE 订阅与 2 分钟无 Patch 兜底机制** — 引入 `useLatestPatch` 与 `useSsePatchVersion` 管理实况数据的准实时合并。若长连接中断或 2 分钟内未收到任何增量 Patch,可见图表自动触发 60s 降级轮询(从 `/api/city/{city}/summary` 获取最新实况,并以 ignoreCache 强刷 full detail)。 +**已实现:SSE 订阅、replay 与 2 分钟无 Patch 兜底机制** — 引入 `useLatestPatch` 与 `useSsePatchVersion` 管理实况数据的准实时合并。若长连接中断,前端使用 `since_revision` replay 缺失事件;若 2 分钟内未收到任何增量 Patch,可见图表自动触发 60s 降级轮询(从 `/api/city/{city}/summary` 获取最新实况,并以 ignoreCache 强刷 full detail)。浏览器后台恢复时会主动刷新可见图表 full detail。 ## 六、待办 diff --git a/docs/deep-research-report.md b/docs/deep-research-report.md index 4d495c98..8385a5ec 100644 --- a/docs/deep-research-report.md +++ b/docs/deep-research-report.md @@ -4,22 +4,22 @@ PolyWeather(仓库:`yangyuan-zhen/PolyWeather`)定位为**面向温度类结算预测市场(如 Polymarket 的温度结算合约)**的”生产级气象情报系统”,核心在于把多源天气观测/预报转化为**结算导向的概率桶(μ + bucket distribution)**;同时提供 Web 仪表盘与 Telegram Bot 两套交互入口,并包含 Polygon 链上 USDC/USDC.e 支付、自动补单与订阅/积分体系。项目 README 现明确仓库代码采用 `AGPL-3.0-only`,同时将品牌、商标、生产私有数据与运营阈值保留在代码许可证之外。 -> **2026-05-23 更新(v1.7.0)**:Polymarket 价格拉取与 UI 层(MarketDecisionLine)已删除,`market_scan` 当前返回空;LGBM 已完全移除,概率引擎仅保留 legacy 高斯 + EMOS/CRPS;Groq、Meteoblue、NMC、pogodaiklimat 数据源和 prewarm 预热系统已移除。 +> **2026-05-28 更新(v1.8.1)**:终端图表已切换为 HTTP snapshot + SSE Patch + Redis Stream/SQLite replay 架构;DEB hourly consensus (`deb_hourly_consensus.v1`) 成为峰值窗口和 DEB 曲线展示的优先小时路径;README 产品截图迁移到 `frontend/public/static`。 ## 项目概览 PolyWeather 的目标与范围在 README/README_ZH 中定义得较清楚:为温度结算市场提供气象情报(多源采集→融合→概率→对照市场报价),并提供“官方看板(Vercel 前端)+ VPS 后端 + Telegram Bot”。 项目主功能可归纳为五层: -**天气层(数据源/采集)**:聚合 51 个城市的实测与预报;支持 AviationWeather METAR(机场观测)、韩国 AMOS 跑道级观测(首尔/釜山)、土耳其 MGM 站网、Open-Meteo(含多模型与集合预报)、美国 NWS(仅美国城市)、以及部分城市使用明确官方站点或历史页面入口(香港 HKO、台湾/深圳相关历史页面等)等。机场类市场仍以 METAR / 机场主站为结算锚点,Wunderground 不描述为物理观测站。 +**天气层(数据源/采集)**:聚合 51 个城市的实测与预报;支持 AviationWeather METAR/TAF、韩国 AMOS 跑道级观测(首尔/釜山)、中国 AMSC AWOS 跑道端点气温、香港 CoWIN 6087 / HKO、土耳其 MGM、Open-Meteo 多模型与集合预报、美国 MADIS HFMETAR 等。机场类市场仍以 METAR / 机场主站或明确官方结算源为锚点,Wunderground 不描述为物理观测站。 **分析层(DEB/趋势/概率/结算口径)**: DEB(Dynamic Error Balancing)基于过去 N 天模型误差(MAE)倒数加权,输出融合预报;运行态仍维护近 14 天 `daily_records` 缓存做当前对账,但长期监督真值与训练特征已经迁到 SQLite 永久表中,并支持基于 WU(Weather Underground 口径)四舍五入的结算命中评估。 趋势/概率引擎在 `trend_engine.py` 中实现:综合“集合预报区间→σ/μ→高温窗口→死盘判定→温度桶概率分布→边界提示”等,用于 bot 展示与 web 结构化数据输出。 -**城市决策层(Scan Terminal / 结构化实况层)**:地图点击城市后加入城市决策卡,前端拉取 full detail、多模型区间、最新 METAR,并通过 `/api/city/{name}/detail` 生成城市级 结构化解读。该解读由 `structured_signal`、`structured_signal`、`reasoning`、`structured_signal`、`risks` 与原始 METAR 证据组成;最高温中枢优先使用 DEB,再回退到 DEB、多模型中心、日内 pace 或当前实测。 +**城市决策层(Scan Terminal / 结构化实况层)**:地图点击城市后加入城市决策卡,前端拉取 full detail、多模型区间、最新 METAR/官方站点/跑道观测,并通过 `/api/city/{name}/detail` 生成城市级结构化数据。终端图表使用 HTTP snapshot + SSE patch + Redis/SQLite replay,最高温中枢优先使用 DEB hourly consensus,再结合多模型中心、日内 pace 和当前实测。 **市场层(Polymarket 行情对照)**:*[v1.7.0 已移除]* 原先从 Gamma API 发现市场、从 CLOB 读取价格/盘口并计算”模型-市场差”,已于 2026-05-23 随 Polymarket 价格拉取层一并删除。当前 `market_scan` 返回空。 **商业化与支付**:订阅(`Pro Monthly 10 USDC`)、积分抵扣、Polygon 链上收款合约(USDC/USDC.e),并提供“事件监听 + 周期确认”的自动补单机制。 **支持的数据集/数据源**:项目不是传统“训练数据集+模型训练”的机器学习仓库;其“数据集”本质是外部实时/预报 API 与站点观测数据。对外部数据的使用需要遵守来源方的访问与速率限制,例如 AviationWeather Data API 明确限制请求频率(含每分钟请求上限/建议降低频率与使用缓存文件)。 **许可证**:仓库根目录 `LICENSE` 当前为 `AGPL-3.0-only`。同时 README 与策略文档明确:品牌、商标、生产私有数据与运营策略不随代码许可证一并授权。 -(插图:项目 README 中包含产品截图,可用于快速理解信息架构与 UI 形态) -![PolyWeather demo map](https://raw.githubusercontent.com/yangyuan-zhen/PolyWeather/main/docs/images/demo_map.png) +(插图:项目 README 中包含产品截图,可用于快速理解实时终端与 Telegram 推送形态) +![PolyWeather realtime terminal](https://raw.githubusercontent.com/yangyuan-zhen/PolyWeather/main/frontend/public/static/web.png) ## 架构与代码库分析 @@ -45,7 +45,7 @@ DEB(Dynamic Error Balancing)基于过去 N 天模型误差(MAE)倒数加 flowchart TB subgraph Clients WEB[Next.js Frontend
Vercel] - CITYCARD[Scan Terminal City Cards
AI airport read + bucket mapping] + CITYCARD[Scan Terminal City Cards
structured observations + probability mapping] TG[Telegram Bot
TeleBot + Handlers] end @@ -127,7 +127,7 @@ Scan Terminal 的城市决策卡现在承担“从天气分析到市场动作解 **Python 依赖**:`requirements.txt` 包含 `requests`、`loguru`、`pyTelegramBotAPI`、`python-dotenv`、`numpy`、`web3`、`fastapi`、`uvicorn` 等,符合“采集+bot+api+链上交互”的需求。 **容器环境**:`Dockerfile` 基于 `python:3.11-slim`,默认启动 bot;`docker-compose.yml` 通过不同 command 分别启动 bot 与 web(`python bot_listener.py` / `python web/app.py`),并挂载运行态数据目录。 -**前端依赖**:前端 README 描述 Next.js、Leaflet、Chart.js、Supabase Auth、WalletConnect 等;`frontend/package.json` 是前端依赖来源。 +**前端依赖**:前端 README 描述 Next.js、Leaflet、Recharts、Supabase Auth、WalletConnect 等;`frontend/package.json` 是前端依赖来源。 ### 数据预处理、模型与“训练/推理”管线 本项目的“模型”主要是统计融合与规则/启发式引擎,而非深度网络训练: @@ -181,7 +181,7 @@ Web/Telegram 请求 → FastAPI 调用采集器抓取/复用缓存 → 分析引 **对标结论**:PolyWeather 与这类“全球神经天气模型”不在同一层级:PolyWeather 是“面向结算市场的产品化情报系统”,其价值核心是**将预测转成可交易/可结算的决策信息**。短中期内更高 ROI 的方向不是“自训大模型”,而是把现有“采集+后处理+市场映射”的链路做成**可复现、可观测、可评测、可扩展**的工程平台;在许可合规前提下,再评估引入外部模型推理作为额外信号源。 ## 优先级改进建议 -下表按截至 `2026-05-23` 的真实状态重排优先级。已完成项不再继续列为”待做”,只保留当前仍需推进的事项。 +下表按截至 `2026-05-28` 的真实状态重排优先级。已完成项不再继续列为”待做”,只保留当前仍需推进的事项。 | 优先级 | 改进项 | 预估工作量 | 主要收益 | 主要风险 | 可执行步骤(建议顺序) | | ------ | --------------------------------------------------------------------------------------------------------------------------------- | -------------------: | ------------------------------------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 中 | **把最小外部监控继续补深**:从“可告警”提升到“可运营” | 3–7 天 | 不再只知道服务坏没坏,还能看资源趋势、来源 SLA 和支付波动 | 指标过多会带来维护噪音 | 1) 增加节点 CPU/内存/磁盘 → 2) 增加 SQLite/支付体积与事件趋势 → 3) 把 HTTP/来源指标细分到城市/来源维度 → 4) 增加日报或异常摘要 | @@ -270,5 +270,3 @@ PolyWeather 的评测应围绕“结算场景”而非传统数值天气预报 - Pangu-Weather(权重 BY-NC-SA,禁商用):https://github.com/198808xc/Pangu-Weather - Polymarket 官方 Python CLOB SDK(MIT):https://github.com/Polymarket/py-clob-client - aiopolymarket(async、类型安全):https://github.com/the-odds-company/aiopolymarket - - diff --git a/docs/images/demo_ankara.png b/docs/images/demo_ankara.png deleted file mode 100644 index 7e12554e..00000000 Binary files a/docs/images/demo_ankara.png and /dev/null differ diff --git a/docs/images/demo_map.png b/docs/images/demo_map.png deleted file mode 100644 index 7cc1bc2a..00000000 Binary files a/docs/images/demo_map.png and /dev/null differ diff --git a/docs/payments/POLYGONSCAN_VERIFY.md b/docs/payments/POLYGONSCAN_VERIFY.md index 721e618d..e98d02f3 100644 --- a/docs/payments/POLYGONSCAN_VERIFY.md +++ b/docs/payments/POLYGONSCAN_VERIFY.md @@ -1,4 +1,4 @@ -# PolyWeatherCheckout PolygonScan 验证(v1.8.0) +# PolyWeatherCheckout PolygonScan 验证(v1.8.1) 最后更新:`2026-03-20` diff --git a/docs/product-review-jun-2026.md b/docs/product-review-jun-2026.md index 57d5eccf..8c45fa4b 100644 --- a/docs/product-review-jun-2026.md +++ b/docs/product-review-jun-2026.md @@ -1,6 +1,6 @@ # PolyWeather 前端产品审查报告 -> 审查日期:2026-06 | 视角:产品经理 | 范围:`frontend/` 全部页面、组件、用户流程 +> 审查日期:2026-06 | 视角:产品经理 | 范围:`frontend/` 全部页面、组件、用户流程 | 最后校准:2026-05-28 ## 一、产品概览 @@ -18,7 +18,7 @@ PolyWeather 是一个面向天气衍生品交易者的气象情报平台。核 | 路由 | 功能 | 是否必需登录 | |------|------|-------------| -| `/` | 主看板 — AI 天气决策台 | 否 | +| `/` | 主看板 — 实时天气决策台 | 否 | | `/account` | 账户中心 — 身份/订阅/钱包/积分/Bot 绑定 | 是 | | `/auth/login` | 登录页(Google OAuth + 邮箱密码) | 否 | | `/docs/[...slug]` | 产品文档中心(8 篇双语文档) | 否 | @@ -37,7 +37,7 @@ PolyWeather 是一个面向天气衍生品交易者的气象情报平台。核 │ 点击城市 → 自动添加到决策卡工作区 + 切换到卡片视图 │ │ 免费用户和 Pro 用户均可使用 │ ├─ 决策卡(分析) ──────────────────────────────┤ -│ 钉选的城市卡片:AI 机场解读 + 模型集群 + 市场层 + 图表 │ +│ 钉选的城市卡片:结构化实况 + 模型集群 + 市场层 + 实时图表 │ │ 需要 Pro 订阅 │ └──────────────────────────────────────────────┘ └── 右侧栏:城市简报面板(始终可见,免费可用) @@ -46,7 +46,7 @@ PolyWeather 是一个面向天气衍生品交易者的气象情报平台。核 ### 关键用户路径 1. **新用户落地** → 看到 3 步引导 → 地图 + 城市列表 → 点击城市 → 看到城市简报 → 想深入分析 → 遇到 Pro 付费墙(含功能说明) -2. **Pro 用户工作流** → 地图选城市 → 自动钉选到决策卡 → 展开卡片 → 阅读 AI 报文解读 → 查看市场层 → 判断交易方向 +2. **Pro 用户工作流** → 地图选城市 → 自动钉选到决策卡 → 展开卡片 → 查看实测/DEB/概率/市场层 → 判断交易方向 ## 三、做得好的地方 @@ -67,4 +67,3 @@ PolyWeather 是一个面向天气衍生品交易者的气象情报平台。核 | 1 | **Docs 无搜索** — 8 篇文档没有搜索功能,用户必须逐篇浏览 | 🟢 待做 | | 2 | **Ops 面板无审计日志** — 管理员补发积分等操作没有审计记录 | 🟢 需后端 | | 3 | **注册后邮件验证引导** — 未验证邮箱的用户反复登录失败 | 🟡 需后端 | - diff --git a/docs/superpowers/plans/2026-05-27-redis-stream-realtime-event-architecture.md b/docs/superpowers/plans/2026-05-27-redis-stream-realtime-event-architecture.md index 1653da9c..a6640d70 100644 --- a/docs/superpowers/plans/2026-05-27-redis-stream-realtime-event-architecture.md +++ b/docs/superpowers/plans/2026-05-27-redis-stream-realtime-event-architecture.md @@ -4,6 +4,8 @@ **Goal:** Add Redis Stream as the production realtime event store and live fanout source while preserving the existing SSE patch protocol. +**Implementation status (2026-05-28):** completed in `v1.8.1`. Production uses Redis Stream when `POLYWEATHER_EVENT_STORE=redis`; SQLite remains the local/fallback event log. Browser API and frontend contract stayed on `/api/events` + `city_observation_patch.v1`. + **Architecture:** Keep `city_observation_patch.v1` and numeric `revision` as the browser contract. Add a Redis-backed event store beside the existing SQLite store, select it through an event-store factory, and let Redis-backed deployments fan out live events through a Redis subscriber loop instead of direct in-process ingest broadcast. **Tech Stack:** FastAPI, redis-py, Redis Stream, SQLite fallback, pytest, existing frontend EventSource hook. @@ -16,21 +18,21 @@ - Create: `tests/test_redis_realtime_event_store.py` - Create: `web/redis_realtime_event_store.py` -- [ ] **Step 1: Write failing tests** +- [x] **Step 1: Write failing tests** Cover append, replay by city, replay gap, and fallback-independent event shape. -- [ ] **Step 2: Verify tests fail** +- [x] **Step 2: Verify tests fail** Run: `python -m pytest tests/test_redis_realtime_event_store.py -q` Expected: fails because `web.redis_realtime_event_store` does not exist. -- [ ] **Step 3: Implement Redis store** +- [x] **Step 3: Implement Redis store** Add `RedisRealtimeEventStore` with `append_event`, `latest_revision`, `replay_events`, `replay_requires_resync`, and idempotent `start_live_subscription`. -- [ ] **Step 4: Verify tests pass** +- [x] **Step 4: Verify tests pass** Run: `python -m pytest tests/test_redis_realtime_event_store.py -q` @@ -41,19 +43,19 @@ Run: `python -m pytest tests/test_redis_realtime_event_store.py -q` - Modify: `web/routers/sse_router.py` - Test: `tests/test_sse_replay.py` -- [ ] **Step 1: Write failing tests** +- [x] **Step 1: Write failing tests** Cover `POLYWEATHER_EVENT_STORE=redis` selecting Redis, SQLite fallback when Redis is not configured, and Redis ingest not doing direct local broadcast. -- [ ] **Step 2: Verify tests fail** +- [x] **Step 2: Verify tests fail** Run: `python -m pytest tests/test_sse_replay.py tests/test_realtime_event_store_factory.py -q` -- [ ] **Step 3: Implement factory and router integration** +- [x] **Step 3: Implement factory and router integration** Use Redis store when configured; otherwise keep existing SQLite store. Ingest broadcasts directly only for stores that do not provide external live fanout. -- [ ] **Step 4: Verify tests pass** +- [x] **Step 4: Verify tests pass** Run: `python -m pytest tests/test_sse_replay.py tests/test_realtime_event_store_factory.py -q` @@ -63,11 +65,11 @@ Run: `python -m pytest tests/test_sse_replay.py tests/test_realtime_event_store_ - Modify: `requirements.txt` - Modify: `docs/superpowers/specs/2026-05-27-redis-stream-realtime-event-architecture-design.md` -- [ ] **Step 1: Add redis-py dependency** +- [x] **Step 1: Add redis-py dependency** Add `redis>=5.0.0` to `requirements.txt`. -- [ ] **Step 2: Add final config notes** +- [x] **Step 2: Add final config notes** Ensure the design doc names the runtime env vars used by code. @@ -76,7 +78,7 @@ Ensure the design doc names the runtime env vars used by code. **Files:** - No production edits unless tests reveal a gap. -- [ ] **Step 1: Run backend realtime tests** +- [x] **Step 1: Run backend realtime tests** Run: @@ -84,7 +86,7 @@ Run: python -m pytest tests/test_realtime_patch_schema.py tests/test_realtime_event_store.py tests/test_redis_realtime_event_store.py tests/test_realtime_event_store_factory.py tests/test_sse_replay.py -q ``` -- [ ] **Step 2: Run broader frontend/backend checks** +- [x] **Step 2: Run broader frontend/backend checks** Run: @@ -95,7 +97,7 @@ npm run typecheck npm run build ``` -- [ ] **Step 3: Inspect diff** +- [x] **Step 3: Inspect diff** Run: diff --git a/docs/superpowers/specs/2026-05-27-redis-stream-realtime-event-architecture-design.md b/docs/superpowers/specs/2026-05-27-redis-stream-realtime-event-architecture-design.md index f385159e..d46238cf 100644 --- a/docs/superpowers/specs/2026-05-27-redis-stream-realtime-event-architecture-design.md +++ b/docs/superpowers/specs/2026-05-27-redis-stream-realtime-event-architecture-design.md @@ -3,6 +3,7 @@ > 日期: 2026-05-27 > 范围: PolyWeather 网站图表实时观测事件层 > 目标服务器: 2 vCPU / 8 GB RAM / 50 GB 系统盘 +> 实施状态: 2026-05-28 已落地为 `v1.8.1` ## 背景 @@ -29,7 +30,7 @@ - 前端协议保持 `city_observation_patch.v1`,不让浏览器感知 Redis。 - 继续使用 numeric `revision`,兼容现有前端 `since_revision` 逻辑。 - 支持按城市订阅、断线 replay、多 worker fanout。 -- 事件只保留短窗口,默认 24 小时;生产不建议超过 24 小时。 +- 事件只保留短窗口;当前默认按 Redis Stream `MAXLEN` 控制,建议以约 24 小时 replay 为目标,不做 72 小时保留。 ## 非目标 diff --git a/frontend/README.md b/frontend/README.md index eec8aa94..90a2a617 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -9,7 +9,7 @@ PolyWeather Pro 的生产前端工程。 - Next.js App Router - React + Tailwind -- Leaflet + Chart.js +- Leaflet + Recharts - Supabase Auth - WalletConnect + 浏览器 EVM 钱包 @@ -23,6 +23,11 @@ PolyWeather Pro 的生产前端工程。 - 主站 Dashboard 支持地图、城市详情、今日日内分析和账户中心 - `/docs` 已提供公开双语产品文档中心,解释日内分析、校准概率、模型栈、TAF 和结算来源 +- 终端图表默认展示全天,可切换高温窗口;所有横轴与 tooltip 时间按城市当地时间渲染 +- 可见终端图表通过 SSE patch 无痛增量刷新,后台切回前台时主动补齐最新 detail,不用 loading 遮罩覆盖已有曲线 +- AMSC/AMOS 跑道城市默认展示结算跑道曲线并高亮,辅助跑道弱化展示;单跑道机场不重复展示聚合线 +- 香港默认展示 CoWIN 6087 参考站 1 分钟曲线,并保留 HKO 10 分钟官方气象层 +- legacy 高斯概率在图表上展示为概率温度带和 `mu` 参考线,不作为时间序列曲线 - 今日日内分析支持: - `锚点状态` - `当前节奏` @@ -185,6 +190,8 @@ Ops: - 支付相关路由:`no-store` - 当 detail 缓存只返回单模型或单日 forecast 时,前端会自动强刷完整 detail,并在补齐前显示同步提示 / 占位卡 - 今日日内分析打开时如果正在切换城市、日期或 detail 深度,弹窗会阻断旧内容点击并显示刷新锁 +- 终端图表订阅 `/api/events?cities=...&since_revision=...&replay_limit=500`,接收 `city_observation_patch.v1`;无 patch 超过 2 分钟时,可见图表才触发 60 秒兜底刷新 +- 前端只消费 HTTP snapshot + SSE patch,不直接感知 Redis;Redis Stream / SQLite event log 都由后端统一封装 ## Vercel 节流建议 @@ -199,4 +206,4 @@ Ops: 详见根目录策略文档:`docs/OPEN_CORE_POLICY.md` -最后更新:`2026-05-23` +最后更新:`2026-05-28` diff --git a/frontend/components/landing/InstitutionalLandingPage.tsx b/frontend/components/landing/InstitutionalLandingPage.tsx index bc3474f3..dbd11334 100644 --- a/frontend/components/landing/InstitutionalLandingPage.tsx +++ b/frontend/components/landing/InstitutionalLandingPage.tsx @@ -3,18 +3,14 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { - Activity, ArrowRight, - BarChart3, Check, CheckCircle2, - CloudSun, Gauge, LineChart, LockKeyhole, Radar, ShieldCheck, - TrendingUp, } from "lucide-react"; import { I18nProvider, useI18n } from "@/hooks/useI18n"; import { @@ -22,22 +18,6 @@ import { hasSupabasePublicEnv, } from "@/lib/supabase/client"; -const RAW_MARKET_ROWS_EN = [ - ["New York", "91.8°F", "+2.4", "High Anomaly", "Approve Temp"], - ["Austin", "103.1°F", "+1.1", "Normal", "Observe"], - ["Seoul", "83.4°F", "-0.7", "Low Anomaly", "Veto Anomaly"], - ["Tokyo", "88.2°F", "+1.8", "High Anomaly", "Approve Temp"], - ["London", "72.6°F", "-0.2", "Normal", "Observe"], -]; - -const RAW_MARKET_ROWS_ZH = [ - ["纽约", "91.8°F", "+2.4", "高偏差", "符合高温"], - ["奥斯汀", "103.1°F", "+1.1", "正常", "持续观察"], - ["首尔", "83.4°F", "-0.7", "低偏差", "否决偏差"], - ["东京", "88.2°F", "+1.8", "高偏差", "符合高温"], - ["伦敦", "72.6°F", "-0.2", "正常", "持续观察"], -]; - const COVERAGE_EN = [ "Live airport observations", "DEB blend forecast", @@ -94,7 +74,6 @@ function InstitutionalLandingScreen() { }); }, []); - const marketRows = isEn ? RAW_MARKET_ROWS_EN : RAW_MARKET_ROWS_ZH; const coverage = isEn ? COVERAGE_EN : COVERAGE_ZH; const platformCards = isEn ? [ @@ -132,10 +111,6 @@ function InstitutionalLandingScreen() { }, ]; - const modelLabels = isEn - ? ["DEB Blend", "Live METAR", "Model Consensus"] - : ["DEB 融合预测", "METAR 机场实测", "模型多方共识"]; - return (
@@ -263,86 +238,26 @@ function InstitutionalLandingScreen() {

-
+
- - {isEn ? "Weather Intelligence Console" : "气象决策分析台"} + + {isEn ? "Realtime Terminal" : "实时终端"}
- {isEn ? "Live" : "实时数据"} + {isEn ? "Real product screenshot" : "真实产品截图"}
-
-
-
- {isEn ? "Temperature Metrics" : "温度决策指标"} - - {isEn ? "Observed / Deviation / Decision" : "实测 / 偏差 / 决策"} - -
-
- {marketRows.map((row) => ( -
- {row[0]} - {row[1]} - - {row[2]} - - - {row[3]} - - - {row[4]} - -
- ))} -
-
-
-
-
- {isEn ? "Model Stack" : "模型堆栈"} - -
-
- {modelLabels.map((label, index) => ( -
-
- {label} - {[82, 67, 74][index]}% -
-
-
-
-
- ))} -
-
-
-
- - {isEn ? "Current Anomaly / Action" : "当前异常与决策"} -
-

- {isEn - ? "New York high-temperature target shows a positive observation deviation with confirmed airport evidence." - : "纽约高温阈值监测出现显著的正向观测偏差,机场天气实况已验证确认。"} -

-
+
+
+ {isEn
@@ -420,8 +335,8 @@ function InstitutionalLandingScreen() {

{isEn - ? "Full access to the institutional weather intelligence workspace. Live METAR, DEB forecasts, probability distribution, AI decision cards, and real-time alerts." - : "完整访问机构级天气决策分析台。实时 METAR、DEB 预报、概率分布、AI 决策卡片、实时通知。"} + ? "Full access to the institutional weather intelligence workspace. Live METAR, DEB forecasts, probability distribution, realtime terminal charts, and alerts." + : "完整访问机构级天气决策分析台。实时 METAR、DEB 预报、概率分布、实时终端图表、实时通知。"}

diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ca24211a..a46e22e2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "polyweather-frontend", - "version": "1.7.0", + "version": "1.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "polyweather-frontend", - "version": "1.7.0", + "version": "1.8.1", "dependencies": { "@radix-ui/react-slot": "^1.1.2", "@supabase/ssr": "^0.5.2", diff --git a/frontend/package.json b/frontend/package.json index 8c425b02..f97fbb3d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "polyweather-frontend", - "version": "1.8.0", + "version": "1.8.1", "private": true, "scripts": { "dev": "node scripts/sync-next-server-chunks.mjs --clean-root && next dev", diff --git a/frontend/public/static/tel.png b/frontend/public/static/tel.png new file mode 100644 index 00000000..a5f8e864 Binary files /dev/null and b/frontend/public/static/tel.png differ diff --git a/frontend/public/static/web.png b/frontend/public/static/web.png new file mode 100644 index 00000000..7cad6036 Binary files /dev/null and b/frontend/public/static/web.png differ