Normalize Wunderground labels to METAR
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
|||||||
getRiskBadgeLabel,
|
getRiskBadgeLabel,
|
||||||
getTemperatureChartData,
|
getTemperatureChartData,
|
||||||
} from "@/lib/dashboard-utils";
|
} from "@/lib/dashboard-utils";
|
||||||
|
import { normalizeObservationSourceLabel } from "@/lib/source-labels";
|
||||||
|
|
||||||
function DetailMiniTemperatureChart({ detail }: { detail: CityDetail }) {
|
function DetailMiniTemperatureChart({ detail }: { detail: CityDetail }) {
|
||||||
const { locale, t } = useI18n();
|
const { locale, t } = useI18n();
|
||||||
@@ -178,17 +179,20 @@ export function DetailPanel() {
|
|||||||
() => getTodayPolymarketUrl(detail, locale),
|
() => getTodayPolymarketUrl(detail, locale),
|
||||||
[detail, locale],
|
[detail, locale],
|
||||||
);
|
);
|
||||||
const basicSettlementLabel =
|
const basicSettlementLabel = normalizeObservationSourceLabel(
|
||||||
selectedSummary?.current?.settlement_source_label ||
|
selectedSummary?.current?.settlement_source_label ||
|
||||||
selectedCityItem?.settlement_source_label ||
|
selectedCityItem?.settlement_source_label ||
|
||||||
selectedCityItem?.settlement_source ||
|
selectedCityItem?.settlement_source,
|
||||||
(locale === "en-US" ? "Settlement source pending" : "结算口径待确认");
|
locale === "en-US" ? "Settlement source pending" : "结算口径待确认",
|
||||||
|
);
|
||||||
const basicAirportLabel =
|
const basicAirportLabel =
|
||||||
selectedCityItem?.airport ||
|
selectedCityItem?.airport ||
|
||||||
selectedSummary?.icao ||
|
selectedSummary?.icao ||
|
||||||
(locale === "en-US" ? "Airport pending" : "机场待确认");
|
(locale === "en-US" ? "Airport pending" : "机场待确认");
|
||||||
const heroSettlementLabel =
|
const heroSettlementLabel = normalizeObservationSourceLabel(
|
||||||
detail?.current?.settlement_source_label || basicSettlementLabel;
|
detail?.current?.settlement_source_label,
|
||||||
|
basicSettlementLabel,
|
||||||
|
);
|
||||||
const heroAirportLabel = detail?.risk?.airport || basicAirportLabel;
|
const heroAirportLabel = detail?.risk?.airport || basicAirportLabel;
|
||||||
const isSparsePanelDetail = Boolean(
|
const isSparsePanelDetail = Boolean(
|
||||||
detail &&
|
detail &&
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ import {
|
|||||||
getWeatherSummary,
|
getWeatherSummary,
|
||||||
} from "@/lib/dashboard-utils";
|
} from "@/lib/dashboard-utils";
|
||||||
import { dashboardClient } from "@/lib/dashboard-client";
|
import { dashboardClient } from "@/lib/dashboard-client";
|
||||||
|
import {
|
||||||
|
normalizeObservationSourceCode,
|
||||||
|
normalizeObservationSourceLabel,
|
||||||
|
} from "@/lib/source-labels";
|
||||||
import type { IntradayMeteorologySignal, MarketScan } from "@/lib/dashboard-types";
|
import type { IntradayMeteorologySignal, MarketScan } from "@/lib/dashboard-types";
|
||||||
|
|
||||||
function normalizeMarketValue(value?: number | null) {
|
function normalizeMarketValue(value?: number | null) {
|
||||||
@@ -1331,9 +1335,9 @@ export function FutureForecastModal() {
|
|||||||
String(intradayMeteorology.peak_window || "").trim() ||
|
String(intradayMeteorology.peak_window || "").trim() ||
|
||||||
paceView?.peakWindowText ||
|
paceView?.peakWindowText ||
|
||||||
"--";
|
"--";
|
||||||
const settlementSourceCode = String(
|
const settlementSourceCode = normalizeObservationSourceCode(
|
||||||
detail.current?.settlement_source || "",
|
detail.current?.settlement_source || "",
|
||||||
).trim().toLowerCase();
|
);
|
||||||
const settlementStationCode = String(
|
const settlementStationCode = String(
|
||||||
detail.current?.station_code || detail.risk?.icao || "",
|
detail.current?.station_code || detail.risk?.icao || "",
|
||||||
)
|
)
|
||||||
@@ -1345,19 +1349,20 @@ export function FutureForecastModal() {
|
|||||||
(locale === "en-US" ? "Anchor station" : "锚点站");
|
(locale === "en-US" ? "Anchor station" : "锚点站");
|
||||||
const airportMetarAnchor =
|
const airportMetarAnchor =
|
||||||
settlementSourceCode === "metar" ||
|
settlementSourceCode === "metar" ||
|
||||||
settlementSourceCode === "wunderground" ||
|
|
||||||
Boolean(settlementStationCode && /^[A-Z]{4}$/.test(settlementStationCode));
|
Boolean(settlementStationCode && /^[A-Z]{4}$/.test(settlementStationCode));
|
||||||
const anchorSourceLabel = airportMetarAnchor
|
const anchorSourceLabel = airportMetarAnchor
|
||||||
? settlementStationCode
|
? settlementStationCode
|
||||||
? `${settlementStationCode} METAR`
|
? `${settlementStationCode} METAR`
|
||||||
: "METAR"
|
: "METAR"
|
||||||
: detail.current?.settlement_source_label ||
|
: normalizeObservationSourceLabel(
|
||||||
detail.current?.settlement_source ||
|
detail.current?.settlement_source_label ||
|
||||||
(locale === "en-US" ? "Official observation" : "官方观测");
|
detail.current?.settlement_source,
|
||||||
|
locale === "en-US" ? "Official observation" : "官方观测",
|
||||||
|
);
|
||||||
const anchorRuleText = airportMetarAnchor
|
const anchorRuleText = airportMetarAnchor
|
||||||
? locale === "en-US"
|
? locale === "en-US"
|
||||||
? `Airport contract anchor: use the ${anchorSourceLabel} reports. Wunderground is only a history display page when present.`
|
? `Airport contract anchor: use the ${anchorSourceLabel} reports. Third-party history pages are display-only when present.`
|
||||||
: `机场合约锚点:以 ${anchorSourceLabel} 报文为准;若出现 Wunderground,它只是历史展示页面。`
|
: `机场合约锚点:以 ${anchorSourceLabel} 报文为准;第三方历史页只作为展示入口。`
|
||||||
: locale === "en-US"
|
: locale === "en-US"
|
||||||
? `Official anchor: use ${anchorSourceLabel} observations for this contract.`
|
? `Official anchor: use ${anchorSourceLabel} observations for this contract.`
|
||||||
: `官方锚点:该合约按 ${anchorSourceLabel} 观测口径判断。`;
|
: `官方锚点:该合约按 ${anchorSourceLabel} 观测口径判断。`;
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ import {
|
|||||||
getTemperatureChartData,
|
getTemperatureChartData,
|
||||||
getWeatherSummary,
|
getWeatherSummary,
|
||||||
} from "@/lib/dashboard-utils";
|
} from "@/lib/dashboard-utils";
|
||||||
|
import {
|
||||||
|
normalizeObservationSourceCode,
|
||||||
|
normalizeObservationSourceLabel,
|
||||||
|
} from "@/lib/source-labels";
|
||||||
|
|
||||||
function EmptyState({ text }: { text: string }) {
|
function EmptyState({ text }: { text: string }) {
|
||||||
return (
|
return (
|
||||||
@@ -548,22 +552,21 @@ export function HeroSummary() {
|
|||||||
const { weatherIcon, weatherText } = getWeatherSummary(data, locale);
|
const { weatherIcon, weatherText } = getWeatherSummary(data, locale);
|
||||||
const metaItems = getHeroMetaItems(data, locale);
|
const metaItems = getHeroMetaItems(data, locale);
|
||||||
const current = data.current || {};
|
const current = data.current || {};
|
||||||
const settlementSourceCode = String(current.settlement_source || "metar")
|
const settlementSourceCode = normalizeObservationSourceCode(
|
||||||
.trim()
|
current.settlement_source || "metar",
|
||||||
.toLowerCase();
|
);
|
||||||
const settlementIcao = String(
|
const settlementIcao = String(
|
||||||
current.station_code || data.risk?.icao || "",
|
current.station_code || data.risk?.icao || "",
|
||||||
)
|
)
|
||||||
.trim()
|
.trim()
|
||||||
.toUpperCase();
|
.toUpperCase();
|
||||||
const settlementSource =
|
const settlementSource =
|
||||||
settlementSourceCode === "wunderground"
|
settlementSourceCode === "metar" && settlementIcao
|
||||||
? settlementIcao
|
? `${settlementIcao} METAR`
|
||||||
? `${settlementIcao} METAR`
|
: normalizeObservationSourceLabel(
|
||||||
: "METAR"
|
current.settlement_source_label || current.settlement_source,
|
||||||
: String(current.settlement_source_label || current.settlement_source || "METAR")
|
"METAR",
|
||||||
.trim()
|
).toUpperCase();
|
||||||
.toUpperCase();
|
|
||||||
const isMax =
|
const isMax =
|
||||||
current.max_so_far != null &&
|
current.max_so_far != null &&
|
||||||
current.temp != null &&
|
current.temp != null &&
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type {
|
|||||||
CitySummary,
|
CitySummary,
|
||||||
RiskLevel,
|
RiskLevel,
|
||||||
} from "@/lib/dashboard-types";
|
} from "@/lib/dashboard-types";
|
||||||
|
import { normalizeObservationSourceLabel } from "@/lib/source-labels";
|
||||||
|
|
||||||
const loadHistoryModal = () =>
|
const loadHistoryModal = () =>
|
||||||
import("@/components/dashboard/HistoryModal").then(
|
import("@/components/dashboard/HistoryModal").then(
|
||||||
@@ -214,10 +215,12 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
|||||||
detail?.risk?.level;
|
detail?.risk?.level;
|
||||||
const deviation = summary?.deviation_monitor || detail?.deviation_monitor;
|
const deviation = summary?.deviation_monitor || detail?.deviation_monitor;
|
||||||
const observationSource =
|
const observationSource =
|
||||||
summary?.current?.settlement_source_label ||
|
normalizeObservationSourceLabel(
|
||||||
detail?.current?.settlement_source_label ||
|
summary?.current?.settlement_source_label ||
|
||||||
city.settlement_source_label ||
|
detail?.current?.settlement_source_label ||
|
||||||
city.airport;
|
city.settlement_source_label,
|
||||||
|
"METAR",
|
||||||
|
);
|
||||||
const probabilityBuckets =
|
const probabilityBuckets =
|
||||||
detail?.probabilities?.distribution ||
|
detail?.probabilities?.distribution ||
|
||||||
(detail?.local_date
|
(detail?.local_date
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { CityDetail } from "@/lib/dashboard-types";
|
import type { CityDetail } from "@/lib/dashboard-types";
|
||||||
|
import { normalizeObservationSourceLabel } from "@/lib/source-labels";
|
||||||
|
|
||||||
export type OfficialSourceLink = {
|
export type OfficialSourceLink = {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -749,7 +750,10 @@ export function getOfficialSourceLinks(detail: CityDetail): OfficialSourceLink[]
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return link;
|
return {
|
||||||
|
...link,
|
||||||
|
label: normalizeObservationSourceLabel(link.label, link.label),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
return links.filter((link) => {
|
return links.filter((link) => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
HistoryPoint,
|
HistoryPoint,
|
||||||
NearbyStation,
|
NearbyStation,
|
||||||
} from "@/lib/dashboard-types";
|
} from "@/lib/dashboard-types";
|
||||||
|
import { normalizeObservationSourceLabel } from "@/lib/source-labels";
|
||||||
|
|
||||||
const METAR_WX_MAP: Record<
|
const METAR_WX_MAP: Record<
|
||||||
string,
|
string,
|
||||||
@@ -98,7 +99,10 @@ function getObservationSourceCode(detail: CityDetail): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getObservationSourceTag(detail: CityDetail): string {
|
function getObservationSourceTag(detail: CityDetail): string {
|
||||||
const label = String(detail.current?.settlement_source_label || "")
|
const label = normalizeObservationSourceLabel(
|
||||||
|
detail.current?.settlement_source_label,
|
||||||
|
"",
|
||||||
|
)
|
||||||
.trim()
|
.trim()
|
||||||
.toUpperCase();
|
.toUpperCase();
|
||||||
if (label) return label;
|
if (label) return label;
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ const MESSAGES: Record<Locale, Record<string, string>> = {
|
|||||||
"history.loading": "正在获取历史数据...",
|
"history.loading": "正在获取历史数据...",
|
||||||
"history.error": "获取历史信息失败",
|
"history.error": "获取历史信息失败",
|
||||||
"history.empty": "近 15 天暂无该城市历史数据",
|
"history.empty": "近 15 天暂无该城市历史数据",
|
||||||
"history.hitRate": "DEB 结算胜率 (WU)",
|
"history.hitRate": "DEB 结算胜率 (METAR)",
|
||||||
"history.mae": "DEB MAE",
|
"history.mae": "DEB MAE",
|
||||||
"history.debHitRate": "DEB 结算胜率 (WU)",
|
"history.debHitRate": "DEB 结算胜率 (METAR)",
|
||||||
"history.debMae": "DEB MAE",
|
"history.debMae": "DEB MAE",
|
||||||
"history.muMae": "μ MAE",
|
"history.muMae": "μ MAE",
|
||||||
"history.bestModelMae": "最佳单模型 MAE",
|
"history.bestModelMae": "最佳单模型 MAE",
|
||||||
@@ -230,9 +230,9 @@ const MESSAGES: Record<Locale, Record<string, string>> = {
|
|||||||
"history.loading": "Loading historical data...",
|
"history.loading": "Loading historical data...",
|
||||||
"history.error": "Failed to load historical data",
|
"history.error": "Failed to load historical data",
|
||||||
"history.empty": "No historical records for this city in the last 15 days",
|
"history.empty": "No historical records for this city in the last 15 days",
|
||||||
"history.hitRate": "DEB Settlement Hit Rate (WU)",
|
"history.hitRate": "DEB Settlement Hit Rate (METAR)",
|
||||||
"history.mae": "DEB MAE",
|
"history.mae": "DEB MAE",
|
||||||
"history.debHitRate": "DEB Settlement Hit Rate (WU)",
|
"history.debHitRate": "DEB Settlement Hit Rate (METAR)",
|
||||||
"history.debMae": "DEB MAE",
|
"history.debMae": "DEB MAE",
|
||||||
"history.muMae": "μ MAE",
|
"history.muMae": "μ MAE",
|
||||||
"history.bestModelMae": "Best Single-model MAE",
|
"history.bestModelMae": "Best Single-model MAE",
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export function normalizeObservationSourceLabel(
|
||||||
|
value?: string | null,
|
||||||
|
fallback = "METAR",
|
||||||
|
) {
|
||||||
|
const raw = String(value || "").trim();
|
||||||
|
if (!raw) return fallback;
|
||||||
|
const lowered = raw.toLowerCase();
|
||||||
|
if (
|
||||||
|
lowered === "wu" ||
|
||||||
|
lowered === "wunderground"
|
||||||
|
) {
|
||||||
|
return "METAR";
|
||||||
|
}
|
||||||
|
if (lowered.includes("wunderground")) {
|
||||||
|
return raw.replace(/wunderground/gi, "METAR");
|
||||||
|
}
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeObservationSourceCode(value?: string | null) {
|
||||||
|
const code = String(value || "").trim().toLowerCase();
|
||||||
|
if (code === "wu" || code === "wunderground") return "metar";
|
||||||
|
return code;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user