清理最后一批 Polymarket 注释+死代码 WeatherDecisionBand

This commit is contained in:
2569718930@qq.com
2026-05-25 20:53:03 +08:00
parent 11a901d22e
commit 7b8826dfa1
13 changed files with 328 additions and 112 deletions
@@ -46,6 +46,7 @@ import { TrainingDashboard } from "@/components/dashboard/scan-terminal/Training
import { LiveTemperatureThresholdChart } from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart"; import { LiveTemperatureThresholdChart } from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart";
import { KoyfinRowsTable } from "@/components/dashboard/scan-terminal/KoyfinRowsTable"; import { KoyfinRowsTable } from "@/components/dashboard/scan-terminal/KoyfinRowsTable";
import { rowName, pct, money, temp, edgeClass } from "@/components/dashboard/scan-terminal/utils"; import { rowName, pct, money, temp, edgeClass } from "@/components/dashboard/scan-terminal/utils";
import { CitySelectorDropdown } from "@/components/dashboard/scan-terminal/CitySelectorDropdown";
function createEmptyAccess(loading = true): ProAccessState { function createEmptyAccess(loading = true): ProAccessState {
return { return {
@@ -248,20 +249,21 @@ function EmptySlotCard({
slotIndex, slotIndex,
isActive, isActive,
isEn, isEn,
availableCities,
onSelectSlot, onSelectSlot,
onSelectCity, onOpenSearch,
}: { }: {
slotIndex: number; slotIndex: number;
isActive: boolean; isActive: boolean;
isEn: boolean; isEn: boolean;
availableCities: { city: string; name: string }[];
onSelectSlot: () => void; onSelectSlot: () => void;
onSelectCity: (city: string) => void; onOpenSearch: () => void;
}) { }) {
return ( return (
<div <div
onClick={onSelectSlot} onClick={() => {
onSelectSlot();
onOpenSearch();
}}
className={clsx( className={clsx(
"flex flex-col items-center justify-center h-full rounded-[4px] border-2 border-dashed p-6 cursor-pointer bg-slate-50/50 transition-all", "flex flex-col items-center justify-center h-full rounded-[4px] border-2 border-dashed p-6 cursor-pointer bg-slate-50/50 transition-all",
isActive isActive
@@ -277,24 +279,15 @@ function EmptySlotCard({
</div> </div>
<div className="text-[10px] text-slate-400 text-center mb-3 max-w-[180px]"> <div className="text-[10px] text-slate-400 text-center mb-3 max-w-[180px]">
{isEn {isEn
? "Select this card and click a city on the left, or select below:" ? "Click to choose a city weather chart for this slot."
: "激活此卡片并从左侧选择城市,或从下方直接选择:"} : "点击为该槽位选择一个城市天气图表。"}
</div> </div>
<select <button
value="" type="button"
onClick={(e) => e.stopPropagation()} // prevent triggering onSelectSlot className="text-[11px] font-semibold text-white px-3 py-1.5 rounded bg-blue-600 hover:bg-blue-700 transition-colors shadow-sm outline-none"
onChange={(e) => {
if (e.target.value) onSelectCity(e.target.value);
}}
className="text-[11px] font-semibold text-slate-600 px-2 py-1 rounded border border-slate-300 bg-white outline-none max-w-[160px]"
> >
<option value="">{isEn ? "Choose city..." : "选择城市..."}</option> {isEn ? "Choose City..." : "选择城市..."}
{availableCities.map((c) => ( </button>
<option key={c.city} value={c.city}>
{c.name}
</option>
))}
</select>
</div> </div>
); );
} }
@@ -376,6 +369,7 @@ function PolyWeatherTerminal({
}); });
const [activeSlotIndex, setActiveSlotIndex] = useState<number>(0); const [activeSlotIndex, setActiveSlotIndex] = useState<number>(0);
const [maximizedSlotIndex, setMaximizedSlotIndex] = useState<number | null>(null); const [maximizedSlotIndex, setMaximizedSlotIndex] = useState<number | null>(null);
const [activeSearchSlotIndex, setActiveSearchSlotIndex] = useState<number | null>(null);
const NAV_ITEMS = [ const NAV_ITEMS = [
{ key: "contracts", Icon: Table2, labelEn: "Contracts", labelZh: "天气合约" }, { key: "contracts", Icon: Table2, labelEn: "Contracts", labelZh: "天气合约" },
@@ -388,6 +382,7 @@ function PolyWeatherTerminal({
}, [selectedRegionKey, setSelectedCity]); }, [selectedRegionKey, setSelectedCity]);
const filteredRegionRows = useMemo(() => { const filteredRegionRows = useMemo(() => {
if (selectedRegionKey === "all") return rows;
return rows.filter( return rows.filter(
(row) => getCityRegion(row) === selectedRegionKey, (row) => getCityRegion(row) === selectedRegionKey,
); );
@@ -395,7 +390,12 @@ function PolyWeatherTerminal({
useEffect(() => { useEffect(() => {
if (filteredRegionRows.length && slots.every((s) => s === null)) { if (filteredRegionRows.length && slots.every((s) => s === null)) {
const next = [filteredRegionRows[0].city, null, null, null]; const next = [
filteredRegionRows[0]?.city || null,
filteredRegionRows[1]?.city || null,
filteredRegionRows[2]?.city || null,
filteredRegionRows[3]?.city || null,
];
setSlots(next); setSlots(next);
try { try {
localStorage.setItem("polyweather_terminal_slots", JSON.stringify(next)); localStorage.setItem("polyweather_terminal_slots", JSON.stringify(next));
@@ -677,29 +677,19 @@ function PolyWeatherTerminal({
</div> </div>
{/* Desktop layout */} {/* Desktop layout */}
<div className="hidden h-full min-h-0 lg:grid lg:grid-cols-[0.96fr_1.72fr] gap-2"> <div className="hidden h-full min-h-0 lg:block">
<div className="flex min-h-0 flex-col gap-2"> <div className="h-full w-full min-h-0">
<CityRegionList
isEn={isEn}
rows={filteredRegionRows}
selectedCity={slots[activeSlotIndex]}
onSelectCity={(city) => handleSelectCityForSlot(activeSlotIndex, city)}
slots={slots}
activeSlotIndex={activeSlotIndex}
/>
</div>
<div className="min-h-0">
{maximizedSlotIndex !== null ? ( {maximizedSlotIndex !== null ? (
// Maximized view // Maximized view
<div <div
onClick={() => setActiveSlotIndex(maximizedSlotIndex)} onClick={() => setActiveSlotIndex(maximizedSlotIndex)}
className={clsx( className={clsx(
"relative h-full rounded-[4px] border overflow-hidden border-blue-500 ring-2 ring-blue-500/20 shadow-md z-10" "relative h-full rounded-[4px] border border-blue-500 ring-2 ring-blue-500/20 shadow-md z-10",
activeSearchSlotIndex === maximizedSlotIndex ? "" : "overflow-hidden"
)} )}
> >
{/* Floating actions toolbar */} {/* Floating actions toolbar */}
<div className="absolute right-[110px] top-[6px] z-20 flex items-center gap-1.5"> <div className="absolute right-2 top-[38px] z-20 flex items-center gap-1.5">
<button <button
type="button" type="button"
onClick={(e) => { onClick={(e) => {
@@ -736,7 +726,21 @@ function PolyWeatherTerminal({
row={filteredRegionRows.find((r) => String(r.city || "").toLowerCase() === slots[maximizedSlotIndex]) || null} row={filteredRegionRows.find((r) => String(r.city || "").toLowerCase() === slots[maximizedSlotIndex]) || null}
allRows={filteredRegionRows} allRows={filteredRegionRows}
compact={false} compact={false}
onSearchClick={() => setActiveSearchSlotIndex(maximizedSlotIndex)}
/> />
{activeSearchSlotIndex === maximizedSlotIndex && (
<CitySelectorDropdown
isEn={isEn}
rows={filteredRegionRows}
onSelectCity={(city) => {
handleSelectCityForSlot(maximizedSlotIndex, city);
setActiveSearchSlotIndex(null);
}}
onClose={() => setActiveSearchSlotIndex(null)}
className="absolute left-3 top-9 z-50 w-[280px] bg-white border border-slate-200 rounded shadow-lg p-2"
/>
)}
</div> </div>
) : ( ) : (
// 2x2 grid layout // 2x2 grid layout
@@ -747,15 +751,28 @@ function PolyWeatherTerminal({
if (!cityInSlot) { if (!cityInSlot) {
return ( return (
<EmptySlotCard <div key={slotIndex} className="relative h-full">
key={slotIndex} <EmptySlotCard
slotIndex={slotIndex} slotIndex={slotIndex}
isActive={isSlotActive} isActive={isSlotActive}
isEn={isEn} isEn={isEn}
availableCities={availableCities} onSelectSlot={() => setActiveSlotIndex(slotIndex)}
onSelectSlot={() => setActiveSlotIndex(slotIndex)} onOpenSearch={() => setActiveSearchSlotIndex(slotIndex)}
onSelectCity={(city) => handleSelectCityForSlot(slotIndex, city)} />
/>
{activeSearchSlotIndex === slotIndex && (
<CitySelectorDropdown
isEn={isEn}
rows={filteredRegionRows}
onSelectCity={(city) => {
handleSelectCityForSlot(slotIndex, city);
setActiveSearchSlotIndex(null);
}}
onClose={() => setActiveSearchSlotIndex(null)}
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 w-[280px] bg-white border border-slate-200 rounded shadow-lg p-2"
/>
)}
</div>
); );
} }
@@ -768,14 +785,15 @@ function PolyWeatherTerminal({
key={slotIndex} key={slotIndex}
onClick={() => setActiveSlotIndex(slotIndex)} onClick={() => setActiveSlotIndex(slotIndex)}
className={clsx( className={clsx(
"relative h-full rounded-[4px] border overflow-hidden transition-all", "relative h-full rounded-[4px] border transition-all",
isSlotActive isSlotActive
? "border-blue-500 ring-2 ring-blue-500/20 shadow-md z-10" ? "border-blue-500 ring-2 ring-blue-500/20 shadow-md z-10"
: "border-[#d2d9e2] hover:border-slate-400" : "border-[#d2d9e2] hover:border-slate-400",
activeSearchSlotIndex === slotIndex ? "" : "overflow-hidden"
)} )}
> >
{/* Floating actions toolbar */} {/* Floating actions toolbar */}
<div className="absolute right-[110px] top-[6px] z-20 flex items-center gap-1.5"> <div className="absolute right-2 top-[38px] z-20 flex items-center gap-1.5">
<button <button
type="button" type="button"
onClick={(e) => { onClick={(e) => {
@@ -812,7 +830,21 @@ function PolyWeatherTerminal({
row={rowForSlot} row={rowForSlot}
allRows={filteredRegionRows} allRows={filteredRegionRows}
compact={true} compact={true}
onSearchClick={() => setActiveSearchSlotIndex(slotIndex)}
/> />
{activeSearchSlotIndex === slotIndex && (
<CitySelectorDropdown
isEn={isEn}
rows={filteredRegionRows}
onSelectCity={(city) => {
handleSelectCityForSlot(slotIndex, city);
setActiveSearchSlotIndex(null);
}}
onClose={() => setActiveSearchSlotIndex(null)}
className="absolute left-3 top-9 z-50 w-[280px] bg-white border border-slate-200 rounded shadow-lg p-2"
/>
)}
</div> </div>
); );
})} })}
@@ -845,9 +877,9 @@ function ScanTerminalScreen() {
hydrated && (proAccess.subscriptionActive || canUseLocalFullAccess); hydrated && (proAccess.subscriptionActive || canUseLocalFullAccess);
const userLocalTime = useUserLocalClock(); const userLocalTime = useUserLocalClock();
const { themeMode } = useScanTerminalTheme(); const { themeMode } = useScanTerminalTheme();
const [selectedRegionKey, setSelectedRegionKey] = useState<string>("east_asia"); const [selectedRegionKey, setSelectedRegionKey] = useState<string>("all");
const [localTimezoneOffsetSeconds, setLocalTimezoneOffsetSeconds] = useState<number | null>(null); const [localTimezoneOffsetSeconds, setLocalTimezoneOffsetSeconds] = useState<number | null>(null);
const [useLocalTimezoneDefault, setUseLocalTimezoneDefault] = useState(true); const [useLocalTimezoneDefault, setUseLocalTimezoneDefault] = useState(false);
const [visibleRegions, setVisibleRegions] = useState<Set<string>>(() => { const [visibleRegions, setVisibleRegions] = useState<Set<string>>(() => {
try { try {
const stored = localStorage.getItem("polyweather_visible_regions"); const stored = localStorage.getItem("polyweather_visible_regions");
@@ -938,7 +970,7 @@ function ScanTerminalScreen() {
}, []); }, []);
useEffect(() => { useEffect(() => {
setSelectedRegionKey(getDefaultRegion()); setSelectedRegionKey("all");
setLocalTimezoneOffsetSeconds(-new Date().getTimezoneOffset() * 60); setLocalTimezoneOffsetSeconds(-new Date().getTimezoneOffset() * 60);
}, []); }, []);
@@ -4,7 +4,7 @@ import type { CityDetail } from "@/lib/dashboard-types";
import { getDisplayAirportPrimary } from "@/lib/airport-observation-display"; import { getDisplayAirportPrimary } from "@/lib/airport-observation-display";
import { formatTemperatureValue } from "@/lib/temperature-utils"; import { formatTemperatureValue } from "@/lib/temperature-utils";
// Settlement runway mapping — matches Polymarket settlement anchors // Settlement runway mapping — matches settlement anchors
const SETTLEMENT_RUNWAY_PAIRS: Record<string, Array<[string, string]>> = { const SETTLEMENT_RUNWAY_PAIRS: Record<string, Array<[string, string]>> = {
shanghai: [["17L", "35R"]], shanghai: [["17L", "35R"]],
beijing: [["01", "19"]], beijing: [["01", "19"]],
@@ -0,0 +1,201 @@
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import clsx from "clsx";
import type { ScanOpportunityRow } from "@/lib/dashboard-types";
import { REGIONS, getCityRegion } from "./continent-grouping";
import { rowName, temp } from "./utils";
interface CitySelectorDropdownProps {
isEn: boolean;
rows: ScanOpportunityRow[];
onSelectCity: (city: string) => void;
onClose: () => void;
className?: string;
}
export function CitySelectorDropdown({
isEn,
rows,
onSelectCity,
onClose,
className,
}: CitySelectorDropdownProps) {
const containerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [searchQuery, setSearchQuery] = useState("");
const [activeTab, setActiveTab] = useState<string>("all");
// Auto-focus input on mount
useEffect(() => {
inputRef.current?.focus();
}, []);
// Handle click outside and Escape key
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
onClose();
}
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
onClose();
}
};
document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("keydown", handleKeyDown);
};
}, [onClose]);
// Tab definitions
const tabs = useMemo(() => {
return [
{ key: "all", labelEn: "ALL", labelZh: "全部" },
...REGIONS.map((r) => ({
key: r.key,
labelEn: r.labelEn.replace("Asia", "").replace("America", "").trim() || r.labelEn,
labelZh: r.labelZh,
})),
];
}, []);
// Filter rows
const filteredRows = useMemo(() => {
const q = searchQuery.toLowerCase().trim();
return rows.filter((row) => {
// 1. Region filter
if (activeTab !== "all") {
const region = getCityRegion(row);
if (region !== activeTab) return false;
}
// 2. Query filter
if (!q) return true;
const haystack = [
row.city,
row.city_display_name,
row.display_name,
row.airport,
]
.filter(Boolean)
.map((s) => s!.toLowerCase());
return haystack.some((s) => s.includes(q));
});
}, [rows, searchQuery, activeTab]);
return (
<div
ref={containerRef}
className={clsx(
"flex flex-col bg-white border border-slate-200 rounded-md shadow-2xl overflow-hidden text-xs text-[#202833] animate-in fade-in-50 zoom-in-95 duration-100",
className
)}
onClick={(e) => e.stopPropagation()} // Prevent card activation
>
{/* Search Input Area */}
<div className="flex items-center gap-2 p-2 border-b border-slate-100 bg-[#f8fafc]">
<input
ref={inputRef}
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={isEn ? "Search city or airport..." : "搜索城市、机场..."}
className="w-full px-2.5 py-1.5 border border-slate-300 rounded bg-white text-xs outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500/20"
/>
<button
type="button"
onClick={onClose}
className="text-slate-400 hover:text-slate-600 px-1 font-mono"
>
</button>
</div>
{/* Region filter tabs */}
<div className="flex items-center gap-1.5 px-2.5 py-1.5 border-b border-slate-100 bg-slate-50/50 overflow-x-auto scrollbar-none">
{tabs.map((tab) => {
const isActive = activeTab === tab.key;
return (
<button
key={tab.key}
type="button"
onClick={() => setActiveTab(tab.key)}
className={clsx(
"px-2 py-0.5 text-[10px] font-bold rounded whitespace-nowrap transition-all",
isActive
? "bg-blue-600 text-white shadow-sm"
: "bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-700"
)}
>
{isEn ? tab.labelEn : tab.labelZh}
</button>
);
})}
</div>
{/* Scrollable list */}
<div className="flex-1 overflow-y-auto max-h-[220px] divide-y divide-slate-50">
{filteredRows.length === 0 ? (
<div className="p-4 text-center text-slate-400 font-medium">
{isEn ? "No matching cities" : "无匹配城市"}
</div>
) : (
filteredRows.map((row) => {
const cityName = rowName(row);
const obsTemp = row.current_temp ?? row.current_max_so_far;
const debPrediction = row.deb_prediction;
const symbol = row.temp_symbol || "°C";
return (
<button
key={row.id}
type="button"
onClick={() => onSelectCity(String(row.city || "").toLowerCase())}
className="flex w-full items-center justify-between px-3 py-2 text-left hover:bg-blue-50/50 transition-colors group"
>
<div className="min-w-0 flex-1 pr-3">
<div className="font-bold text-slate-800 group-hover:text-blue-600 truncate">
{cityName}
</div>
{row.airport && (
<div className="text-[10px] text-slate-400 font-mono truncate">
{row.airport}
</div>
)}
</div>
{/* Weather info */}
<div className="flex items-center gap-3 font-mono text-[11px] shrink-0 text-slate-500">
{obsTemp !== undefined && obsTemp !== null && (
<div className="flex flex-col items-end">
<span className="text-[8px] text-slate-400 font-sans scale-90 uppercase">Obs</span>
<strong className="text-slate-700 font-bold">
{obsTemp}
<span className="text-[9px] font-normal font-sans">{symbol}</span>
</strong>
</div>
)}
{debPrediction !== undefined && debPrediction !== null && (
<div className="flex flex-col items-end">
<span className="text-[8px] text-slate-400 font-sans scale-90 uppercase">DEB</span>
<strong className="text-orange-600 font-bold">
{debPrediction}
<span className="text-[9px] font-normal font-sans">{symbol}</span>
</strong>
</div>
)}
</div>
</button>
);
})
)}
</div>
</div>
);
}
@@ -832,11 +832,13 @@ export function LiveTemperatureThresholdChart({
row, row,
allRows = [], allRows = [],
compact = false, compact = false,
onSearchClick,
}: { }: {
isEn: boolean; isEn: boolean;
row: ScanOpportunityRow | null; row: ScanOpportunityRow | null;
allRows?: ScanOpportunityRow[]; allRows?: ScanOpportunityRow[];
compact?: boolean; compact?: boolean;
onSearchClick?: () => void;
}) { }) {
const [hourly, setHourly] = useState<HourlyForecast>(null); const [hourly, setHourly] = useState<HourlyForecast>(null);
const city = String(row?.city || "").toLowerCase().trim(); const city = String(row?.city || "").toLowerCase().trim();
@@ -1009,19 +1011,37 @@ export function LiveTemperatureThresholdChart({
[series, data], [series, data],
); );
const panelTitle = row const subtitle = row
? `${rowName(row)} · ${ ? isEn
isEn ? timeframe === "1D"
? timeframe === "1D" ? "Live & Forecast"
? "Live & Forecast" : `${timeframe} Forecast`
: `${timeframe} Forecast` : timeframe === "1D"
: timeframe === "1D" ? "实测与预测"
? "实测与预测" : `${timeframe}预报`
: `${timeframe}预报` : "";
}`
: isEn const panelTitle = row ? (
? "Temperature Chart" <div className="flex items-center gap-1">
: "气温图表"; <button
type="button"
onClick={onSearchClick}
className={clsx(
"flex items-center gap-1.5 px-1.5 py-0.5 rounded text-left transition-colors font-bold text-slate-800 outline-none select-none",
onSearchClick ? "hover:bg-slate-200/80 cursor-pointer" : ""
)}
>
<span>{rowName(row)}</span>
{onSearchClick && <span className="text-[8px] text-slate-400"></span>}
</button>
<span className="text-slate-400 font-normal">·</span>
<span className="text-slate-500 font-normal">{subtitle}</span>
</div>
) : isEn ? (
"Temperature Chart"
) : (
"气温图表"
);
const timeframeActions = ( const timeframeActions = (
<div className="flex items-center gap-1 rounded bg-[#eef2f6] p-0.5 border border-slate-200"> <div className="flex items-center gap-1 rounded bg-[#eef2f6] p-0.5 border border-slate-200">
@@ -10,7 +10,7 @@ export function Panel({
}: { }: {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
title: string; title: React.ReactNode;
actions?: React.ReactNode; actions?: React.ReactNode;
}) { }) {
return ( return (
@@ -1,35 +0,0 @@
"use client";
import clsx from "clsx";
import type { WeatherDecisionView } from "@/components/dashboard/scan-terminal/city-card-decision-utils";
export function WeatherDecisionBand({
decisionView,
decisionWhyText,
isEn,
paceDeltaText,
}: {
decisionView: WeatherDecisionView;
decisionWhyText: string;
isEn: boolean;
paceDeltaText: string;
}) {
return (
<section className={clsx("scan-ai-decision-band", decisionView.tone)}>
<div className="scan-ai-decision-main">
<span>{decisionView.kicker}</span>
<strong>{decisionView.action}</strong>
<p className="scan-ai-decision-why">{decisionWhyText}</p>
</div>
<div className="scan-ai-decision-metrics">
<span>
{isEn ? "Weather range" : "天气区间"}
<b>{decisionView.targetRange}</b>
</span>
<span>
{isEn ? "Path delta" : "路径偏差"} <b>{paceDeltaText}</b>
</span>
</div>
</section>
);
}
@@ -27,8 +27,6 @@ const LABELS: Record<string, string> = {
hko: "HKO (香港)", hko: "HKO (香港)",
singapore_mss: "Singapore MSS", singapore_mss: "Singapore MSS",
cwa: "CWA (台湾)", cwa: "CWA (台湾)",
polymarket_gamma: "Polymarket Gamma",
polymarket_clob: "Polymarket CLOB",
amos: "AMOS (韩国跑道)", amos: "AMOS (韩国跑道)",
amsc_awos: "AMSC AWOS (中国)", amsc_awos: "AMSC AWOS (中国)",
noaa_wrh: "NOAA WRH (美国结算)", noaa_wrh: "NOAA WRH (美国结算)",
+2 -2
View File
@@ -492,7 +492,7 @@ export const DOCS_PAGES: DocsPage[] = [
{ {
type: "bullets", type: "bullets",
items: [ items: [
"自动识别当前 Polymarket 页面中的城市,也支持手动切换。", "自动识别当前页面中的城市,也支持手动切换。",
"展示城市档案:结算站点、站点距离、观测更新时间、周边站点数量。", "展示城市档案:结算站点、站点距离、观测更新时间、周边站点数量。",
"展示今日日内走势(简版):DEB 走势与机场主站实况 / 官方增强站网对照,可悬停查看时间与温度。", "展示今日日内走势(简版):DEB 走势与机场主站实况 / 官方增强站网对照,可悬停查看时间与温度。",
"展示多日最高温预报(简版),并提供一键刷新与跳转主站入口。", "展示多日最高温预报(简版),并提供一键刷新与跳转主站入口。",
@@ -566,7 +566,7 @@ export const DOCS_PAGES: DocsPage[] = [
{ {
type: "bullets", type: "bullets",
items: [ items: [
"Auto-detects the current Polymarket page city, with manual switching also available.", "Auto-detects the current page city, with manual switching also available.",
"Shows a city profile with settlement station, station distance, observation timestamp, and nearby station count.", "Shows a city profile with settlement station, station distance, observation timestamp, and nearby station count.",
"Shows a compact intraday chart with DEB versus airport-primary observations and official nearby-network observations, including hoverable time and temperature.", "Shows a compact intraday chart with DEB versus airport-primary observations and official nearby-network observations, including hoverable time and temperature.",
"Shows a compact multi-day daily-high forecast, plus refresh and jump-to-site actions.", "Shows a compact multi-day daily-high forecast, plus refresh and jump-to-site actions.",
+1 -1
View File
@@ -151,7 +151,7 @@ CITY_REGISTRY = {
"risk_emoji": "🟡", "risk_emoji": "🟡",
"airport_name": "流浮山天文台站", "airport_name": "流浮山天文台站",
"distance_km": 0.0, "distance_km": 0.0,
"warning": "香港天文台流浮山站分钟级观测,Polymarket 深圳温度市场结算源;非机场 METAR,不可与 ZGSZ/VHHH 报文混用。", "warning": "香港天文台流浮山站分钟级观测,深圳温度市场结算源;非机场 METAR,不可与 ZGSZ/VHHH 报文混用。",
}, },
"taipei": { "taipei": {
"name": "Taipei", "name": "Taipei",
+1 -1
View File
@@ -1,4 +1,4 @@
# Polymarket 城市温度市场 - 数据偏差风险档案 # 城市温度市场 - 数据偏差风险档案
# 基于 METAR 机场站与市区实际温度的系统性差异 # 基于 METAR 机场站与市区实际温度的系统性差异
from src.data_collection.city_registry import CITY_REGISTRY from src.data_collection.city_registry import CITY_REGISTRY
+2 -2
View File
@@ -37,7 +37,7 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
- NWS (US National Weather Service) - NWS (US National Weather Service)
- MGM (Turkish Meteorological Service) - MGM (Turkish Meteorological Service)
- JMA / HKO / CWA (country official networks) - JMA / HKO / CWA (country official networks)
- Polymarket (weather derivative markets) - Weather derivative markets
""" """
from src.data_collection.city_registry import CITY_REGISTRY from src.data_collection.city_registry import CITY_REGISTRY
@@ -612,7 +612,7 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
def extract_city_from_question(self, question: str) -> Optional[str]: def extract_city_from_question(self, question: str) -> Optional[str]:
""" """
Polymarket 问题描述或 Slug 中提取城市名称 市场问题描述或 Slug 中提取城市名称
""" """
q = question.lower() q = question.lower()
+1 -1
View File
@@ -595,7 +595,7 @@ HIGH_FREQ_AIRPORT_ICAO = {
"san francisco": "KSFO", "houston": "KHOU", "dallas": "KDAL", "san francisco": "KSFO", "houston": "KHOU", "dallas": "KDAL",
"austin": "KAUS", "seattle": "KSEA", "austin": "KAUS", "seattle": "KSEA",
} }
# Settlement runway mapping — matches Polymarket settlement anchor stations. # Settlement runway mapping — matches settlement anchor stations.
# Format: (low_number, high_number) order-independent; stored sorted for lookup. # Format: (low_number, high_number) order-independent; stored sorted for lookup.
SETTLEMENT_RUNWAY_PAIRS: Dict[str, Set[Tuple[str, str]]] = { SETTLEMENT_RUNWAY_PAIRS: Dict[str, Set[Tuple[str, str]]] = {
"shanghai": {("17L", "35R")}, "shanghai": {("17L", "35R")},
+1 -1
View File
@@ -148,7 +148,7 @@ def _scan_city_terminal_rows_quick(
*, *,
force_refresh: bool = False, force_refresh: bool = False,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Fast path that skips Polymarket matching — returns a single row per city """Fast path that returns cached analysis rows only — returns a single row per city
with cached analysis data (Obs, DEB, probabilities) but no market prices.""" with cached analysis data (Obs, DEB, probabilities) but no market prices."""
data = _analyze( data = _analyze(
city, city,