feat: Introduce a new Next.js modern frontend with a history API route and include legacy static assets.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const API_BASE = process.env.POLYWEATHER_API_BASE_URL;
|
||||
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
context: { params: Promise<{ name: string }> },
|
||||
) {
|
||||
if (!API_BASE) {
|
||||
return NextResponse.json(
|
||||
{ error: "POLYWEATHER_API_BASE_URL is not configured" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
const { name } = await context.params;
|
||||
const url = `${API_BASE}/api/history/${encodeURIComponent(name)}`;
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: { Accept: "application/json" },
|
||||
cache: "no-store",
|
||||
});
|
||||
if (!res.ok) {
|
||||
const raw = await res.text();
|
||||
return NextResponse.json(
|
||||
{ error: `Backend returned ${res.status}`, detail: raw.slice(0, 300) },
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch history", detail: String(error) },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PolyWeatherDashboard } from "@/components/polyweather-dashboard";
|
||||
|
||||
export default function ModernPage() {
|
||||
return <PolyWeatherDashboard />;
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
import { PolyWeatherDashboard } from "@/components/polyweather-dashboard";
|
||||
|
||||
export default function HomePage() {
|
||||
return <PolyWeatherDashboard />;
|
||||
return (
|
||||
<main className="h-screen w-screen overflow-hidden bg-black">
|
||||
<iframe
|
||||
title="PolyWeather Legacy Dashboard"
|
||||
src="/legacy/index.html"
|
||||
className="h-full w-full border-0"
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PolyWeather — 天气衍生品智能地图</title>
|
||||
<meta name="description" content="PolyWeather 天气衍生品智能地图。实时 METAR、MGM、DEB 融合预报与 AI 分析。">
|
||||
|
||||
<!-- Leaflet -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
|
||||
<!-- Google Fonts: Inter -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- ── Map ── -->
|
||||
<div id="map"></div>
|
||||
|
||||
<!-- ── Header Overlay ── -->
|
||||
<header id="header">
|
||||
<div class="brand">
|
||||
<h1>🌡️ PolyWeather</h1>
|
||||
<span class="subtitle">天气衍生品智能分析</span>
|
||||
</div>
|
||||
<button class="info-btn" id="btnShowGuide" title="查看系统技术说明">ℹ️ 技术说明</button>
|
||||
<div class="live-badge" id="liveBadge">
|
||||
<span class="pulse-dot"></span>
|
||||
<span>实时</span>
|
||||
</div>
|
||||
<button class="refresh-btn" id="refreshAllBtn" title="刷新所有数据">⟳</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ── City List Sidebar (left) ── -->
|
||||
<nav id="cityList" class="city-list">
|
||||
<div class="city-list-header">
|
||||
<span>🏙️ 监控城市</span>
|
||||
<span class="city-count" id="cityCount">0</span>
|
||||
</div>
|
||||
<div id="cityListItems" class="city-list-items">
|
||||
<!-- Dynamically populated -->
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- ── Detail Panel (right) ── -->
|
||||
<aside id="panel" class="detail-panel hidden">
|
||||
<div class="panel-header">
|
||||
<button class="panel-close" id="panelClose">✕</button>
|
||||
<div class="panel-title-area">
|
||||
<h2 id="panelCityName">—</h2>
|
||||
<div class="panel-meta">
|
||||
<span id="panelRiskBadge" class="risk-badge">—</span>
|
||||
<span id="panelLocalTime" class="local-time">—</span>
|
||||
<button class="history-btn" id="btnShowHistory" title="查看历史记录与准确率">📊 历史对账</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="panelContent" class="panel-body">
|
||||
<!-- ── Temperature Hero ── -->
|
||||
<section class="hero-section">
|
||||
<div class="hero-weather" id="heroWeather"></div>
|
||||
<div class="hero-temp">
|
||||
<span class="hero-value" id="heroTemp">—</span>
|
||||
<span class="hero-unit" id="heroUnit">°C</span>
|
||||
</div>
|
||||
<div class="hero-max-time" id="heroMaxTime"></div>
|
||||
<div class="hero-details">
|
||||
<div class="hero-item">
|
||||
<span class="label">📍 当前实测</span>
|
||||
<span class="value" id="heroCurrent">—</span>
|
||||
</div>
|
||||
<div class="hero-item">
|
||||
<span class="label">🎯 WU 结算</span>
|
||||
<span class="value highlight" id="heroWU">—</span>
|
||||
</div>
|
||||
<div class="hero-item">
|
||||
<span class="label">🧬 DEB 预测</span>
|
||||
<span class="value" id="heroDEB">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-sub" id="heroSub">
|
||||
<!-- obs time, cloud, wind -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Trend Sparkline ── -->
|
||||
<section class="chart-section">
|
||||
<h3>📊 今日温度走势</h3>
|
||||
<div class="chart-wrapper">
|
||||
<canvas id="tempChart"></canvas>
|
||||
</div>
|
||||
<div class="chart-legend" id="chartLegend"></div>
|
||||
</section>
|
||||
|
||||
<!-- ── Probability Bars ── -->
|
||||
<section class="prob-section">
|
||||
<h3>🎲 结算概率分布</h3>
|
||||
<div id="probBars" class="prob-bars">
|
||||
<!-- Dynamically populated -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Multi-Model Comparison ── -->
|
||||
<section class="models-section">
|
||||
<h3>🔬 多模型预报</h3>
|
||||
<div id="modelBars" class="model-bars">
|
||||
<!-- Dynamically populated -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Forecast Table ── -->
|
||||
<section class="forecast-section">
|
||||
<h3>📅 多日预报</h3>
|
||||
<div id="forecastTable" class="forecast-table"></div>
|
||||
<div class="sun-info" id="sunInfo"></div>
|
||||
</section>
|
||||
|
||||
<!-- ── AI Analysis ── -->
|
||||
<section class="ai-section">
|
||||
<h3>🤖 AI 深度分析</h3>
|
||||
<div id="aiAnalysis" class="ai-box">
|
||||
<span class="ai-placeholder">点击城市后加载...</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ── Risk Profile ── -->
|
||||
<section class="risk-section">
|
||||
<h3>⚠️ 数据偏差风险</h3>
|
||||
<div id="riskInfo" class="risk-info"></div>
|
||||
</section>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ── Loading Overlay ── -->
|
||||
<div id="loading" class="loading-overlay hidden">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>正在获取气象数据,请稍候...</span>
|
||||
</div>
|
||||
|
||||
<!-- ── Technical Guide Modal ── -->
|
||||
<div id="guideModal" class="modal-overlay hidden">
|
||||
<div class="modal-content large">
|
||||
<div class="modal-header">
|
||||
<h2>📚 PolyWeather 系统技术说明</h2>
|
||||
<button class="modal-close" id="guideModalClose">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="guide-grid">
|
||||
<div class="guide-card">
|
||||
<h3>🧬 DEB 动态融合预测</h3>
|
||||
<p><b>Dynamic Ensemble Blending</b> 是系统的核心算法。它不只是简单的平均值,而是根据各个模型(ECMWF, GFS, MGM等)在**过去 30
|
||||
天**内的实际表现,动态分配权重。表现越稳的模型,在最终决策中占比越高。</p>
|
||||
</div>
|
||||
<div class="guide-card">
|
||||
<h3>🎲 结算概率引擎 (μ)</h3>
|
||||
<p>基于正态分布模型。我们提取所有预报模型的离散度作为**不确定性系数 ($\sigma$)**,结合 DEB 预测值作为**期望值 ($\mu$)**。这能告诉你某个温度区间(如 10°C
|
||||
盘口)发生的真实概率,辅助对冲决策。</p>
|
||||
</div>
|
||||
<div class="guide-card">
|
||||
<h3>📍 结算点 (Airport) 逻辑</h3>
|
||||
<p>Polymarket 结算是以**机场 METAR
|
||||
气象站**为准。在安卡拉等复杂城市,我们同时监控**市中心总站**(潜力热岛)和**机场站**(物理结算点)。当两者温差巨大时,往往预示着随后的补涨跳升机会。</p>
|
||||
</div>
|
||||
<div class="guide-card">
|
||||
<h3>⚠️ 风险偏置档案</h3>
|
||||
<p>系统记录了每个城市机场站与市区的距离、海拔差及季度性偏差。例如,首尔仁川机场因靠海,最高温通常比市区偏低;慕尼黑机场因海拔高,夜间降温极快。这些偏置已自动整合进 AI 分析逻辑中。
|
||||
</p>
|
||||
</div>
|
||||
<div class="guide-card">
|
||||
<h3>📈 未来日期概率分布</h3>
|
||||
<p>当你切换到未来 3-5 天时,系统会基于各模型对该日期的预报分歧度重新计算概率。分歧越大,分布越扁平(风险高);共识越强,分布越尖锐(机会明朗)。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="guide-footer">
|
||||
<p>※ 数据来源:NOAA METAR, Turkish MGM, Open-Meteo Ensemble, Meteoblue, NWS.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── History Chart Modal ── -->
|
||||
<div id="historyModal" class="modal-overlay hidden">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>📊 <span id="historyModalTitle">历史准确率</span></h2>
|
||||
<button class="modal-close" id="historyModalClose">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="history-stats" id="historyStats"></div>
|
||||
<div class="history-chart-wrapper">
|
||||
<canvas id="historyChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Leaflet JS -->
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<!-- Leaflet Heatmap Plugin -->
|
||||
<script src="https://leaflet.github.io/Leaflet.heat/dist/leaflet-heat.js"></script>
|
||||
<!-- Chart.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
|
||||
<script src="/static/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user