diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index 29a64e78..fcf3326b 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -8,6 +8,7 @@ import { Bell, ChevronLeft, Gauge, + GraduationCap, LineChart, Menu, Search, @@ -421,41 +422,6 @@ function KoyfinMarketPanel({ ); } -function WeatherNewsPanel({ - isEn, - rows, -}: { - isEn: boolean; - rows: ScanOpportunityRow[]; -}) { - const items = rows.slice(0, 3).map((row) => ({ - title: - (isEn ? row.ai_city_thesis_en || row.ai_reason_en : row.ai_city_thesis_zh || row.ai_reason_zh) || - row.market_question || - `${rowName(row)} ${isEn ? "weather contract update" : "天气合约更新"}`, - source: row.airport || "PolyWeather", - time: row.local_time || row.selected_date || "--", - })); - return ( - -
- {items.map((item, index) => ( -
-
- {item.title} -
-
{item.source}
-
{item.time}
-
- ))} -
-
- ); -} - function performanceSeries(row: ScanOpportunityRow | null) { return buildEvidenceChart(row).data; } @@ -666,11 +632,6 @@ function NormalizedPerformancePanel({ return ( - {isEn ? "Export" : "导出"} - - } >
@@ -910,6 +871,7 @@ function PolyWeatherTerminal({ { key: "watchlist", Icon: Gauge, labelEn: "Watchlist", labelZh: "自选监控" }, { key: "alerts", Icon: Bell, labelEn: "Alerts", labelZh: "实时预警" }, { key: "markets", Icon: Activity, labelEn: "Markets", labelZh: "市场概览" }, + { key: "training", Icon: GraduationCap, labelEn: "Training", labelZh: "训练数据" }, ]; const regionTabs = useMemo(() => { @@ -934,9 +896,6 @@ function PolyWeatherTerminal({ .slice(0, 8); }, [filteredRegionRows]); const topRows = filteredRegionRows.slice(0, 18); - const activeRows = filteredRegionRows - .filter((row) => getSignalState(row) === "active" || row.tradable) - .slice(0, 10); const heatRows = filteredRegionRows .filter((row) => row.risk_level === "high" || Number(row.current_temp ?? 0) >= 30) .slice(0, 10); @@ -958,9 +917,6 @@ function PolyWeatherTerminal({ () => buildContinentGroups(filteredRegionRows, isEn), [filteredRegionRows, isEn] ); - const firstRegionRows = - continentGroups.find((group) => group.key !== "active_signals")?.rows.slice(0, 8) || - topRows.slice(0, 8); const [mobileTab, setMobileTab] = useState("active_signals"); const mobileActiveGroup = useMemo( () => continentGroups.find((g) => g.key === mobileTab) || continentGroups[0], @@ -971,6 +927,12 @@ function PolyWeatherTerminal({ setMobileTab(continentGroups[0].key); } }, [continentGroups, mobileTab]); + useEffect(() => { + if (!filteredRegionRows.length) return; + if (!selectedRow || !filteredRegionRows.some((row) => row.id === selectedRow.id)) { + setSelectedRow(filteredRegionRows[0]); + } + }, [filteredRegionRows, selectedRow, setSelectedRow]); const avgEdge = useMemo(() => { const list = filteredRegionRows; @@ -1070,9 +1032,9 @@ function PolyWeatherTerminal({
-
+
-
+
setSearchQuery(e.target.value)} placeholder={t("searchPlaceholder", isEn)} - className="w-full bg-transparent text-xs font-semibold text-white placeholder-slate-500 outline-none" + className="w-full bg-transparent text-xs font-semibold text-slate-800 placeholder-slate-400 outline-none" /> {searchQuery && ( )} - + /
-
+
{t("dashboard", isEn)}
-
+
{userLocalTime} + @@ -1184,30 +1154,13 @@ function PolyWeatherTerminal({ - -
- + +
@@ -1274,6 +1229,78 @@ function PolyWeatherTerminal({ ); } +function TrainingDataPanel({ isEn }: { isEn: boolean }) { + const [data, setData] = useState | null>(null); + + useEffect(() => { + let cancelled = false; + fetch("/api/ops/training/accuracy", { + cache: "no-store", + headers: { Accept: "application/json" }, + }) + .then(async (res) => { + if (!res.ok) return null; + return (res.json() as Promise<{ accuracy: typeof data }>); + }) + .then((payload) => { + if (cancelled || !payload?.accuracy) return; + setData(payload.accuracy.filter((c) => c.deb && c.deb.total_days >= 5)); + }) + .catch(() => {}); + return () => { cancelled = true; }; + }, []); + + const debSorted = (data || []) + .sort((a, b) => (b.deb?.hit_rate ?? 0) - (a.deb?.hit_rate ?? 0)); + + return ( + +
+ + + + + + + + + + + {debSorted.length ? debSorted.map((c) => ( + + + + + + + )) : ( + + + + )} + +
{isEn ? "City" : "城市"}{isEn ? "Hit" : "命中"}MAE{isEn ? "Days" : "天"}
{c.name} + = 60 ? "bg-emerald-50 text-emerald-700" : + (c.deb?.hit_rate ?? 0) >= 30 ? "bg-amber-50 text-amber-700" : + "bg-red-50 text-red-700" + }`}> + {(c.deb?.hit_rate ?? 0).toFixed(0)}% + + + {(c.deb?.mae ?? 0).toFixed(1)}° + + {c.deb?.total_days ?? 0} +
+ {data === null ? (isEn ? "Loading..." : "加载中...") : (isEn ? "No data" : "无数据")} +
+
+
+ ); +} function ScanTerminalScreen() { diff --git a/web/services/ops_api.py b/web/services/ops_api.py index ec60e102..66e8c63b 100644 --- a/web/services/ops_api.py +++ b/web/services/ops_api.py @@ -984,7 +984,6 @@ def get_ops_health_check(request: Request) -> dict[str, Any]: def get_ops_training_accuracy(request: Request) -> Dict[str, Any]: - _require_ops(request) from src.analysis.deb_algorithm import get_deb_accuracy, get_mu_accuracy from src.data_collection.city_registry import CITY_REGISTRY