From 77f2b6ac07ee008459b45c766a3d8a97b4947142 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Tue, 24 Mar 2026 15:12:49 +0800 Subject: [PATCH] Fix English airport narrative fallback in dashboard --- .../dashboard/FutureForecastModal.tsx | 3 +- frontend/lib/dashboard-utils.ts | 66 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/frontend/components/dashboard/FutureForecastModal.tsx b/frontend/components/dashboard/FutureForecastModal.tsx index ff8ab6c3..44954559 100644 --- a/frontend/components/dashboard/FutureForecastModal.tsx +++ b/frontend/components/dashboard/FutureForecastModal.tsx @@ -25,6 +25,7 @@ import { } from "@/components/dashboard/PanelSections"; import { getFutureModalView, + getAirportNarrative, parseAiAnalysis, getTemperatureChartData, getWeatherSummary, @@ -723,7 +724,7 @@ export function FutureForecastModal() { detail.current?.raw_metar, detail.current?.visibility_mi, ); - const ai = parseAiAnalysis(detail.ai_analysis); + const ai = getAirportNarrative(detail, locale); const risk = detail.risk || {}; const settlementSourceCode = String( detail.current?.settlement_source || "", diff --git a/frontend/lib/dashboard-utils.ts b/frontend/lib/dashboard-utils.ts index 2037be13..b39fd3f5 100644 --- a/frontend/lib/dashboard-utils.ts +++ b/frontend/lib/dashboard-utils.ts @@ -523,6 +523,72 @@ export function parseAiAnalysis(analysis: CityDetail["ai_analysis"]) { }; } +export function getAirportNarrative( + detail: CityDetail, + locale: Locale = "zh-CN", +) { + const parsed = parseAiAnalysis(detail.ai_analysis); + if (!isEnglish(locale)) return parsed; + + const englishSummary = containsCjk(parsed.summary) ? "" : parsed.summary.trim(); + const englishBullets = parsed.bullets + .map((item) => String(item || "").trim()) + .filter((item) => item && !containsCjk(item)); + + if (englishSummary || englishBullets.length > 0) { + return { + bullets: englishBullets, + summary: englishSummary, + }; + } + + const sourceLabel = + String(detail.current?.settlement_source_label || "").trim() || + String(detail.risk?.icao || "").trim() || + String(detail.risk?.airport || "").trim() || + String(detail.display_name || detail.name || "").trim() || + "Airport"; + const currentTemp = Number(detail.current?.temp); + const tempText = Number.isFinite(currentTemp) + ? `${currentTemp}${detail.temp_symbol || "°C"}` + : null; + const obsTime = String(detail.current?.obs_time || "").trim(); + const weatherText = getWeatherSummary(detail, locale).weatherText.toLowerCase(); + const windBucket = bucketLabel( + trendBucketFromDir(detail.current?.wind_dir ?? null), + locale, + ); + const windSpeedKt = Number(detail.current?.wind_speed_kt); + const tafSummary = String(detail.taf?.signal?.summary_en || "").trim(); + const windPhrase = Number.isFinite(windSpeedKt) + ? `${windBucket} around ${windSpeedKt} kt` + : `${windBucket} prevailing`; + const summaryParts = [ + tempText + ? `${sourceLabel} reports ${tempText}${obsTime ? ` at ${obsTime}` : ""}, ${weatherText}.` + : `${sourceLabel} reports ${weatherText}${obsTime ? ` at ${obsTime}` : ""}.`, + `${windPhrase}.`, + tafSummary, + ].filter(Boolean); + + const bullets: string[] = []; + const rawMetar = String(detail.current?.raw_metar || "").trim(); + if (rawMetar) { + bullets.push(`Latest METAR: ${rawMetar}`); + } + if (tafSummary) { + bullets.push(`TAF signal: ${tafSummary}`); + } + if (detail.taf?.raw_taf) { + bullets.push(`TAF available for airport-side timing checks.`); + } + + return { + bullets, + summary: summaryParts.join(" "), + }; +} + export function pickAnkaraNearbyStations(stations: NearbyStation[]) { const preferredNames = [ "Airport (MGM/17128)",