Add LightGBM daily high forecasting pipeline
This commit is contained in:
@@ -4,7 +4,7 @@ import { startTransition, useEffect, useMemo, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useDashboardStore } from "@/hooks/useDashboardStore";
|
||||
import { useI18n } from "@/hooks/useI18n";
|
||||
import { CityListItem } from "@/lib/dashboard-types";
|
||||
import { CityListItem, DeviationMonitor } from "@/lib/dashboard-types";
|
||||
|
||||
type RiskGroupKey = "high" | "medium" | "low" | "other";
|
||||
|
||||
@@ -50,7 +50,7 @@ function normalizeExpandedGroups(
|
||||
|
||||
export function CitySidebar() {
|
||||
const store = useDashboardStore();
|
||||
const { t } = useI18n();
|
||||
const { locale, t } = useI18n();
|
||||
const selectedCity = store.selectedCity;
|
||||
const riskOrder = { high: 0, medium: 1, low: 2, other: 3 };
|
||||
const [expandedGroups, setExpandedGroups] = useState<
|
||||
@@ -114,6 +114,16 @@ export function CitySidebar() {
|
||||
} catch {}
|
||||
}, [expandedGroups]);
|
||||
|
||||
const formatDeviationText = (monitor?: DeviationMonitor | null) => {
|
||||
if (!monitor?.available) return "";
|
||||
const label =
|
||||
locale === "en-US" ? monitor.label_en : monitor.label_zh;
|
||||
const trendLabel =
|
||||
locale === "en-US" ? monitor.trend_label_en : monitor.trend_label_zh;
|
||||
if (!label) return "";
|
||||
return trendLabel ? `${label} · ${trendLabel}` : label;
|
||||
};
|
||||
|
||||
const groupMeta: Array<{ key: RiskGroupKey; label: string }> = [
|
||||
{ key: "high", label: t("sidebar.group.high") },
|
||||
{ key: "medium", label: t("sidebar.group.medium") },
|
||||
@@ -172,6 +182,9 @@ export function CitySidebar() {
|
||||
temp: `${snapshot.current.temp}${tempSymbol}`,
|
||||
})
|
||||
: t("common.na");
|
||||
const deviationText = formatDeviationText(
|
||||
snapshot?.deviation_monitor,
|
||||
);
|
||||
const peakTempText =
|
||||
detail?.current?.max_so_far != null &&
|
||||
detail.current.max_temp_time
|
||||
@@ -182,6 +195,11 @@ export function CitySidebar() {
|
||||
: detail?.current?.max_temp_time
|
||||
? t("sidebar.peakAt", { time: detail.current.max_temp_time })
|
||||
: "";
|
||||
const deviationDirection =
|
||||
snapshot?.deviation_monitor?.direction || "normal";
|
||||
const deviationSeverity =
|
||||
snapshot?.deviation_monitor?.severity || "normal";
|
||||
const secondaryText = deviationText || peakTempText;
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -211,7 +229,19 @@ export function CitySidebar() {
|
||||
<span className="city-local-time">
|
||||
{snapshot?.local_time ? `🕒 ${snapshot.local_time}` : ""}
|
||||
</span>
|
||||
<span className="city-max-info">{peakTempText}</span>
|
||||
<span
|
||||
className={clsx(
|
||||
"city-max-info",
|
||||
deviationText && "city-deviation-info",
|
||||
deviationText &&
|
||||
`city-deviation-${deviationDirection}`,
|
||||
deviationText &&
|
||||
deviationSeverity === "strong" &&
|
||||
"strong",
|
||||
)}
|
||||
>
|
||||
{secondaryText}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -488,6 +488,30 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.root :global(.city-item .city-deviation-info) {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.root :global(.city-item .city-deviation-cold) {
|
||||
color: #38bdf8;
|
||||
}
|
||||
|
||||
.root :global(.city-item .city-deviation-hot) {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.root :global(.city-item .city-deviation-normal) {
|
||||
color: #22d3ee;
|
||||
}
|
||||
|
||||
.root :global(.city-item .city-deviation-info.strong) {
|
||||
text-shadow: 0 0 10px rgba(56, 189, 248, 0.18);
|
||||
}
|
||||
|
||||
.root :global(.city-item .city-deviation-hot.strong) {
|
||||
text-shadow: 0 0 10px rgba(245, 158, 11, 0.24);
|
||||
}
|
||||
|
||||
.root :global(.city-item .risk-dot) {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
@@ -74,6 +74,7 @@ export function toCitySummary(detail: CityDetail): CitySummary {
|
||||
deb: {
|
||||
prediction: detail.deb?.prediction,
|
||||
},
|
||||
deviation_monitor: detail.deviation_monitor,
|
||||
risk: {
|
||||
level: detail.risk?.level,
|
||||
warning: detail.risk?.warning,
|
||||
|
||||
@@ -58,40 +58,6 @@ const CITY_SPECIFIC_SOURCES: Record<string, OfficialSourceLink[]> = {
|
||||
kind: "metar",
|
||||
},
|
||||
],
|
||||
"shek kong": [
|
||||
{
|
||||
label: "香港天文台",
|
||||
href: "https://www.hko.gov.hk/en/index.html",
|
||||
kind: "agency",
|
||||
},
|
||||
{
|
||||
label: "HKO 区域天气数据",
|
||||
href: "https://data.weather.gov.hk/weatherAPI/hko_data/regional-weather/latest_1min_temperature.csv",
|
||||
kind: "agency",
|
||||
},
|
||||
{
|
||||
label: "VHSK Timeseries",
|
||||
href: "https://www.weather.gov/wrh/timeseries?site=VHSK",
|
||||
kind: "metar",
|
||||
},
|
||||
],
|
||||
"lau fau shan": [
|
||||
{
|
||||
label: "香港天文台",
|
||||
href: "https://www.hko.gov.hk/en/index.html",
|
||||
kind: "agency",
|
||||
},
|
||||
{
|
||||
label: "HKO 区域天气数据",
|
||||
href: "https://data.weather.gov.hk/weatherAPI/hko_data/regional-weather/latest_1min_temperature.csv",
|
||||
kind: "agency",
|
||||
},
|
||||
{
|
||||
label: "HKO 实时读数页",
|
||||
href: "https://www.hko.gov.hk/textonly/v2/forecast/text_readings_e.htm",
|
||||
kind: "agency",
|
||||
},
|
||||
],
|
||||
taipei: [
|
||||
{
|
||||
label: "NOAA RCTP Timeseries",
|
||||
@@ -150,6 +116,108 @@ const CITY_SPECIFIC_SOURCES: Record<string, OfficialSourceLink[]> = {
|
||||
kind: "metar",
|
||||
},
|
||||
],
|
||||
"los angeles": [
|
||||
{
|
||||
label: "NWS Los Angeles/Oxnard",
|
||||
href: "https://www.weather.gov/lox/",
|
||||
kind: "agency",
|
||||
},
|
||||
{
|
||||
label: "LAX Airport",
|
||||
href: "https://www.flylax.com/",
|
||||
kind: "airport",
|
||||
},
|
||||
{
|
||||
label: "KLAX METAR",
|
||||
href: "https://aviationweather.gov/data/metar/?id=KLAX&decoded=1&taf=1",
|
||||
kind: "metar",
|
||||
},
|
||||
],
|
||||
"san francisco": [
|
||||
{
|
||||
label: "NWS San Francisco Bay Area",
|
||||
href: "https://www.weather.gov/mtr/",
|
||||
kind: "agency",
|
||||
},
|
||||
{
|
||||
label: "SFO Airport",
|
||||
href: "https://www.flysfo.com/",
|
||||
kind: "airport",
|
||||
},
|
||||
{
|
||||
label: "KSFO METAR",
|
||||
href: "https://aviationweather.gov/data/metar/?id=KSFO&decoded=1&taf=1",
|
||||
kind: "metar",
|
||||
},
|
||||
],
|
||||
aurora: [
|
||||
{
|
||||
label: "NWS Denver/Boulder",
|
||||
href: "https://www.weather.gov/bou/",
|
||||
kind: "agency",
|
||||
},
|
||||
{
|
||||
label: "Buckley Space Force Base",
|
||||
href: "https://www.buckley.spaceforce.mil/",
|
||||
kind: "airport",
|
||||
},
|
||||
{
|
||||
label: "KBKF METAR",
|
||||
href: "https://aviationweather.gov/data/metar/?id=KBKF&decoded=1&taf=1",
|
||||
kind: "metar",
|
||||
},
|
||||
],
|
||||
austin: [
|
||||
{
|
||||
label: "NWS Austin/San Antonio",
|
||||
href: "https://www.weather.gov/ewx/",
|
||||
kind: "agency",
|
||||
},
|
||||
{
|
||||
label: "Austin-Bergstrom Airport",
|
||||
href: "https://www.austintexas.gov/airport",
|
||||
kind: "airport",
|
||||
},
|
||||
{
|
||||
label: "KAUS METAR",
|
||||
href: "https://aviationweather.gov/data/metar/?id=KAUS&decoded=1&taf=1",
|
||||
kind: "metar",
|
||||
},
|
||||
],
|
||||
houston: [
|
||||
{
|
||||
label: "NWS Houston/Galveston",
|
||||
href: "https://www.weather.gov/hgx/",
|
||||
kind: "agency",
|
||||
},
|
||||
{
|
||||
label: "William P. Hobby Airport",
|
||||
href: "https://www.fly2houston.com/hobby",
|
||||
kind: "airport",
|
||||
},
|
||||
{
|
||||
label: "KHOU METAR",
|
||||
href: "https://aviationweather.gov/data/metar/?id=KHOU&decoded=1&taf=1",
|
||||
kind: "metar",
|
||||
},
|
||||
],
|
||||
"mexico city": [
|
||||
{
|
||||
label: "SMN",
|
||||
href: "https://smn.conagua.gob.mx/",
|
||||
kind: "agency",
|
||||
},
|
||||
{
|
||||
label: "AICM",
|
||||
href: "https://www.aicm.com.mx/",
|
||||
kind: "airport",
|
||||
},
|
||||
{
|
||||
label: "MMMX METAR",
|
||||
href: "https://aviationweather.gov/data/metar/?id=MMMX&decoded=1&taf=1",
|
||||
kind: "metar",
|
||||
},
|
||||
],
|
||||
ankara: [
|
||||
{
|
||||
label: "MGM",
|
||||
|
||||
@@ -162,6 +162,7 @@ export interface CitySummary {
|
||||
deb?: {
|
||||
prediction?: number | null;
|
||||
};
|
||||
deviation_monitor?: DeviationMonitor;
|
||||
risk?: {
|
||||
level?: RiskLevel;
|
||||
warning?: string | null;
|
||||
@@ -169,6 +170,19 @@ export interface CitySummary {
|
||||
updated_at?: string | null;
|
||||
}
|
||||
|
||||
export interface DeviationMonitor {
|
||||
available?: boolean;
|
||||
current_delta?: number | null;
|
||||
reference_temp?: number | null;
|
||||
direction?: "normal" | "cold" | "hot" | string;
|
||||
severity?: "normal" | "light" | "strong" | string;
|
||||
trend?: "stable" | "expanding" | "contracting" | string;
|
||||
label_zh?: string | null;
|
||||
label_en?: string | null;
|
||||
trend_label_zh?: string | null;
|
||||
trend_label_en?: string | null;
|
||||
}
|
||||
|
||||
export interface HourlySeries {
|
||||
times?: string[];
|
||||
temps?: Array<number | null>;
|
||||
@@ -301,6 +315,7 @@ export interface CityDetail {
|
||||
forecast?: ForecastData;
|
||||
multi_model?: Record<string, number | null>;
|
||||
deb?: DebForecast;
|
||||
deviation_monitor?: DeviationMonitor;
|
||||
probabilities?: {
|
||||
mu?: number | null;
|
||||
distribution?: ProbabilityBucket[];
|
||||
|
||||
@@ -186,6 +186,7 @@ export interface ModelComparison {
|
||||
ICON?: number;
|
||||
GEM?: number;
|
||||
JMA?: number;
|
||||
LGBM?: number;
|
||||
MGM?: number;
|
||||
NWS?: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user