From bda476556f1c3c8f4a75c409b0a8298aa5932d6d Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Thu, 23 Apr 2026 06:37:55 +0800 Subject: [PATCH] Align homepage EMOS ladder to market buckets --- .../dashboard/PolyWeatherDashboard.tsx | 99 ++++++++++++++++++- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/frontend/components/dashboard/PolyWeatherDashboard.tsx b/frontend/components/dashboard/PolyWeatherDashboard.tsx index 504d50b5..06edc55d 100644 --- a/frontend/components/dashboard/PolyWeatherDashboard.tsx +++ b/frontend/components/dashboard/PolyWeatherDashboard.tsx @@ -17,6 +17,9 @@ import type { CityDetail, CityListItem, CitySummary, + MarketScan, + MarketTopBucket, + ProbabilityBucket, RiskLevel, } from "@/lib/dashboard-types"; import { @@ -158,6 +161,89 @@ function getProbabilityLabel( return "--"; } +function parseBucketThreshold(bucket?: { + label?: string | null; + value?: number | null; + temp?: number | null; + bucket?: string | null; + range?: string | null; +}) { + const directValue = bucket?.value ?? bucket?.temp; + if (Number.isFinite(Number(directValue))) return Number(directValue); + const text = String(bucket?.label || bucket?.bucket || bucket?.range || ""); + const match = text.match(/-?\d+(?:\.\d+)?/); + if (!match) return null; + const parsed = Number(match[0]); + return Number.isFinite(parsed) ? parsed : null; +} + +function getBucketMatchKey(bucket?: { + label?: string | null; + value?: number | null; + temp?: number | null; + bucket?: string | null; + range?: string | null; +}) { + const threshold = parseBucketThreshold(bucket); + if (threshold != null) return `t:${threshold.toFixed(2)}`; + return `l:${String(bucket?.label || bucket?.bucket || bucket?.range || "").trim().toLowerCase()}`; +} + +function normalizeProbabilityValue(value: number | null | undefined) { + if (!Number.isFinite(Number(value))) return null; + const numeric = Number(value); + return Math.abs(numeric) <= 1 ? numeric : numeric / 100; +} + +function buildMarketAlignedProbabilities( + marketScan: MarketScan | null | undefined, + probabilityBuckets: ProbabilityBucket[], + symbol: string, +) { + const modelByKey = new Map( + probabilityBuckets.map((bucket) => [getBucketMatchKey(bucket), bucket]), + ); + const marketBuckets: Array = [ + ...(Array.isArray(marketScan?.all_buckets) ? marketScan.all_buckets : []), + ...(Array.isArray(marketScan?.top_buckets) ? marketScan.top_buckets : []), + ...(marketScan?.temperature_bucket ? [marketScan.temperature_bucket] : []), + ]; + const aligned: ProbabilityBucket[] = []; + const seen = new Set(); + + for (const marketBucket of marketBuckets) { + const key = getBucketMatchKey(marketBucket); + if (!key || seen.has(key)) continue; + seen.add(key); + + const matchedModelBucket = modelByKey.get(key); + aligned.push({ + label: getProbabilityLabel(marketBucket, symbol), + value: + marketBucket.value ?? + ("temp" in marketBucket ? marketBucket.temp : undefined) ?? + matchedModelBucket?.value ?? + null, + bucket: + ("bucket" in marketBucket ? marketBucket.bucket : undefined) ?? + matchedModelBucket?.bucket ?? + null, + range: + ("range" in marketBucket ? marketBucket.range : undefined) ?? + matchedModelBucket?.range ?? + null, + unit: + marketBucket.unit ?? + matchedModelBucket?.unit ?? + (symbol === "°F" ? "F" : "C"), + probability: + normalizeProbabilityValue(matchedModelBucket?.probability) ?? null, + }); + } + + return aligned.length ? aligned.slice(0, 4) : probabilityBuckets.slice(0, 4); +} + function formatProbability(value: number | null | undefined) { if (!Number.isFinite(Number(value))) return "--"; const normalized = @@ -699,8 +785,12 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) { ? detail?.multi_model_daily?.[detail.local_date]?.probabilities : undefined) || []; - const displayedProbabilities = probabilityBuckets.slice(0, 4); const marketScan = detail?.market_scan; + const displayedProbabilities = buildMarketAlignedProbabilities( + marketScan, + probabilityBuckets, + symbol, + ); const marketBucket = marketScan?.temperature_bucket || marketScan?.top_buckets?.[0] || null; const yesPrice = @@ -1039,8 +1129,11 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) { {displayedProbabilities.length ? (
{displayedProbabilities.map((bucket, index) => { - const probability = Number(bucket.probability ?? 0); - const width = Math.max(6, Math.min(100, probability * 100)); + const probability = normalizeProbabilityValue(bucket.probability); + const width = + probability == null + ? 6 + : Math.max(6, Math.min(100, probability * 100)); return (