Refresh sparse model detail caches in forecast views
This commit is contained in:
@@ -609,6 +609,7 @@ export function FutureForecastModal() {
|
||||
const max = Math.max(...values);
|
||||
const spread = max - min;
|
||||
return {
|
||||
count: values.length,
|
||||
max,
|
||||
min,
|
||||
spread,
|
||||
@@ -790,6 +791,15 @@ export function FutureForecastModal() {
|
||||
? "Model spread is unavailable right now."
|
||||
: "当前拿不到可用的模型分歧。";
|
||||
}
|
||||
const modelEntries = Object.entries(modelView?.models || {}).filter(
|
||||
([, value]) => value !== null && value !== undefined && Number.isFinite(Number(value)),
|
||||
);
|
||||
if (modelEntries.length === 1) {
|
||||
const [singleModelName, singleModelValue] = modelEntries[0];
|
||||
return locale === "en-US"
|
||||
? `Only ${singleModelName} is available right now at ${Number(singleModelValue).toFixed(1)}${detail.temp_symbol}; multi-model spread is temporarily unavailable.`
|
||||
: `当前只收到 ${singleModelName} ${Number(singleModelValue).toFixed(1)}${detail.temp_symbol},其他多模型暂未回传,所以这里先不判断模型分歧。`;
|
||||
}
|
||||
return locale === "en-US"
|
||||
? `Model range runs from ${modelSpreadView.min.toFixed(1)}${detail.temp_symbol} to ${modelSpreadView.max.toFixed(1)}${detail.temp_symbol}; spread ${modelSpreadView.spread.toFixed(1)}${detail.temp_symbol}.`
|
||||
: `当前模型区间在 ${modelSpreadView.min.toFixed(1)}${detail.temp_symbol} 到 ${modelSpreadView.max.toFixed(1)}${detail.temp_symbol},分歧 ${modelSpreadView.spread.toFixed(1)}${detail.temp_symbol}。`;
|
||||
|
||||
@@ -609,6 +609,7 @@ export function ModelForecast({
|
||||
([, value]) =>
|
||||
value !== null && value !== undefined && Number.isFinite(Number(value)),
|
||||
);
|
||||
const hasSingleModelOnly = modelEntries.length === 1;
|
||||
|
||||
// 如果没有任何数值,给出提示
|
||||
if (modelEntries.length === 0) {
|
||||
@@ -637,6 +638,19 @@ export function ModelForecast({
|
||||
<section className="models-section">
|
||||
{!hideTitle && <h3>{t("section.models")}</h3>}
|
||||
<div className="model-bars">
|
||||
{hasSingleModelOnly && (
|
||||
<div
|
||||
style={{
|
||||
color: "var(--text-secondary)",
|
||||
fontSize: "11px",
|
||||
marginBottom: "8px",
|
||||
}}
|
||||
>
|
||||
{locale === "en-US"
|
||||
? "Single-model fallback: waiting for the rest of the model cluster."
|
||||
: "当前处于单模型回退,其他模型结果还没回传。"}
|
||||
</div>
|
||||
)}
|
||||
{modelEntries
|
||||
.sort((a, b) => Number(b[1] || 0) - Number(a[1] || 0))
|
||||
.map(([name, value]) => {
|
||||
|
||||
@@ -91,6 +91,28 @@ const BACKGROUND_SUMMARY_REFRESH_MS = 30_000;
|
||||
const EAGER_CITY_SUMMARIES_ENABLED =
|
||||
process.env.NEXT_PUBLIC_POLYWEATHER_EAGER_CITY_SUMMARIES === "true";
|
||||
|
||||
function countAvailableModels(
|
||||
detail?: CityDetail | null,
|
||||
targetDate?: string | null,
|
||||
): number {
|
||||
if (!detail) return 0;
|
||||
const date = String(targetDate || detail.local_date || "").trim();
|
||||
const dailyModels = detail.multi_model_daily?.[date]?.models;
|
||||
const models = dailyModels && typeof dailyModels === "object"
|
||||
? dailyModels
|
||||
: detail.multi_model || {};
|
||||
return Object.values(models).filter((value) =>
|
||||
Number.isFinite(Number(value)),
|
||||
).length;
|
||||
}
|
||||
|
||||
function hasSparseModelCoverage(
|
||||
detail?: CityDetail | null,
|
||||
targetDate?: string | null,
|
||||
): boolean {
|
||||
return countAvailableModels(detail, targetDate) <= 1;
|
||||
}
|
||||
|
||||
export function DashboardStoreProvider({
|
||||
children,
|
||||
}: {
|
||||
@@ -598,6 +620,12 @@ export function DashboardStoreProvider({
|
||||
mapStopMotionRef.current();
|
||||
setFutureModalDate(dateStr);
|
||||
if (!selectedCity || !proAccess.subscriptionActive) return;
|
||||
const cachedDetail = cityDetailsByName[selectedCity];
|
||||
const needsModelRefresh =
|
||||
!forceRefresh && hasSparseModelCoverage(cachedDetail, dateStr);
|
||||
if (needsModelRefresh) {
|
||||
void ensureCityDetail(selectedCity, true).catch(() => {});
|
||||
}
|
||||
const cacheKey = getMarketScanCacheKey(selectedCity, dateStr);
|
||||
setLoadingState((current) => ({ ...current, marketScan: true }));
|
||||
void ensureCityMarketScan(
|
||||
@@ -624,6 +652,8 @@ export function DashboardStoreProvider({
|
||||
setFutureModalDate(cachedDetail.local_date);
|
||||
}
|
||||
if (!proAccess.subscriptionActive) return;
|
||||
const needsModelRefresh =
|
||||
!forceRefresh && hasSparseModelCoverage(cachedDetail, cachedDetail?.local_date);
|
||||
|
||||
setLoadingState((current) => ({
|
||||
...current,
|
||||
@@ -634,7 +664,7 @@ export function DashboardStoreProvider({
|
||||
try {
|
||||
const detail = await ensureCityDetail(
|
||||
selectedCity,
|
||||
Boolean(forceRefresh),
|
||||
Boolean(forceRefresh || needsModelRefresh),
|
||||
);
|
||||
setSelectedForecastDate(detail.local_date);
|
||||
setFutureModalDate(detail.local_date);
|
||||
|
||||
@@ -52,11 +52,24 @@ function normalizeRevisionPart(value: unknown) {
|
||||
|
||||
export function getCityRevision(source?: CityDetail | CitySummary | null) {
|
||||
if (!source) return "";
|
||||
const modelDaily =
|
||||
"multi_model_daily" in source && source.multi_model_daily
|
||||
? source.multi_model_daily?.[source.local_date || ""]
|
||||
: null;
|
||||
const modelFootprint = modelDaily?.models || ("multi_model" in source ? source.multi_model : null);
|
||||
return [
|
||||
normalizeRevisionPart(source.updated_at),
|
||||
normalizeRevisionPart(source.current?.obs_time),
|
||||
normalizeRevisionPart(source.current?.temp),
|
||||
normalizeRevisionPart(source.deb?.prediction),
|
||||
normalizeRevisionPart(
|
||||
modelFootprint && typeof modelFootprint === "object"
|
||||
? Object.keys(modelFootprint)
|
||||
.sort()
|
||||
.map((key) => `${key}:${normalizeRevisionPart(modelFootprint[key])}`)
|
||||
.join("|")
|
||||
: "",
|
||||
),
|
||||
].join("|");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user