feat: Implement core PolyWeather application with city weather query service, trend analysis, data collection, and dashboard UI.
This commit is contained in:
@@ -168,12 +168,6 @@ export interface SourceForecasts {
|
||||
weather_gov?: {
|
||||
forecast_periods?: WeatherGovPeriod[];
|
||||
};
|
||||
meteoblue?: {
|
||||
today_high?: number | null;
|
||||
daily_highs?: Array<number | null>;
|
||||
unit?: string | null;
|
||||
url?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DailyModelForecast {
|
||||
|
||||
@@ -330,67 +330,18 @@ export function getProbabilityView(detail: CityDetail, targetDate?: string | nul
|
||||
}
|
||||
|
||||
export function getModelView(detail: CityDetail, targetDate?: string | null) {
|
||||
const toFiniteNumber = (value: unknown): number | null => {
|
||||
if (value === null || value === undefined || value === "") return null;
|
||||
const numeric = Number(value);
|
||||
return Number.isFinite(numeric) ? numeric : null;
|
||||
};
|
||||
|
||||
const pickMeteoblueForDate = (dateStr: string) => {
|
||||
const meteoblue = detail.source_forecasts?.meteoblue;
|
||||
if (!meteoblue) return null;
|
||||
|
||||
const dates = detail.forecast?.daily?.map((item) => item.date) || [];
|
||||
const index = dates.findIndex((date) => date === dateStr);
|
||||
const todayHigh = toFiniteNumber(meteoblue.today_high);
|
||||
|
||||
// Today must always use Meteoblue daily max (today_high) first.
|
||||
if (dateStr === detail.local_date && todayHigh != null) {
|
||||
return todayHigh;
|
||||
}
|
||||
|
||||
const dailyHighs = Array.isArray(meteoblue.daily_highs)
|
||||
? meteoblue.daily_highs
|
||||
: [];
|
||||
if (index >= 0) {
|
||||
const byDailyHigh = toFiniteNumber(dailyHighs[index]);
|
||||
if (byDailyHigh != null) return byDailyHigh;
|
||||
}
|
||||
|
||||
if (index === 0 && todayHigh != null) return todayHigh;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const withMeteoblue = (
|
||||
models: Record<string, number | null>,
|
||||
dateStr: string,
|
||||
) => {
|
||||
const nextModels = { ...models };
|
||||
const existing = toFiniteNumber(nextModels.Meteoblue);
|
||||
if (existing == null) {
|
||||
const meteoblueVal = pickMeteoblueForDate(dateStr);
|
||||
if (meteoblueVal != null) {
|
||||
nextModels.Meteoblue = meteoblueVal;
|
||||
}
|
||||
} else {
|
||||
nextModels.Meteoblue = existing;
|
||||
}
|
||||
return nextModels;
|
||||
};
|
||||
|
||||
const date = targetDate || detail.local_date;
|
||||
const daily = detail.multi_model_daily?.[date];
|
||||
if (daily) {
|
||||
return {
|
||||
deb: daily.deb?.prediction ?? null,
|
||||
models: withMeteoblue(daily.models || {}, date),
|
||||
models: daily.models || {},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
deb: detail.deb?.prediction ?? null,
|
||||
models: withMeteoblue(detail.multi_model || {}, date),
|
||||
models: detail.multi_model || {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -712,10 +663,6 @@ export function computeFrontTrendSignal(
|
||||
? isEnglish(locale)
|
||||
? "Temperature declines with pressure rebound and/or northerly shift. Next 6-48h leans cold-front suppression."
|
||||
: "温度下滑、气压回升或风向转北,未来 6-48 小时更像冷锋或冷平流压制。"
|
||||
: detail.name !== "ankara" && Boolean(detail.source_forecasts?.meteoblue)
|
||||
? isEnglish(locale)
|
||||
? "Structured trend layer mainly uses weather.gov, Open-Meteo and Meteoblue for 6-48h warm/cold flow judgement."
|
||||
: "结构化来源以 weather.gov、Open-Meteo、Meteoblue 为主,用于判断未来 6-48 小时冷暖平流趋势。"
|
||||
: isEnglish(locale)
|
||||
? "Structured trend layer mainly uses weather.gov and Open-Meteo for 6-48h warm/cold flow judgement."
|
||||
: "结构化来源以 weather.gov 和 Open-Meteo 为主,用于判断未来 6-48 小时冷暖平流趋势。",
|
||||
|
||||
@@ -42,7 +42,7 @@ const MESSAGES: Record<Locale, Record<string, string>> = {
|
||||
"guide.title": "📎 PolyWeather 系统技术说明",
|
||||
"guide.closeAria": "关闭技术说明",
|
||||
"guide.footer":
|
||||
"数据源以 Aviation Weather / METAR、Turkish MGM、Open-Meteo、weather.gov 为主,部分城市补充 Meteoblue。",
|
||||
"数据源以 Aviation Weather / METAR、Turkish MGM、Open-Meteo、weather.gov 为主。",
|
||||
|
||||
"history.title": "📊 历史准确率对账 - {city}",
|
||||
"history.closeAria": "关闭历史对账",
|
||||
@@ -143,7 +143,7 @@ const MESSAGES: Record<Locale, Record<string, string>> = {
|
||||
"guide.title": "📎 PolyWeather Technical Overview",
|
||||
"guide.closeAria": "Close technical overview",
|
||||
"guide.footer":
|
||||
"Primary data sources are Aviation Weather / METAR, Turkish MGM, Open-Meteo, and weather.gov, with Meteoblue added for selected cities.",
|
||||
"Primary data sources are Aviation Weather / METAR, Turkish MGM, Open-Meteo, and weather.gov.",
|
||||
|
||||
"history.title": "📊 Historical Reconciliation - {city}",
|
||||
"history.closeAria": "Close history reconciliation",
|
||||
|
||||
@@ -186,7 +186,6 @@ export interface ModelComparison {
|
||||
JMA?: number;
|
||||
MGM?: number;
|
||||
NWS?: number;
|
||||
Meteoblue?: number;
|
||||
}
|
||||
|
||||
export interface DEBAnalysis {
|
||||
|
||||
Reference in New Issue
Block a user