diff --git a/frontend/components/dashboard/scan-terminal/ModelSummaryDashboard.tsx b/frontend/components/dashboard/scan-terminal/ModelSummaryDashboard.tsx index d7563e20..80360ffa 100644 --- a/frontend/components/dashboard/scan-terminal/ModelSummaryDashboard.tsx +++ b/frontend/components/dashboard/scan-terminal/ModelSummaryDashboard.tsx @@ -6,14 +6,12 @@ import { useEffect, useMemo, useRef, useState } from "react"; import type { ScanOpportunityRow } from "@/lib/dashboard-types"; import { MODEL_SUMMARY_MODEL_COLUMNS, - buildModelSummaryProbabilityColumns, buildModelSummaryRows, filterModelSummaryRows, formatModelSummaryProbability, formatModelSummaryLocalTime, formatModelSummaryTemp, hasModelSummaryForecastData, - type ModelSummaryProbabilityColumn, type ModelSummaryRow, } from "@/lib/model-summary"; @@ -34,6 +32,7 @@ const SUMMARY_TEXT = { region: { en: "Region", zh: "区域" }, localTime: { en: "Local Time", zh: "当地时间" }, gaussianMu: { en: "Gaussian μ", zh: "高斯 μ" }, + probabilityDistribution: { en: "Probability Distribution", zh: "概率分布" }, median: { en: "Median", zh: "模型中位数" }, spread: { en: "Spread", zh: "分歧范围" }, empty: { en: "No model summary rows match the current filters.", zh: "当前筛选下没有模型汇总数据。" }, @@ -101,11 +100,9 @@ function FilterToggle({ function ModelSummaryRowView({ row, nowMs, - probabilityColumns, }: { row: ModelSummaryRow; nowMs: number | null; - probabilityColumns: ModelSummaryProbabilityColumn[]; }) { return ( @@ -138,26 +135,32 @@ function ModelSummaryRowView({ - {probabilityColumns.map((column) => { - const bucket = row.probabilityBucketMap[column.key] || null; - const isTopBucket = bucket?.key === row.topProbabilityBucketKey; - return ( - - - {formatModelSummaryProbability(bucket?.probability)} - - - ); - })} + + {row.probabilityBuckets.length ? ( +
+ {row.probabilityBuckets.map((bucket) => { + const isTopBucket = bucket.key === row.topProbabilityBucketKey; + return ( + + {bucket.label} + {formatModelSummaryProbability(bucket.probability)} + + ); + })} +
+ ) : ( + + )} + ); } @@ -233,11 +236,6 @@ export function ModelSummaryDashboard({ } }, [incomingSummaryRows, incomingHasForecastData]); - const probabilityColumns = useMemo( - () => buildModelSummaryProbabilityColumns(summaryRows), - [summaryRows], - ); - const visibleRows = useMemo( () => filterModelSummaryRows(summaryRows, { @@ -295,7 +293,7 @@ export function ModelSummaryDashboard({
@@ -317,15 +315,9 @@ export function ModelSummaryDashboard({ - {probabilityColumns.map((column) => ( - - ))} + @@ -334,7 +326,6 @@ export function ModelSummaryDashboard({ key={row.cityKey} row={row} nowMs={nowMs} - probabilityColumns={probabilityColumns} /> ))} diff --git a/frontend/components/dashboard/scan-terminal/__tests__/modelSummaryDashboard.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/modelSummaryDashboard.test.ts index 2e0aca9e..7a3a8670 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/modelSummaryDashboard.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/modelSummaryDashboard.test.ts @@ -2,7 +2,6 @@ import fs from "node:fs"; import path from "node:path"; import { MODEL_SUMMARY_MODEL_COLUMNS, - buildModelSummaryProbabilityColumns, buildModelSummaryRows, filterModelSummaryRows, formatModelSummaryProbability, @@ -132,12 +131,6 @@ export function runTests() { parisRow.topProbabilityBucketKey === "31.5-32.5°C", "model summary should map probability buckets and identify the top bucket", ); - const probabilityColumns = buildModelSummaryProbabilityColumns(summaryRows); - assert( - probabilityColumns.map((column) => column.key).join("|") === - "30.5-31.5°C|31.5-32.5°C|32.5-33.5°C|34.5-35.5°C|35.5-36.5°C", - "model summary should expose dynamic probability bucket columns sorted by temperature", - ); assert(formatModelSummaryProbability(null) === "—", "missing probability should render as an em dash"); assert(formatModelSummaryProbability(0.424) === "42%", "probability buckets should render as rounded percentages"); assert(formatModelSummaryTemp(null, "°C") === "—", "missing model temperatures should render as an em dash"); @@ -204,10 +197,13 @@ export function runTests() { modelSummarySource.includes("MODEL_SUMMARY_MODEL_COLUMNS") && modelSummarySource.includes("lastGoodSummaryRowsRef") && modelSummarySource.includes("hasModelSummaryForecastData") && - modelSummarySource.includes("buildModelSummaryProbabilityColumns") && modelSummarySource.includes("Gaussian μ") && modelSummarySource.includes("高斯 μ") && + modelSummarySource.includes("Probability Distribution") && + modelSummarySource.includes("概率分布") && modelSummarySource.includes("topProbabilityBucketKey") && + modelSummarySource.includes("probabilityBuckets.map") && + !modelSummarySource.includes("probabilityColumns") && modelSummarySource.includes("min-w-[96px]") && modelSummarySource.includes("Local Time") && modelSummarySource.includes("当地时间") && diff --git a/frontend/lib/model-summary.ts b/frontend/lib/model-summary.ts index 0b2216aa..a958437d 100644 --- a/frontend/lib/model-summary.ts +++ b/frontend/lib/model-summary.ts @@ -29,14 +29,6 @@ export type ModelSummaryProbabilityBucket = { probability: number; }; -export type ModelSummaryProbabilityColumn = { - key: string; - label: string; - lower: number; - upper: number; - unit: string; -}; - export type ModelSummaryRow = { cityKey: string; cityName: string; @@ -324,27 +316,3 @@ export function hasModelSummaryForecastData(rows: ModelSummaryRow[]) { return MODEL_SUMMARY_MODEL_COLUMNS.some((column) => row.models[column.key] != null); }); } - -export function buildModelSummaryProbabilityColumns( - rows: ModelSummaryRow[], -): ModelSummaryProbabilityColumn[] { - const byKey = new Map(); - rows.forEach((row) => { - row.probabilityBuckets.forEach((bucket) => { - if (byKey.has(bucket.key)) return; - const unitMatch = bucket.key.match(/[^\d.\-\s]+$/); - byKey.set(bucket.key, { - key: bucket.key, - label: bucket.label, - lower: bucket.lower, - upper: bucket.upper, - unit: unitMatch?.[0] || row.tempSymbol || "°C", - }); - }); - }); - - return [...byKey.values()].sort((a, b) => { - if (a.unit !== b.unit) return a.unit.localeCompare(b.unit); - return a.lower - b.lower || a.upper - b.upper; - }); -}
{copy("gaussianMu", isEn)} - {column.label} - + {copy("probabilityDistribution", isEn)} +