docs: refresh v1.8.1 release docs

This commit is contained in:
2569718930@qq.com
2026-05-28 20:46:35 +08:00
parent 784b27a954
commit c2fe8caddf
30 changed files with 397 additions and 229 deletions
+7
View File
@@ -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
+13
View File
@@ -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
### 新增与重构
+32 -11
View File
@@ -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`
+32 -13
View File
@@ -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 个监控城市的实测与预报数据。
- DEBDynamic 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<br/>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`
+1 -1
View File
@@ -1 +1 @@
1.8.0
1.8.1
+33 -4
View File
@@ -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` |
## 未接入城市
+46 -4
View File
@@ -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=<int>`:可选,断线重连后从指定 revision 之后 replay。
- `replay_limit=<int>`:可选,默认 `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 仍是完整 snapshotSSE 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 小时
+32 -4
View File
@@ -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` 参考线,帮助用户判断当前实测距概率中心和高概率区域的关系。
+7 -3
View File
@@ -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. 后续路线
+14
View File
@@ -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 分钟)。
+9
View File
@@ -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`
## 九、常见问题
+36 -2
View File
@@ -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 归一化接入。
+17 -1
View File
@@ -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 告警
+36 -26
View File
@@ -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 城 | ✅ |
| --- | --- | --- |
| JMA AMeDAS | Tokyo | ✅ |
| AMOS (韩国) | Seoul, Busan 跑道传感器 | ✅ |
| AMSC AWOS (中国) | 北京上海广州、成都、重庆、武汉、青岛跑道端点气温 | ✅ |
| MGM (土耳其) | Ankara, Istanbul | ✅ |
| FMI (芬兰) | Helsinki | ✅ |
| HKO (香港) | Hong Kong | ✅ |
| KNMI (荷兰) | Amsterdam | ✅(需 key |
| CoWIN 6087 (香港) | Hong Kong 1 分钟参考站 | ✅ |
| HKO (香港) | Hong Kong / Shenzhen / Lau Fau Shan 10 分钟官方气象层 | ✅ |
| CWA (台湾) | Taipei | ✅ |
| NMC (中国) | 国内城市 fallback | ✅ |
| Singapore MSS | Singapore | ✅ |
| IMS Lod (以色列) | Tel Aviv | ✅ |
| AEROWEB / AROME HD | Paris | ✅ |
| NMC (中国) | 国内城市 fallback | ✅ |
| IMGW (波兰) | Warsaw | ⚠️ 未配 key |
| Russia pogodaiklimat | Moscow | ❌ 已移除 |
## 可选 / 已禁用
| 服务 | 用途 | 状态 |
| -------------- | ------------- | ----------- |
| --- | --- | --- |
| OpenWeatherMap | 天气 fallback | ⚠️ 未配 key |
| VisualCrossing | 历史天气 | ⚠️ 未配 key |
| Meteoblue | 天气预报 | ❌ 已移除 |
| 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,不把缺失模型误报为实测缺失。
+1 -1
View File
@@ -1,4 +1,4 @@
# Supabase + 登录 + 支付接入说明(v1.8.0
# Supabase + 登录 + 支付接入说明(v1.8.1
最后更新:`2026-03-14`
+1 -1
View File
@@ -1,4 +1,4 @@
# 技术债与工程待办(v1.8.0
# 技术债与工程待办(v1.8.1
最后更新:`2026-05-10`
+12 -12
View File
@@ -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。
## 六、待办
+8 -10
View File
@@ -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/CRPSGroq、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/趋势/概率/结算口径)**:
DEBDynamic Error Balancing)基于过去 N 天模型误差(MAE)倒数加权,输出融合预报;运行态仍维护近 14 天 `daily_records` 缓存做当前对账,但长期监督真值与训练特征已经迁到 SQLite 永久表中,并支持基于 WUWeather 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 @@ DEBDynamic Error Balancing)基于过去 N 天模型误差(MAE)倒数加
flowchart TB
subgraph Clients
WEB[Next.js Frontend<br/>Vercel]
CITYCARD[Scan Terminal City Cards<br/>AI airport read + bucket mapping]
CITYCARD[Scan Terminal City Cards<br/>structured observations + probability mapping]
TG[Telegram Bot<br/>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 SDKMIT):https://github.com/Polymarket/py-clob-client
- aiopolymarketasync、类型安全):https://github.com/the-odds-company/aiopolymarket
Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
# PolyWeatherCheckout PolygonScan 验证(v1.8.0
# PolyWeatherCheckout PolygonScan 验证(v1.8.1
最后更新:`2026-03-20`
+4 -5
View File
@@ -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 | **注册后邮件验证引导** — 未验证邮箱的用户反复登录失败 | 🟡 需后端 |
@@ -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:
@@ -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 小时保留
## 非目标
+9 -2
View File
@@ -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,不直接感知 RedisRedis Stream / SQLite event log 都由后端统一封装
## Vercel 节流建议
@@ -199,4 +206,4 @@ Ops
详见根目录策略文档:`docs/OPEN_CORE_POLICY.md`
最后更新:`2026-05-23`
最后更新:`2026-05-28`
@@ -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 (
<div className="min-h-screen bg-[#f4f7fb] text-slate-950">
<header className="sticky top-0 z-30 border-b border-slate-200 bg-white/95 backdrop-blur">
@@ -263,88 +238,28 @@ function InstitutionalLandingScreen() {
</p>
</div>
<div className="rounded-2xl border border-slate-300 bg-white shadow-[0_24px_80px_rgba(15,23,42,0.16)]">
<div className="overflow-hidden rounded-2xl border border-slate-300 bg-white shadow-[0_24px_80px_rgba(15,23,42,0.16)] animate-fade-up [animation-delay:650ms] opacity-0">
<div className="flex items-center justify-between border-b border-slate-200 px-4 py-3">
<div className="flex items-center gap-2 text-sm font-bold">
<BarChart3 size={16} className="text-blue-700" />
{isEn ? "Weather Intelligence Console" : "气象决策分析台"}
<LineChart size={16} className="text-blue-700" />
{isEn ? "Realtime Terminal" : "实时终端"}
</div>
<div className="flex items-center gap-2 text-xs font-semibold text-slate-500">
<span className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse" />
{isEn ? "Live" : "实时数据"}
{isEn ? "Real product screenshot" : "真实产品截图"}
</div>
</div>
<div className="grid gap-3 p-3 lg:grid-cols-[1fr_0.85fr]">
<div className="rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
<div className="flex items-center justify-between border-b border-slate-200 bg-slate-50/80 px-4 py-2.5">
<strong className="text-sm text-slate-800">{isEn ? "Temperature Metrics" : "温度决策指标"}</strong>
<span className="text-xs font-semibold text-slate-500 uppercase tracking-wider">
{isEn ? "Observed / Deviation / Decision" : "实测 / 偏差 / 决策"}
</span>
</div>
<div className="divide-y divide-slate-100">
{marketRows.map((row) => (
<div
key={row[0]}
className="grid grid-cols-[1.1fr_0.8fr_0.6fr_0.7fr_0.9fr] items-center gap-2 px-3 py-3 text-sm"
>
<span className="font-semibold">{row[0]}</span>
<span className="font-mono text-slate-700">{row[1]}</span>
<span
className={
row[2].startsWith("+")
? "font-mono font-bold text-emerald-700"
: "font-mono font-bold text-red-600"
}
>
{row[2]}
</span>
<span className="text-xs font-bold text-slate-500">
{row[3]}
</span>
<span className="rounded bg-blue-50 px-2 py-1 text-xs font-bold text-blue-700">
{row[4]}
</span>
</div>
))}
</div>
</div>
<div className="space-y-3">
<div className="rounded-xl border border-slate-200 p-4">
<div className="mb-3 flex items-center justify-between">
<strong className="text-sm">{isEn ? "Model Stack" : "模型堆栈"}</strong>
<LineChart size={16} className="text-blue-700" />
</div>
<div className="space-y-3">
{modelLabels.map((label, index) => (
<div key={label}>
<div className="mb-1 flex justify-between text-xs font-semibold text-slate-500">
<span>{label}</span>
<span>{[82, 67, 74][index]}%</span>
</div>
<div className="h-2 rounded-full bg-slate-100">
<div
className="h-2 rounded-full bg-blue-600"
style={{ width: `${[82, 67, 74][index]}%` }}
<div className="bg-slate-50 p-2 sm:p-3">
<div className="overflow-hidden rounded-xl border border-slate-200 bg-white">
<img
src="/static/web.png"
alt={isEn ? "PolyWeather realtime terminal screenshot" : "PolyWeather 实时终端截图"}
className="block h-auto w-full object-contain"
loading="eager"
decoding="async"
/>
</div>
</div>
))}
</div>
</div>
<div className="rounded-xl border border-emerald-200 bg-emerald-50 p-4">
<div className="mb-2 flex items-center gap-2 text-sm font-bold text-emerald-800">
<TrendingUp size={16} className="animate-pulse" />
{isEn ? "Current Anomaly / Action" : "当前异常与决策"}
</div>
<p className="text-sm leading-relaxed text-emerald-900/90 font-medium">
{isEn
? "New York high-temperature target shows a positive observation deviation with confirmed airport evidence."
: "纽约高温阈值监测出现显著的正向观测偏差,机场天气实况已验证确认。"}
</p>
</div>
</div>
</div>
</div>
</section>
@@ -420,8 +335,8 @@ function InstitutionalLandingScreen() {
</h3>
<p className="mt-3 text-sm text-slate-500 leading-relaxed">
{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 预报、概率分布、实时终端图表、实时通知。"}
</p>
<div className="mt-6 flex items-baseline">
<span className="text-5xl font-black tracking-tight text-slate-900">
+2 -2
View File
@@ -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",
+1 -1
View File
@@ -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",
Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB