Highlight precipitation windows in front trend signals

This commit is contained in:
2569718930@qq.com
2026-04-11 10:16:00 +08:00
parent 95e1fd50d2
commit faa5bc94a7
+121 -8
View File
@@ -1854,6 +1854,107 @@ export function computeFrontTrendSignal(
(max, point) => Math.max(max, Number(point.precipProb) || 0),
0,
);
const precipWindowSource =
futureSlice.length >= 2 ? futureSlice : slice;
const precipCandidates = precipWindowSource
.map((point) => ({
label: normalizeHm(point.label),
minute: pointMinutes(point),
precip: Number(point.precipProb) || 0,
}))
.filter(
(point): point is { label: string; minute: number; precip: number } =>
point.label != null && point.minute != null,
);
const precipThreshold = precipMax >= 70 ? 50 : precipMax >= 40 ? 40 : 0;
const precipWindows: Array<{
start: number;
end: number;
startLabel: string;
endLabel: string;
peak: number;
}> = [];
if (precipThreshold > 0) {
let active:
| {
start: number;
end: number;
startLabel: string;
endLabel: string;
peak: number;
}
| null = null;
for (const point of precipCandidates) {
if (point.precip >= precipThreshold) {
if (!active) {
active = {
start: point.minute,
end: point.minute,
startLabel: point.label,
endLabel: point.label,
peak: point.precip,
};
} else {
active.end = point.minute;
active.endLabel = point.label;
active.peak = Math.max(active.peak, point.precip);
}
} else if (active) {
precipWindows.push(active);
active = null;
}
}
if (active) precipWindows.push(active);
}
const primaryPrecipWindow =
precipWindows.length > 0
? [...precipWindows].sort((left, right) => {
if (right.peak !== left.peak) return right.peak - left.peak;
return (right.end - right.start) - (left.end - left.start);
})[0]
: null;
const precipWindowLabel = primaryPrecipWindow
? `${primaryPrecipWindow.startLabel}-${primaryPrecipWindow.endLabel}`
: "";
const precipPeakOverlap =
primaryPrecipWindow &&
peakWindowStartMinutes != null &&
peakWindowEndMinutes != null
? Math.max(
0,
Math.min(primaryPrecipWindow.end, peakWindowEndMinutes) -
Math.max(primaryPrecipWindow.start, peakWindowStartMinutes),
)
: 0;
const precipOverlapLevel =
primaryPrecipWindow && peakWindowStartMinutes != null && peakWindowEndMinutes != null
? precipPeakOverlap >= 120
? "high"
: precipPeakOverlap > 0
? "medium"
: "low"
: "unknown";
const precipWindowSummary = (() => {
if (!primaryPrecipWindow) {
return isEnglish(locale)
? "No concentrated model precipitation window is visible yet."
: "模型里暂时还看不出明显集中的降水窗口。";
}
const overlapText = isEnglish(locale)
? precipOverlapLevel === "high"
? "It overlaps heavily with the peak window."
: precipOverlapLevel === "medium"
? "It overlaps part of the peak window."
: "It sits mostly outside the peak window."
: precipOverlapLevel === "high"
? "与峰值窗口重叠较高。"
: precipOverlapLevel === "medium"
? "与峰值窗口部分重叠。"
: "主要落在峰值窗口之外。";
return isEnglish(locale)
? `Model precipitation window is ${precipWindowLabel}, peaking near ${Math.round(primaryPrecipWindow.peak)}%. ${overlapText}`
: `模型降水窗口在 ${precipWindowLabel},峰值约 ${Math.round(primaryPrecipWindow.peak)}%。${overlapText}`;
})();
const firstBucket = trendBucketFromDir(first.windDir);
const lastBucket = trendBucketFromDir(last.windDir);
const weatherGovPeriods = getForecastTextForDate(detail, dateStr);
@@ -1981,7 +2082,11 @@ export function computeFrontTrendSignal(
}
if (precipMax >= 50) {
parts.push("Precipitation risk is high enough to watch for cloud/rain suppression.");
parts.push(
primaryPrecipWindow
? `Model precipitation risk concentrates in ${precipWindowLabel}.`
: "Precipitation risk is high enough to watch for cloud/rain suppression.",
);
}
if (!parts.length) {
@@ -2023,7 +2128,11 @@ export function computeFrontTrendSignal(
}
if (precipMax >= 50) {
parts.push("降水概率已足以关注云雨压温。");
parts.push(
primaryPrecipWindow
? `模型降水窗口主要落在 ${precipWindowLabel}`
: "降水概率已足以关注云雨压温。",
);
}
if (!parts.length) {
@@ -2175,13 +2284,13 @@ export function computeFrontTrendSignal(
const precipNote = (() => {
if (precipMax >= 60) {
return isEnglish(locale)
? "Precipitation risk is high enough that cloud/rain suppression can materially change the peak outcome."
: "降水概率已高到足以显著改变峰值兑现结果,需要重点防压温。";
? `${precipWindowSummary} Cloud/rain suppression can materially change the peak outcome.`
: `${precipWindowSummary} 降水风险已高到足以显著改变峰值兑现结果,需要重点防压温。`;
}
if (precipMax >= 40) {
return isEnglish(locale)
? "Precipitation risk is meaningful; watch whether cloud and showers interrupt daytime heating."
: "降水概率已有存在感,需要关注云系和阵雨是否打断白天增温。";
? `${precipWindowSummary} Watch whether cloud and showers interrupt daytime heating.`
: `${precipWindowSummary} 需要关注云系和阵雨是否打断白天增温。`;
}
return isEnglish(locale)
? "Precipitation risk remains limited and is used mainly as a suppression check."
@@ -2215,10 +2324,12 @@ export function computeFrontTrendSignal(
value: `${bucketLabel(firstBucket, locale)} -> ${bucketLabel(lastBucket, locale)}`,
},
{
label: isEnglish(locale) ? "Precip probability" : "降水概率",
label: isEnglish(locale) ? "Precip window" : "降水窗口",
note: precipNote,
tone: precipMax >= 50 ? "cold" : "",
value: `${Math.round(precipMax)}%`,
value: primaryPrecipWindow
? `${precipWindowLabel} · ${Math.round(precipMax)}%`
: `${Math.round(precipMax)}%`,
},
{
label: isEnglish(locale) ? "Forecast cloud-cover delta" : "预测云量增幅",
@@ -2253,6 +2364,8 @@ export function computeFrontTrendSignal(
metrics,
upperAirMetrics,
upperAirSummary,
precipOverlapLevel,
precipWindowLabel,
precipMax,
score,
summary: combinedSummary || backendSummary || summary,