From 4410cebce7c779199d40d9ed0e42c068df237a6e Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sat, 25 Apr 2026 06:52:17 +0800 Subject: [PATCH] feat: implement ScanTerminalDashboard with KPI bar and opportunity table components --- .../components/dashboard/OpportunityTable.tsx | 4 ++-- frontend/components/dashboard/ScanKPIBar.tsx | 14 ++++++-------- .../dashboard/ScanTerminalDashboard.tsx | 16 ++++++++++------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/frontend/components/dashboard/OpportunityTable.tsx b/frontend/components/dashboard/OpportunityTable.tsx index 0efc3acc..e7ab1102 100644 --- a/frontend/components/dashboard/OpportunityTable.tsx +++ b/frontend/components/dashboard/OpportunityTable.tsx @@ -319,13 +319,13 @@ function getOpportunityStrength(edgePercent?: number | null, locale = "zh-CN") { const normalized = Number.isFinite(edge) ? edge : 0; if (normalized >= 20) { return { - label: locale === "en-US" ? "Strong" : "强机会", + label: locale === "en-US" ? "High confidence" : "高胜率", tone: "strong", }; } if (normalized >= 10) { return { - label: locale === "en-US" ? "Medium" : "中机会", + label: locale === "en-US" ? "Medium confidence" : "中等胜率", tone: "medium", }; } diff --git a/frontend/components/dashboard/ScanKPIBar.tsx b/frontend/components/dashboard/ScanKPIBar.tsx index b95f3cfe..1d61de42 100644 --- a/frontend/components/dashboard/ScanKPIBar.tsx +++ b/frontend/components/dashboard/ScanKPIBar.tsx @@ -5,10 +5,9 @@ import { useI18n } from "@/hooks/useI18n"; import type { ScanOpportunityRow, ScanTerminalResponse } from "@/lib/dashboard-types"; import { getMarketFocus } from "@/lib/scan-market-focus"; -function formatPercent(value?: number | null, signed = false): string { +function formatTemperature(value?: number | null, unit?: string | null): string { if (value == null || Number.isNaN(Number(value))) return "--"; - const numeric = Number(value); - return `${signed && numeric >= 0 ? "+" : ""}${numeric.toFixed(1)}%`; + return `${Number(value).toFixed(1)}${unit || "°C"}`; } function countByRisk(rows: ScanOpportunityRow[]) { @@ -37,7 +36,6 @@ export function ScanKPIBar({ }) { const { locale } = useI18n(); const isEn = locale === "en-US"; - const summary = response?.summary; const riskCounts = countByRisk(rows); const tradableRows = rows.filter((row) => row.tradable && !row.closed); const liveRows = rows.filter((row) => row.active || row.accepting_orders); @@ -79,14 +77,14 @@ export function ScanKPIBar({ { label: isEn ? "Book Status" : "盘口状态", value: `${liveRows.length}`, - note: `${isEn ? "Tradable" : "可交易"} ${tradableRows.length} · ${isEn ? "No edge" : "无机会"} ${Math.max(0, rows.length - tradableRows.length)}`, + note: `${isEn ? "Evaluable" : "可评估"} ${tradableRows.length} · ${isEn ? "Pending" : "待评估"} ${Math.max(0, rows.length - tradableRows.length)}`, tone: "blue", }, { - label: isEn ? "Opportunity Quality" : "机会质量", - value: formatPercent(summary?.avg_edge_percent, true), + label: isEn ? "Win-rate Basis" : "胜率依据", + value: bestRow ? formatTemperature(bestRow.deb_prediction, bestRow.temp_symbol || bestRow.target_unit) : "--", note: bestRow - ? `${isEn ? "Best" : "最佳"} ${bestRow.city_display_name || bestRow.display_name || bestRow.city} · ${formatPercent(bestRow.edge_percent, true)}` + ? `${isEn ? "Focus" : "焦点"} ${bestRow.city_display_name || bestRow.display_name || bestRow.city} · DEB / ${isEn ? "models" : "模型"} / METAR` : isEn ? "No active market now" : "当前没有活跃市场", diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index cd8eb4df..a373f637 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -405,7 +405,7 @@ function ScanAiAnalysisView({
- {isEn ? "V4 city analysis" : "V4 城市级分析"} + {isEn ? "AI city forecast" : "AI 城市级预测"}

{aiScan?.status === "ready" ? (isEn ? aiScan.summary_en || aiScan.summary_zh : aiScan.summary_zh || aiScan.summary_en) || @@ -457,7 +457,7 @@ function ScanAiAnalysisView({ (isEn ? group.thesis?.thesis_en || group.thesis?.summary_en || group.rows[0]?.ai_city_thesis_en : group.thesis?.thesis_zh || group.thesis?.summary_zh || group.rows[0]?.ai_city_thesis_zh) || - (isEn ? "Waiting for V4 city thesis." : "等待 V4 城市研判。"); + (isEn ? "Waiting for AI city forecast." : "等待 AI 城市预测。"); const clusterNote = group.thesis?.model_cluster_note || group.rows[0]?.ai_city_model_cluster_note || null; return ( @@ -483,7 +483,7 @@ function ScanAiAnalysisView({

- {normalizeTemperatureLabel(row.action || row.target_label, tempSymbol) || + {normalizeTemperatureLabel(row.target_label, tempSymbol) || row.market_question || row.id} @@ -491,7 +491,11 @@ function ScanAiAnalysisView({ {row.deb_prediction != null ? `DEB ${formatTemperatureValue(row.deb_prediction, tempSymbol)} · ` : ""} - {row.edge_percent != null ? `edge ${Number(row.edge_percent).toFixed(1)}%` : "--"} + {row.ai_forecast_confidence + ? `${isEn ? "confidence" : "信心"} ${row.ai_forecast_confidence}` + : isEn + ? "forecast mapping" + : "预测映射"}
{getAiDecisionLabel(row, locale)} @@ -1202,10 +1206,10 @@ function ScanTerminalScreen() { : "当前显示上次成功快照" : isEn ? isPro - ? "Read-only market scan with peak-first main signal" + ? "Read-only market scan with DEB-first win-rate signal" : "Free preview: distribution view and city briefing" : isPro - ? "只读市场扫描,主信号按 EMOS 主峰优先" + ? "只读市场扫描,主信号按 DEB 最终融合预测优先" : "免费预览:分布视图和城市简报可查看"}