尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Live hyperlocal weather station dashboard for Midtown Manhattan: Astro on Cloudflare Workers, AWS CDK and Lambda, analog and rain-probability models.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Live hyperlocal weather dashboard for Midtown Manhattan, New York. Data from a private Ambient Weather WS-2902 station, updated every 5 minutes.查看完整 README ↗Live hyperlocal weather dashboard for Midtown Manhattan, New York. Data from a private Ambient Weather WS-2902 station, updated every 5 minutes.
Live: wx.jamestannahill.com · API: api.wx.jamestannahill.com/current
client:visible): ~90 KB of chart JS only loads when the chart scrolls into viewPublic, read-only, no authentication required.
| Endpoint | Description |
|---|---|
GET /current | Latest reading with all ML signals and metadata |
GET /history?hours=N | Last N hours of readings (max 720), downsampled for longer ranges, with per-slot baselines and std dev |
GET /rain-events?days=N | Parsed rain events from the last N days (default 30) |
GET /daily-summaries?days=N | Pre-computed daily summaries from the last N days (default 30) |
GET /nearby | Latest snapshot of nearby personal weather stations |
GET /health | Health check: data freshness and poller status |
GET /forecast | Standalone analog forecast |
GET /records | Standalone station records |
GET /climate-context | NOAA/ERA5 climate context |
GET /alerts | Active NWS alerts for station point |
GET /export?days=N&format=csv | Bulk export (json or csv) |
GET /openapi.json | OpenAPI 3 specification |
The analog forecast, station records, and daily summary are embedded in the GET /current response (forecast, station_records, daily_summary); there are no standalone endpoints for them.
Ambient Weather WS-2902
│ (Wi-Fi, every 5 min)
▼
Ambient Weather Cloud API
│
▼
wx-poller (Lambda, EventBridge 5-min)
• Fetches reading
• Range + stuck-sensor validation
• NOAA METAR fetch → UHI delta
• Welford rolling stats (mean + variance per MM-HH slot)
• Writes to wx-readings (90-day TTL)
│
├──► wx-forecaster (Lambda, 30-min)
│ • Nearest-neighbor analog forecast on 90-day hourly data
│ • Evaluates previous forecast accuracy → MAE stored
│
├──► wx-ml-fitter (Lambda, weekly Sun 3AM UTC)
│ • Scans 90-day readings, labels rain events
│ • Fits logistic regression w/ class weighting
│ • Stores coefficients to wx-ml-models if F1 ≥ 0.05
│
├──► wx-summarizer (Lambda, daily 5AM UTC)
│ • Processes last 30 days (idempotent)
│ • Computes daily stats + prose summary
│ • Writes to wx-daily-summaries
│
├──► wx-records-tracker (Lambda, weekly Sun 2AM UTC)
│ • Scans 90-day readings
│ • Computes per-month station records
│ • Writes to wx-station-records
│
├──► wx-alerter (Lambda, 15-min)
│ • Evaluates anomaly thresholds
│ • Sends SES email on trigger (2h cooldown)
│
├──► wx-climate-bootstrap (Lambda, one-time)
│ • Backfills 156yr NOAA Central Park daily records (noaa-ghcn-pds S3)
│ • Backfills 85yr ERA5 reanalysis via Open-Meteo
│ • Writes to wx-climate-normals
│
└──► wx-climate-updater (Lambda, daily 6AM UTC)
• Appends yesterday's NOAA + ERA5 observations
• Keeps climate baseline current
wx-api (Lambda, API Gateway HTTP API → CloudFront)
• /current : live reading + all ML signals + climate context + daily summary + records
• /history : downsampled readings with baselines + std dev
• /rain-events: parsed rain events
• /daily-summaries: pre-computed day summaries
• /nearby : latest nearby-stations snapshot
Cloudflare Worker (Astro 6 SSR) → wx.jamestannahill.com (dashboard)
• Hero + 12 conditions cards SSR'd at request time from /current
→ first paint shows real values, AI crawlers see the whole dashboard
• Edge-cached 60s (s-maxage=60, stale-while-revalidate=300)
• /og.png generated on demand at the edge via Satori + resvg-wasm
(workers-og); a real snapshot keyed by ?v= is edge-cached 5 min,
a degraded render (API miss) is served uncached so the next
crawler scrape recovers the current snapshot; no AWS dependency
• Chart shipped as Preact island, hydrates on client:visible
• Partytown moves GA off the main thread; /docs prefetched on hover
API Gateway + CloudFront → api.wx.jamestannahill.com
| Layer | Technology |
|---|---|
| Station | Ambient Weather WS-2902, Wi-Fi gateway |
| Compute | AWS Lambda (Python 3.12, arm64): API + ingest only |
| Scheduling | AWS EventBridge |
| Storage | AWS DynamoDB (on-demand, 12 tables) |
| API | AWS API Gateway HTTP API |
| CDN | AWS CloudFront (API only); Cloudflare Workers (dashboard + OG) |
| Dashboard | Astro 6 server-rendered on Cloudflare Workers, Preact island for the chart, vanilla JS for everything else, uPlot, NHG Display font; design rules in astro/DESIGN.md |
| OG image | Generated at the edge via workers-og (Satori + resvg-wasm), bundled NHG Display TTF |
| IaC | AWS CDK (Python) |
| AWS SES | |
| Secrets | AWS Secrets Manager |
| Climate data | 156yr NOAA GHCN-Daily (Central Park) + 85yr ERA5 reanalysis |
| Forecast | Apple WeatherKit API |
| Active alerts | NOAA National Weather Service API |
| Airport METAR | NOAA aviationweather.gov |
| Table | Key | Purpose |
|---|---|---|
wx-readings | station_id + timestamp | Raw 5-min readings, 90-day TTL |
wx-daily-stats | station_id + month_hour | Rolling mean + variance per MM-HH baseline slot |
wx-alerts | alert_type | Alert debounce state |
wx-forecasts | station_id | Latest pre-computed analog forecast |
wx-forecast-accuracy | station_id + evaluated_at | Per-evaluation MAE + running mean |
wx-uhi-seasonal | station_id + month | Monthly rolling UHI delta averages |
wx-ml-models | model_id | Fitted logistic regression weights + metrics |
wx-daily-summaries | station_id + date | Daily stats + prose summary |
wx-station-records | station_id + month | Per-month extreme records with dates |
wx-climate-doy | mmdd | NOAA GHCN per-day-of-year distributions (156yr Central Park) |
wx-climate-hourly | doy + hour | ERA5 per-DOY-hour distributions (85yr reanalysis) |
Logistic regression: p = σ(w·x + b). Features: humidity (normalized), 1-hour pressure trend, dew-point depression, sin/cos of hour-of-day. Weekly refit via gradient descent (300 epochs, L2 regularization, class weighting n_neg/n_pos). Current model: F1=0.396, recall=82%, n=24,348.
1-hour buckets of the last 90 days → normalize to [0,1] → 6-hour fingerprint → nearest 5 Euclidean neighbors → average their next 3-hour trajectories. Confidence = inverse of mean neighbor distance, normalized to 0 to 100%.
Welford's online algorithm accumulates per-slot variance alongside the rolling mean. Transition to EMA variance at 8,640 samples (30 days). Standard deviation is stored as std_{field} per MM-HH slot and served via /history.
On each /current call, climate_context.py loads the NOAA + ERA5 normals for today's calendar date and computes a daily_verdict for both temp_high and temp_low. Each verdict includes: the observed value, the long-run p5/p50/p95 percentiles in °F, a z-score, and a label (much hotter / warmer / normal / cooler / much colder). The dashboard renders deviation bars that start at the historical average and extend to today's value, with the full p5→max(p95, value) range as the track.
lambdas/
shared/ # DynamoDB client, Secrets Manager, UHI fetch
wx_poller/ # 5-min station data collection
wx_api/ # REST API + all signal computation
wx_alerter/ # Anomaly alert emails
wx_bootstrap/ # One-time ERA5 + WU backfill (readings)
wx_climate_bootstrap/ # One-time NOAA GHCN + ERA5 climate normals backfill
wx_climate_updater/ # Daily NOAA + ERA5 climate update
wx_forecaster/ # Analog forecast + accuracy tracking
wx_ml_fitter/ # Weekly logistic regression training
wx_summarizer/ # Daily prose summaries
wx_records_tracker/ # Weekly station records
astro/
src/pages/
index.astro # SSR dashboard (hero + conditions grid pre-rendered)
docs.astro # Prerendered (export const prerender = true)
og.png.ts # Edge-rendered OG image (workers-og + Satori)
sitemap.xml.ts # Dynamic sitemap (lastmod = render date)
404.astro # Branded 404 page
src/middleware.ts # Security headers (HSTS, nosniff, frame, referrer) on all SSR responses
src/layouts/ # Base.astro: shared head, schema, fonts
src/components/
Chart.tsx # Preact island for uPlot chart (client:visible)
SchemaOrgIndex.astro / SchemaOrgDocs.astro
src/og-fonts/ # NHG Display TTFs bundled into the worker for OG render
public/ # app.js, style.css, llms.txt, _headers, weatherkit.png
astro.config.mjs # cloudflare adapter, partytown, preact, prefetch
cdk/
wx_stack.py # CDK stack (Lambdas, DynamoDB, API Gateway, API CloudFront)
app.py # CDK entry point
scripts/
deploy_worker.sh # Build Astro, patch wrangler.json, wrangler deploy
# Deploy AWS infrastructure (Lambdas, DynamoDB, API Gateway, etc.)
cd cdk && npx cdk deploy --require-approval never
# Deploy dashboard Worker to Cloudflare
# - Builds Astro (SSR for /, prerender for /docs, served from docs.html)
# - Patches the auto-generated wrangler.json (name, vars, custom domain)
# - wrangler deploy → wx.jamestannahill.com
bash scripts/deploy_worker.sh
First-time setup needs npx wrangler login (one-shot OAuth) and a DNS record
for wx.jamestannahill.com that doesn't conflict with the Custom Domain.
Estimated cost: about $4 to $6 per month AWS, Worker on the free tier.