From 8e2317592d4fff973ec0e1ca1c50e8ca7565e284 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sat, 16 May 2026 22:19:10 +0800 Subject: [PATCH] feat: implement chart utilities for temperature forecasting, calibration, and observation processing --- .../__tests__/temperatureChartData.test.ts | 40 +++++++++++++++++++ frontend/lib/chart-utils.ts | 16 ++++---- 2 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 frontend/components/dashboard/scan-terminal/__tests__/temperatureChartData.test.ts diff --git a/frontend/components/dashboard/scan-terminal/__tests__/temperatureChartData.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/temperatureChartData.test.ts new file mode 100644 index 00000000..80778b58 --- /dev/null +++ b/frontend/components/dashboard/scan-terminal/__tests__/temperatureChartData.test.ts @@ -0,0 +1,40 @@ +import { getTemperatureChartData } from "@/lib/chart-utils"; +import type { CityDetail } from "@/lib/dashboard-types"; + +function assert(condition: unknown, message: string) { + if (!condition) throw new Error(message); +} + +export function runTests() { + const chartData = getTemperatureChartData( + { + name: "test-city", + display_name: "Test City", + local_date: "2026-05-16", + local_time: "10:00", + temp_symbol: "°C", + hourly: { + times: [ + "2026-05-16T08:00:00+08:00", + "2026-05-16T09:00:00+08:00", + "2026-05-16T10:00:00+08:00", + "2026-05-16T11:00:00+08:00", + ], + temps: [20, 22, 24, 26], + }, + forecast: { today_high: 28 }, + deb: { prediction: 28 }, + } as CityDetail, + "zh-CN", + ); + + assert(chartData, "temperature chart data should exist for ISO datetime hourly input"); + assert( + chartData?.datasets.debSeries.some((point) => point.labelTime === "08:00" && point.y === 20), + "temperature chart should normalize ISO hourly times into HH:mm points", + ); + assert( + chartData?.datasets.debSeries.some((point) => point.labelTime === "10:00" && point.y === 24), + "temperature chart should keep normalized hourly temperatures on the curve", + ); +} diff --git a/frontend/lib/chart-utils.ts b/frontend/lib/chart-utils.ts index aa863c03..cb71fdd5 100644 --- a/frontend/lib/chart-utils.ts +++ b/frontend/lib/chart-utils.ts @@ -372,7 +372,7 @@ export function getTemperatureChartData( const rawTemps = Array.isArray(hourly.temps) ? hourly.temps : []; const validEntries = rawTimes .map((time, index) => ({ - tail: String(time || "").trim(), + tail: normalizeHm(String(time || "").trim()) || "", value: Number(rawTemps[index]), })) .filter((entry) => entry.tail !== ""); @@ -598,7 +598,7 @@ export function getTemperatureChartData( const tafMarkersRaw = Array.isArray(detail.taf?.signal?.markers) ? detail.taf?.signal?.markers || [] : []; - const normalizeHm = (value: unknown): string | null => { + const normalizeTafHm = (value: unknown): string | null => { const match = String(value || "").match(/(\d{1,2}):(\d{2})/); if (!match) return null; const hour = Number.parseInt(match[1], 10); @@ -632,7 +632,7 @@ export function getTemperatureChartData( } return hour * 60 + minute; }; - const currentMinutes = chartHmToMinutes(normalizeHm(detail.local_time)); + const currentMinutes = chartHmToMinutes(normalizeTafHm(detail.local_time)); const peakFirstHour = Number(detail.peak?.first_h); const peakLastHour = Number(detail.peak?.last_h); const peakWindowStartMinutes = @@ -692,23 +692,23 @@ export function getTemperatureChartData( const currentTafMarker = currentMinutes !== null ? tafMarkers.find((marker) => { - const start = chartHmToMinutes(normalizeHm(marker.startLocal)); - const end = chartHmToMinutes(normalizeHm(marker.endLocal)); + const start = chartHmToMinutes(normalizeTafHm(marker.startLocal)); + const end = chartHmToMinutes(normalizeTafHm(marker.endLocal)); return start !== null && end !== null && currentMinutes >= start && currentMinutes <= end; }) || null : null; const nextTafMarker = currentMinutes !== null && !currentTafMarker ? tafMarkers.find((marker) => { - const start = chartHmToMinutes(normalizeHm(marker.startLocal)); + const start = chartHmToMinutes(normalizeTafHm(marker.startLocal)); return start !== null && start > currentMinutes; }) || null : null; const peakWindowTafMarker = peakWindowStartMinutes !== null && peakWindowEndMinutes !== null ? tafMarkers.find((marker) => { - const start = chartHmToMinutes(normalizeHm(marker.startLocal)); - const end = chartHmToMinutes(normalizeHm(marker.endLocal)); + const start = chartHmToMinutes(normalizeTafHm(marker.startLocal)); + const end = chartHmToMinutes(normalizeTafHm(marker.endLocal)); return ( start !== null && end !== null &&