feat: implement chart utilities for temperature forecasting, calibration, and observation processing

This commit is contained in:
2569718930@qq.com
2026-05-16 22:19:10 +08:00
parent 80ca892611
commit 8e2317592d
2 changed files with 48 additions and 8 deletions
@@ -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",
);
}
+8 -8
View File
@@ -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 &&