From 03eca3f93b80ee4a2d88583d2528267882472654 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sun, 26 Apr 2026 12:39:16 +0800 Subject: [PATCH] feat: implement AiPinnedForecastView component with associated decision utilities and data hooks --- .../scan-terminal/AiPinnedForecastView.tsx | 41 +- .../scan-terminal/city-card-decision-utils.ts | 60 +- .../scan-terminal/use-ai-city-card-data.ts | 246 +- frontend/package-lock.json | 7590 +---------------- 4 files changed, 639 insertions(+), 7298 deletions(-) diff --git a/frontend/components/dashboard/scan-terminal/AiPinnedForecastView.tsx b/frontend/components/dashboard/scan-terminal/AiPinnedForecastView.tsx index 6ad6d524..e9b7f269 100644 --- a/frontend/components/dashboard/scan-terminal/AiPinnedForecastView.tsx +++ b/frontend/components/dashboard/scan-terminal/AiPinnedForecastView.tsx @@ -5,7 +5,11 @@ import { ChevronDown, RefreshCw, X } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { ModelForecast } from "@/components/dashboard/PanelSections"; import { AiCityTemperatureChart } from "@/components/dashboard/scan-terminal/AiCityTemperatureChart"; -import { buildMarketDecisionView, buildWeatherDecisionView } from "@/components/dashboard/scan-terminal/city-card-decision-utils"; +import { + buildMarketDecisionView, + buildWeatherDecisionView, + resolveExpectedHighCandidate, +} from "@/components/dashboard/scan-terminal/city-card-decision-utils"; import { findDetailForCity } from "@/components/dashboard/scan-terminal/city-detail-utils"; import { findRowForCity, getPeakWindowLabel, normalizeCityKey } from "@/components/dashboard/scan-terminal/decision-utils"; import { LoadingSignal } from "@/components/dashboard/scan-terminal/LoadingSignal"; @@ -127,6 +131,8 @@ function AiPinnedCityCard({ detail?.current?.temp ?? row?.current_temp ?? null; + const debNumber = toFiniteDecisionNumber(deb); + const currentTempNumber = toFiniteDecisionNumber(currentTemp); const modelRange = modelMin != null && modelMax != null ? `${formatTemperatureValue(modelMin, tempSymbol, { digits: 1 })} ~ ${formatTemperatureValue(modelMax, tempSymbol, { digits: 1 })}` @@ -206,15 +212,18 @@ function AiPinnedCityCard({ ? "Model support is unavailable, so this city must rely on DEB path and METAR observations." : "暂无可用多模型支撑,需要主要参考 DEB 路径和 METAR 实测。"; const aiPredictedMax = toFiniteDecisionNumber(aiCityForecast?.predicted_max); - const decisionExpectedHighNumber = aiPredictedMax != null - ? aiPredictedMax - : paceView?.paceAdjustedHigh != null - ? paceView.paceAdjustedHigh - : deb; + const decisionExpectedHighNumber = resolveExpectedHighCandidate({ + aiPredictedMax, + currentTemp: currentTempNumber, + deb: debNumber, + modelMax, + modelMin, + paceAdjustedHigh: paceView?.paceAdjustedHigh ?? null, + }); const decisionView = buildWeatherDecisionView({ aiCityForecast, - currentTemp, - deb, + currentTemp: currentTempNumber, + deb: debNumber, isEn, localModelSupportNote, modelEntries, @@ -264,8 +273,8 @@ function AiPinnedCityCard({ {detail?.local_time || row?.local_time || "--"} DEB{" "} - {deb != null - ? formatTemperatureValue(deb, tempSymbol, { digits: 1 }) + {debNumber != null + ? formatTemperatureValue(debNumber, tempSymbol, { digits: 1 }) : "--"} {isEn ? "Model" : "模型"} {modelRange} @@ -275,11 +284,9 @@ function AiPinnedCityCard({
{isEn ? "Expected high" : "预计最高温"} - {paceView?.paceAdjustedHigh != null - ? formatTemperatureValue(paceView.paceAdjustedHigh, tempSymbol, { digits: 1 }) - : deb != null - ? formatTemperatureValue(deb, tempSymbol, { digits: 1 }) - : "--"} + {decisionExpectedHighNumber != null + ? formatTemperatureValue(decisionExpectedHighNumber, tempSymbol, { digits: 1 }) + : "--"}