feat: add weekly reward loop, i18n support, and scan terminal dashboard components

This commit is contained in:
2569718930@qq.com
2026-05-27 07:50:05 +08:00
parent 2670e2f8ee
commit 79d94bed5f
14 changed files with 786 additions and 76 deletions
@@ -4,6 +4,7 @@ import {
__getDebPeakWindowRangeForTest,
__getLiveObservationLabelsForTest,
__getObservationDisplayMetricsForTest,
__getPeakGlowStateForTest,
__getVisibleTemperatureSeriesForTest,
__isTemperatureSeriesVisibleByDefaultForTest,
__mergePatchIntoHourlyForTest,
@@ -22,6 +23,69 @@ function runwayKey(rwy: string) {
}
export function runTests() {
const peakGlowSeries = [
{
key: "hourly_forecast",
label: "DEB Forecast",
source: "DEB Hourly",
color: "#f97316",
values: [26, 29, 32, 32, 30],
},
{
key: "madis",
label: "METAR",
source: "METAR",
color: "#0284c7",
values: [26.0, 29.0, null, 30.4, null],
},
] as any;
const peakGlowData = [
{ ts: Date.UTC(2026, 4, 27, 10, 0), hourly_forecast: 26, madis: 26.0 },
{ ts: Date.UTC(2026, 4, 27, 11, 0), hourly_forecast: 29, madis: 29.0 },
{ ts: Date.UTC(2026, 4, 27, 12, 0), hourly_forecast: 32, madis: null },
{ ts: Date.UTC(2026, 4, 27, 13, 0), hourly_forecast: 32, madis: 30.4 },
{ ts: Date.UTC(2026, 4, 27, 14, 0), hourly_forecast: 30, madis: null },
] as any;
assert(
__getPeakGlowStateForTest({ temp_symbol: "°C" } as any, peakGlowData, peakGlowSeries).state === "near_peak",
"city chart should enter near-peak glow when live temperature is within 2C of DEB and not cooling",
);
assert(
__getPeakGlowStateForTest({ temp_symbol: "°C" } as any, peakGlowData, [
peakGlowSeries[0],
{ ...peakGlowSeries[1], values: [26.0, 29.0, null, 29.2, null] },
] as any).state === "watch",
"city chart should enter watch glow when live temperature is within 3C of DEB but not near peak",
);
assert(
__getPeakGlowStateForTest({ temp_symbol: "°C" } as any, peakGlowData, [
peakGlowSeries[0],
{ ...peakGlowSeries[1], values: [26.0, 29.0, null, 32.1, null] },
] as any).state === "breakout",
"city chart should use breakout glow when live temperature reaches or exceeds DEB peak",
);
assert(
__getPeakGlowStateForTest({ temp_symbol: "°C" } as any, [
{ ts: Date.UTC(2026, 4, 27, 10, 0), hourly_forecast: 26, madis: 26.0 },
{ ts: Date.UTC(2026, 4, 27, 11, 0), hourly_forecast: 30, madis: 30.0 },
{ ts: Date.UTC(2026, 4, 27, 12, 0), hourly_forecast: 32, madis: 31.8 },
{ ts: Date.UTC(2026, 4, 27, 13, 0), hourly_forecast: 30, madis: 30.8 },
{ ts: Date.UTC(2026, 4, 27, 14, 0), hourly_forecast: 27, madis: 30.2 },
] as any, [
{ ...peakGlowSeries[0], values: [26, 30, 32, 30, 27] },
{ ...peakGlowSeries[1], values: [26.0, 30.0, 31.8, 30.8, 30.2] },
] as any).state === "cooling",
"city chart should stop hot glow and show cooling state after peak when live temperature rolls over",
);
assert(
__getPeakGlowStateForTest({ temp_symbol: "°F" } as any, peakGlowData, [
{ ...peakGlowSeries[0], values: [79, 84, 90, 90, 86] },
{ ...peakGlowSeries[1], values: [80, 84, null, 86.2, null] },
] as any).state === "watch",
"US Fahrenheit charts should convert Celsius thresholds before deciding peak glow state",
);
const guangzhou = {
city: "guangzhou",
local_date: "2026-05-25",
@@ -433,6 +497,41 @@ export function runTests() {
assert(madisSeries.label.includes("MADIS"), "US MADIS series should be labeled as NOAA MADIS instead of plain METAR");
assert(madisSeries.values.filter((value: number | null) => value !== null).length >= 2, "MADIS series should keep sub-hourly observations");
const torontoWithLatestAirportReport = __buildTemperatureChartDataForTest(
{
city: "toronto",
local_date: "2026-05-27",
local_time: "19:16",
tz_offset_seconds: -4 * 60 * 60,
airport: "CYYZ",
temp_symbol: "°C",
} as any,
{
localTime: "19:16",
times: ["10:00", "13:00", "16:00", "19:00"],
temps: [23, 26, 27, 26],
airportPrimary: {
source_code: "metar",
source_label: "METAR",
temp: 26,
obs_time: "2026-05-27T23:16:00Z",
},
airportPrimaryTodayObs: [
["2026-05-27T21:00:00Z", 27],
["2026-05-27T22:00:00Z", 28],
["2026-05-27T23:00:00Z", 27],
],
} as any,
"1D",
);
const latestAirportPoint = torontoWithLatestAirportReport.data.find(
(point) => point.label === "19:16:00" && point.madis === 26,
);
assert(
latestAirportPoint,
"latest airport/METAR report should be appended to the live chart series even when history stops earlier",
);
const newYorkMinuteStream = __buildTemperatureChartDataForTest(
{
city: "new york",
@@ -41,4 +41,9 @@ export function runTests() {
selectorSource.includes("grid grid-cols-3"),
"grid selector must expose at most a 3 by 3 chart layout",
);
assert(
dashboardSource.includes("if (!cityInSlot || !rowForSlot)") &&
dashboardSource.includes("handleSelectCityForSlot(slotIndex, null);"),
"stale saved chart slots must render the empty city picker instead of a row=null Temperature Chart",
);
}