diff --git a/frontend/components/dashboard/HistoryChart.tsx b/frontend/components/dashboard/HistoryChart.tsx index b5ff2e2b..299ac512 100644 --- a/frontend/components/dashboard/HistoryChart.tsx +++ b/frontend/components/dashboard/HistoryChart.tsx @@ -125,7 +125,7 @@ export function HistoryChart() { titleFont: { family: "Inter", size: 13, weight: 600 }, callbacks: { label: (ctx) => - `${ctx.dataset.label}: ${ctx.parsed.y?.toFixed(1)}°`, + `${ctx.dataset.label}: ${ctx.parsed.y?.toFixed(1)}${store.selectedDetail?.temp_symbol || "°C"}`, }, }, }, diff --git a/frontend/components/dashboard/scan-terminal/AiCityTemperatureChart.tsx b/frontend/components/dashboard/scan-terminal/AiCityTemperatureChart.tsx index 410d5eca..d20575ee 100644 --- a/frontend/components/dashboard/scan-terminal/AiCityTemperatureChart.tsx +++ b/frontend/components/dashboard/scan-terminal/AiCityTemperatureChart.tsx @@ -109,7 +109,6 @@ export function AiCityTemperatureChart({ detail }: { detail: CityDetail }) { >["datasets"] = [ { borderColor: "rgba(100, 116, 139, 0.72)", - borderDash: [6, 4], borderWidth: 1.6, data: chartData.datasets.debPast.map( (value, index) => value ?? chartData.datasets.debFuture[index], @@ -117,6 +116,10 @@ export function AiCityTemperatureChart({ detail }: { detail: CityDetail }) { fill: false, label: forecastLabel, pointRadius: 0, + segment: { + borderDash: (ctx: { p0DataIndex: number }) => + chartData.currentIndex != null && ctx.p0DataIndex < chartData.currentIndex ? [] : [6, 4], + }, spanGaps: true, tension: 0.28, }, @@ -148,6 +151,23 @@ export function AiCityTemperatureChart({ detail }: { detail: CityDetail }) { showLine: false, }); + const ci = chartData.currentIndex; + if (ci != null && ci >= 0 && ci < (chartData.times?.length || 0)) { + datasets.push({ + backgroundColor: "rgba(77, 163, 255, 0.5)", + borderColor: "rgba(77, 163, 255, 0.5)", + borderDash: [3, 4], + borderWidth: 1.2, + data: [ + { x: ci, y: chartData.max }, + { x: ci, y: chartData.min }, + ], + label: "Now", + pointRadius: 0, + showLine: true, + }); + } + return { data: { datasets, @@ -165,7 +185,7 @@ export function AiCityTemperatureChart({ detail }: { detail: CityDetail }) { borderColor: "rgba(77, 163, 255, 0.38)", borderWidth: 1, }, - }, + } as Record, responsive: true, scales: { x: { diff --git a/frontend/components/dashboard/scan-terminal/AiPinnedCityCard.tsx b/frontend/components/dashboard/scan-terminal/AiPinnedCityCard.tsx index 72272da3..16c589db 100644 --- a/frontend/components/dashboard/scan-terminal/AiPinnedCityCard.tsx +++ b/frontend/components/dashboard/scan-terminal/AiPinnedCityCard.tsx @@ -461,6 +461,9 @@ export function AiPinnedCityCard({ const dataFreshnessRows = [ { label: isHkoObservation ? (isEn ? "HKO" : "天文台") : "METAR", + labelTitle: isHkoObservation + ? (isEn ? "Hong Kong Observatory official readings" : "香港天文台官方实测") + : (isEn ? "Meteorological Aerodrome Report — airport weather observation" : "机场气象观测报文"), value: buildObservationFreshnessValue({ detail, displayTime: metarReportTimeDisplay, diff --git a/frontend/components/dashboard/scan-terminal/DataFreshnessBar.tsx b/frontend/components/dashboard/scan-terminal/DataFreshnessBar.tsx index 054c3c63..e9a7dfef 100644 --- a/frontend/components/dashboard/scan-terminal/DataFreshnessBar.tsx +++ b/frontend/components/dashboard/scan-terminal/DataFreshnessBar.tsx @@ -4,6 +4,7 @@ import type { StatusTone } from "@/components/dashboard/scan-terminal/CityStatus export type DataFreshnessRow = { label: string; + labelTitle?: string; value: string; tone: string; }; @@ -26,7 +27,7 @@ export function DataFreshnessBar({ {isEn ? "Data freshness" : "数据新鲜度"} {rows.map((freshness) => ( - {freshness.label}{freshnessSeparator} + {freshness.label}{freshnessSeparator} {freshness.value} ))} diff --git a/frontend/components/dashboard/scan-terminal/city-card-decision-utils.ts b/frontend/components/dashboard/scan-terminal/city-card-decision-utils.ts index 2ed3362f..fe658ac4 100644 --- a/frontend/components/dashboard/scan-terminal/city-card-decision-utils.ts +++ b/frontend/components/dashboard/scan-terminal/city-card-decision-utils.ts @@ -555,15 +555,15 @@ export function buildWeatherDecisionView({ : "等待模型补齐" : paceTone === "warm" ? isEn - ? "Watch hotter range" - : "关注偏高温区间" + ? "Revise upward" + : "预计最高温上修" : paceTone === "cold" ? isEn - ? "Avoid chasing high" - : "暂不追高温" + ? "Revise downward" + : "预计最高温下修" : isEn - ? "Wait for peak-window confirmation" - : "等待峰值窗口确认"; + ? "Stay with model base" + : "维持模型基准"; const expectedHigh = center != null && Number.isFinite(Number(center)) ? formatTemperatureValue(Number(center), tempSymbol, { digits: 1 }) diff --git a/frontend/components/dashboard/scan-terminal/city-decision-state.ts b/frontend/components/dashboard/scan-terminal/city-decision-state.ts index f3a99b37..b4731a6f 100644 --- a/frontend/components/dashboard/scan-terminal/city-decision-state.ts +++ b/frontend/components/dashboard/scan-terminal/city-decision-state.ts @@ -142,28 +142,28 @@ export function buildCityDecisionState({ : "watch"; const primaryReason = observedHighBreak ? isEn - ? "Observation has broken above the model range." - : "实测已突破模型上沿。" + ? "Observation has broken above the model range. Consider the upside scenario; wait for the next bulletin to confirm the breakout." + : "实测已突破模型上沿。建议关注偏高温区间,等待下一报文确认突破是否持续。" : peakHasPassed ? isEn - ? "Peak window has passed; confirm whether a new high can still form." - : "峰值窗口已过,确认是否还会出现新高。" + ? "Peak window has passed; confirm whether a new high can still form. Avoid chasing if no new high prints." + : "峰值窗口已过,确认是否还会出现新高。若无新高,建议避免追高。" : observationStale ? isEn - ? "Observation is stale and needs the next report." - : "观测已过旧,需要下一报文确认。" + ? "Observation is stale and needs the next report. Use only as background reference until fresh data arrives." + : "观测已过旧,需要下一报文确认。当前数据仅作背景参考,建议等待新报文后再做判断。" : marketStatus === "unavailable" ? isEn - ? "Weather evidence is usable, but no tradable quote is available yet." - : "天气证据可参考,但暂无可交易价格。" + ? "Weather evidence is usable, but no tradable quote is available yet. Weather judgment still applies." + : "天气证据可参考,但暂无可交易价格。天气判断仍可参考,无需急于操作。" : modelHighlyConsistent ? isEn - ? "Models are aligned; wait for observation confirmation." - : "模型高度一致,等待实测确认。" + ? "Models are aligned; wait for observation confirmation. A clear direction should emerge after the next report." + : "模型高度一致,等待实测确认。下一报文后方向会更明确。" : needsNextBulletin ? isEn - ? "The next bulletin is more likely to decide direction." - : "下一报文更可能决定方向。" + ? "The next bulletin is more likely to decide direction. Hold until the picture clears." + : "下一报文更可能决定方向。建议等待信号明确后再做决策。" : isEn ? "Compare new observations with the expected high through the peak window." : "在峰值窗口内继续对照实测与预计高点。"; diff --git a/frontend/lib/chart-utils.ts b/frontend/lib/chart-utils.ts index 3d4e039a..5ffbf361 100644 --- a/frontend/lib/chart-utils.ts +++ b/frontend/lib/chart-utils.ts @@ -877,6 +877,7 @@ export function getTemperatureChartData( const xMax = times.length ? chartHmToMinutes(times[times.length - 1]) ?? 24 * 60 : 24 * 60; return { + currentIndex, datasets: { airportMetarPoints, airportMetarSeries,