Prioritize homepage summary preloading for Asia and Europe

This commit is contained in:
2569718930@qq.com
2026-04-12 10:42:00 +08:00
parent 77b4d7b341
commit 627bb8aa06
4 changed files with 248 additions and 60 deletions
+2
View File
@@ -95,6 +95,8 @@ POLYWEATHER_MONITORING_ALERT_CHAT_IDS=
########################################
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=
NEXT_PUBLIC_WALLETCONNECT_POLYGON_RPC_URL=https://polygon-bor-rpc.publicnode.com
# Optional: disable homepage city summary preloading. Default is enabled.
NEXT_PUBLIC_POLYWEATHER_DISABLE_EAGER_SUMMARIES=false
########################################
# 7) Optional modules
+16
View File
@@ -153,3 +153,19 @@
{"city": "taipei", "timestamp": "2026-04-11T11:30:00.000Z", "date": "2026-04-11", "temp_symbol": "°C", "raw_mu": null, "raw_sigma": 0.3, "deb_prediction": 27.6, "ensemble": {"p10": null, "median": null, "p90": null}, "multi_model": {"Open-Meteo": 27.6}, "max_so_far": 33.0, "observation": {"current_temp": 27.0, "humidity": null, "wind_speed_kt": 4.0, "visibility_mi": null, "local_hour": 20.0}, "peak_status": "past", "prob_snapshot": [{"v": 33, "p": 1.0}], "shadow_prob_snapshot": [], "probability_engine": "legacy", "probability_mode": "legacy", "calibration_version": null, "calibration_source": null, "calibrated_mu": null, "calibrated_sigma": null}
{"city": "taipei", "timestamp": "2026-04-11T11:30:00.000Z", "date": "2026-04-11", "temp_symbol": "°C", "raw_mu": null, "raw_sigma": 0.10938964843750025, "deb_prediction": 29.5, "ensemble": {"p10": 28.9, "median": 29.4, "p90": 29.8}, "multi_model": {"Open-Meteo": 27.6, "ECMWF": 31.0, "GFS": 32.4, "ICON": 27.6, "GEM": 29.7, "JMA": 28.9}, "max_so_far": 33.0, "observation": {"current_temp": 27.0, "humidity": null, "wind_speed_kt": 4.0, "visibility_mi": null, "local_hour": 20.0}, "peak_status": "past", "prob_snapshot": [{"v": 33, "p": 1.0}], "shadow_prob_snapshot": [], "probability_engine": "legacy", "probability_mode": "legacy", "calibration_version": null, "calibration_source": null, "calibrated_mu": null, "calibrated_sigma": null}
{"city": "taipei", "timestamp": "2026-04-11T19:50:00+08:00", "date": "2026-04-11", "temp_symbol": "°C", "raw_mu": null, "raw_sigma": 0.10938964843749983, "deb_prediction": 30.3, "ensemble": {"p10": 28.8, "median": 29.3, "p90": 29.7}, "multi_model": {"Open-Meteo": 29.7, "ECMWF": 30.9, "GFS": 32.4, "ICON": 29.7, "GEM": 29.6, "JMA": 29.3}, "max_so_far": 32.1, "observation": {"current_temp": 27.6, "humidity": 73.0, "wind_speed_kt": 2.9, "visibility_mi": null, "local_hour": 20.1}, "peak_status": "past", "prob_snapshot": [{"v": 32, "p": 1.0}], "shadow_prob_snapshot": [], "probability_engine": "legacy", "probability_mode": "legacy", "calibration_version": null, "calibration_source": null, "calibrated_mu": null, "calibrated_sigma": null}
{"city": "ankara", "timestamp": "2026-04-12T02:00:00.000Z", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 8.5, "raw_sigma": 1.0, "deb_prediction": 8.5, "ensemble": {"p10": null, "median": null, "p90": null}, "multi_model": {"Open-Meteo": 8.5}, "max_so_far": 2.0, "observation": {"current_temp": 0.0, "humidity": null, "wind_speed_kt": 2.0, "visibility_mi": null, "local_hour": 5.166666666666667}, "peak_status": "before", "prob_snapshot": [{"v": 8, "p": 0.35}, {"v": 9, "p": 0.35}, {"v": 10, "p": 0.139}, {"v": 7, "p": 0.139}], "shadow_prob_snapshot": [{"v": 9, "p": 0.359}, {"v": 8, "p": 0.359}, {"v": 7, "p": 0.132}, {"v": 10, "p": 0.132}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 8.5, "calibrated_sigma": 0.955}
{"city": "ankara", "timestamp": "2026-04-12T02:00:00.000Z", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 8.93, "raw_sigma": 0.3515625000000001, "deb_prediction": 9.1, "ensemble": {"p10": 8.5, "median": 9.0, "p90": 9.4}, "multi_model": {"Open-Meteo": 8.5, "ECMWF": 7.9, "GFS": 10.0, "ICON": 8.5, "GEM": 8.9, "JMA": 9.2}, "max_so_far": 2.0, "observation": {"current_temp": 0.0, "humidity": null, "wind_speed_kt": 2.0, "visibility_mi": null, "local_hour": 5.166666666666667}, "peak_status": "before", "prob_snapshot": [{"v": 9, "p": 0.837}, {"v": 8, "p": 0.111}, {"v": 10, "p": 0.052}], "shadow_prob_snapshot": [{"v": 9, "p": 0.851}, {"v": 8, "p": 0.102}, {"v": 10, "p": 0.046}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 8.93, "calibrated_sigma": 0.3389843750000001}
{"city": "istanbul", "timestamp": "2026-04-12T04:50:00+03:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 11.7, "raw_sigma": 1.0, "deb_prediction": 11.7, "ensemble": {"p10": null, "median": null, "p90": null}, "multi_model": {"Open-Meteo": 11.7}, "max_so_far": 8.0, "observation": {"current_temp": 7.0, "humidity": 52.7, "wind_speed_kt": 1.0, "visibility_mi": null, "local_hour": 5.183333333333334}, "peak_status": "before", "prob_snapshot": [{"v": 12, "p": 0.374}, {"v": 11, "p": 0.311}, {"v": 13, "p": 0.179}, {"v": 10, "p": 0.103}], "shadow_prob_snapshot": [{"v": 12, "p": 0.387}, {"v": 11, "p": 0.316}, {"v": 13, "p": 0.174}, {"v": 10, "p": 0.095}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 11.7, "calibrated_sigma": 0.955}
{"city": "istanbul", "timestamp": "2026-04-12T04:50:00+03:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 11.52, "raw_sigma": 0.390625, "deb_prediction": 11.5, "ensemble": {"p10": 10.5, "median": 11.1, "p90": 11.5}, "multi_model": {"Open-Meteo": 11.7, "ECMWF": 11.9, "GFS": 11.1, "ICON": 11.7, "GEM": 12.8, "JMA": 10.1}, "max_so_far": 8.0, "observation": {"current_temp": 7.0, "humidity": 52.7, "wind_speed_kt": 1.0, "visibility_mi": null, "local_hour": 5.183333333333334}, "peak_status": "before", "prob_snapshot": [{"v": 12, "p": 0.52}, {"v": 11, "p": 0.48}], "shadow_prob_snapshot": [{"v": 12, "p": 0.521}, {"v": 11, "p": 0.479}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 11.52, "calibrated_sigma": 0.37609375}
{"city": "hong kong", "timestamp": "2026-04-12T10:00:00+08:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 28.999999999999996, "raw_sigma": 0.75, "deb_prediction": 28.2, "ensemble": {"p10": null, "median": null, "p90": null}, "multi_model": {"Open-Meteo": 27.5, "HKO(港天文)": 29.0}, "max_so_far": 27.2, "observation": {"current_temp": 27.2, "humidity": 77.0, "wind_speed_kt": 5.4, "visibility_mi": null, "local_hour": 10.2}, "peak_status": "before", "prob_snapshot": [{"v": 28, "p": 0.412}, {"v": 29, "p": 0.412}, {"v": 27, "p": 0.088}, {"v": 30, "p": 0.088}], "shadow_prob_snapshot": [{"v": 28, "p": 0.413}, {"v": 29, "p": 0.413}, {"v": 27, "p": 0.087}, {"v": 30, "p": 0.087}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 28.999999999999996, "calibrated_sigma": 0.7478013193453208}
{"city": "hong kong", "timestamp": "2026-04-12T10:00:00+08:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 27.71, "raw_sigma": 0.30937500000000073, "deb_prediction": 27.6, "ensemble": {"p10": 28.1, "median": 28.2, "p90": 28.5}, "multi_model": {"Open-Meteo": 27.5, "HKO(港天文)": 29.0, "ECMWF": 27.0, "GFS": 27.2, "ICON": 27.5, "GEM": 27.1, "JMA": 27.6}, "max_so_far": 27.2, "observation": {"current_temp": 27.2, "humidity": 77.0, "wind_speed_kt": 5.4, "visibility_mi": null, "local_hour": 10.2}, "peak_status": "before", "prob_snapshot": [{"v": 27, "p": 0.824}, {"v": 28, "p": 0.176}], "shadow_prob_snapshot": [{"v": 27, "p": 0.814}, {"v": 28, "p": 0.186}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 27.71, "calibrated_sigma": 0.32103944874938267}
{"city": "taipei", "timestamp": "2026-04-12T10:00:00+08:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 30.9, "raw_sigma": 2.6999999999999993, "deb_prediction": 30.9, "ensemble": {"p10": null, "median": null, "p90": null}, "multi_model": {"Open-Meteo": 30.9}, "max_so_far": 29.2, "observation": {"current_temp": 29.2, "humidity": 64.0, "wind_speed_kt": 1.2, "visibility_mi": null, "local_hour": 10.2}, "peak_status": "before", "prob_snapshot": [{"v": 31, "p": 0.182}, {"v": 30, "p": 0.173}, {"v": 32, "p": 0.168}, {"v": 29, "p": 0.143}], "shadow_prob_snapshot": [{"v": 31, "p": 0.185}, {"v": 30, "p": 0.175}, {"v": 32, "p": 0.17}, {"v": 29, "p": 0.143}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 30.9, "calibrated_sigma": 2.6381596739291915}
{"city": "taipei", "timestamp": "2026-04-12T10:00:00+08:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 30.689999999999998, "raw_sigma": 2.8574999999999995, "deb_prediction": 30.9, "ensemble": {"p10": 29.7, "median": 30.2, "p90": 30.6}, "multi_model": {"Open-Meteo": 30.9, "ECMWF": 30.9, "GFS": 32.9, "ICON": 30.9, "GEM": 28.9, "JMA": 30.8}, "max_so_far": 29.2, "observation": {"current_temp": 29.2, "humidity": 64.0, "wind_speed_kt": 1.2, "visibility_mi": null, "local_hour": 10.2}, "peak_status": "before", "prob_snapshot": [{"v": 31, "p": 0.179}, {"v": 30, "p": 0.175}, {"v": 32, "p": 0.163}, {"v": 29, "p": 0.152}], "shadow_prob_snapshot": [{"v": 31, "p": 0.183}, {"v": 30, "p": 0.178}, {"v": 32, "p": 0.165}, {"v": 29, "p": 0.153}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 30.689999999999998, "calibrated_sigma": 2.778122088606427}
{"city": "ankara", "timestamp": "2026-04-12T02:00:00.000Z", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 8.5, "raw_sigma": 1.0, "deb_prediction": 8.5, "ensemble": {"p10": null, "median": null, "p90": null}, "multi_model": {"Open-Meteo": 8.5}, "max_so_far": 2.0, "observation": {"current_temp": 0.0, "humidity": null, "wind_speed_kt": 2.0, "visibility_mi": null, "local_hour": 5.216666666666667}, "peak_status": "before", "prob_snapshot": [{"v": 8, "p": 0.35}, {"v": 9, "p": 0.35}, {"v": 10, "p": 0.139}, {"v": 7, "p": 0.139}], "shadow_prob_snapshot": [{"v": 9, "p": 0.359}, {"v": 8, "p": 0.359}, {"v": 7, "p": 0.132}, {"v": 10, "p": 0.132}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 8.5, "calibrated_sigma": 0.955}
{"city": "istanbul", "timestamp": "2026-04-12T04:50:00+03:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 11.7, "raw_sigma": 1.0, "deb_prediction": 11.7, "ensemble": {"p10": null, "median": null, "p90": null}, "multi_model": {"Open-Meteo": 11.7}, "max_so_far": 8.0, "observation": {"current_temp": 7.0, "humidity": 52.7, "wind_speed_kt": 1.0, "visibility_mi": null, "local_hour": 5.233333333333333}, "peak_status": "before", "prob_snapshot": [{"v": 12, "p": 0.374}, {"v": 11, "p": 0.311}, {"v": 13, "p": 0.179}, {"v": 10, "p": 0.103}], "shadow_prob_snapshot": [{"v": 12, "p": 0.387}, {"v": 11, "p": 0.316}, {"v": 13, "p": 0.174}, {"v": 10, "p": 0.095}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 11.7, "calibrated_sigma": 0.955}
{"city": "ankara", "timestamp": "2026-04-12T02:00:00.000Z", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 8.93, "raw_sigma": 0.3515625000000001, "deb_prediction": 9.1, "ensemble": {"p10": 8.5, "median": 9.0, "p90": 9.4}, "multi_model": {"Open-Meteo": 8.5, "ECMWF": 7.9, "GFS": 10.0, "ICON": 8.5, "GEM": 8.9, "JMA": 9.2}, "max_so_far": 2.0, "observation": {"current_temp": 0.0, "humidity": null, "wind_speed_kt": 2.0, "visibility_mi": null, "local_hour": 5.25}, "peak_status": "before", "prob_snapshot": [{"v": 9, "p": 0.837}, {"v": 8, "p": 0.111}, {"v": 10, "p": 0.052}], "shadow_prob_snapshot": [{"v": 9, "p": 0.851}, {"v": 8, "p": 0.102}, {"v": 10, "p": 0.046}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 8.93, "calibrated_sigma": 0.3389843750000001}
{"city": "istanbul", "timestamp": "2026-04-12T04:50:00+03:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 11.52, "raw_sigma": 0.390625, "deb_prediction": 11.5, "ensemble": {"p10": 10.5, "median": 11.1, "p90": 11.5}, "multi_model": {"Open-Meteo": 11.7, "ECMWF": 11.9, "GFS": 11.1, "ICON": 11.7, "GEM": 12.8, "JMA": 10.1}, "max_so_far": 8.0, "observation": {"current_temp": 7.0, "humidity": 52.7, "wind_speed_kt": 1.0, "visibility_mi": null, "local_hour": 5.25}, "peak_status": "before", "prob_snapshot": [{"v": 12, "p": 0.52}, {"v": 11, "p": 0.48}], "shadow_prob_snapshot": [{"v": 12, "p": 0.521}, {"v": 11, "p": 0.479}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 11.52, "calibrated_sigma": 0.37609375}
{"city": "taipei", "timestamp": "2026-04-12T10:00:00+08:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 30.9, "raw_sigma": 2.6999999999999993, "deb_prediction": 30.9, "ensemble": {"p10": null, "median": null, "p90": null}, "multi_model": {"Open-Meteo": 30.9}, "max_so_far": 29.2, "observation": {"current_temp": 29.2, "humidity": 64.0, "wind_speed_kt": 1.2, "visibility_mi": null, "local_hour": 10.283333333333333}, "peak_status": "before", "prob_snapshot": [{"v": 31, "p": 0.182}, {"v": 30, "p": 0.173}, {"v": 32, "p": 0.168}, {"v": 29, "p": 0.143}], "shadow_prob_snapshot": [{"v": 31, "p": 0.185}, {"v": 30, "p": 0.175}, {"v": 32, "p": 0.17}, {"v": 29, "p": 0.143}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 30.9, "calibrated_sigma": 2.6381596739291915}
{"city": "hong kong", "timestamp": "2026-04-12T10:00:00+08:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 28.999999999999996, "raw_sigma": 0.75, "deb_prediction": 28.2, "ensemble": {"p10": null, "median": null, "p90": null}, "multi_model": {"Open-Meteo": 27.5, "HKO(港天文)": 29.0}, "max_so_far": 27.2, "observation": {"current_temp": 27.2, "humidity": 77.0, "wind_speed_kt": 5.4, "visibility_mi": null, "local_hour": 10.283333333333333}, "peak_status": "before", "prob_snapshot": [{"v": 28, "p": 0.412}, {"v": 29, "p": 0.412}, {"v": 27, "p": 0.088}, {"v": 30, "p": 0.088}], "shadow_prob_snapshot": [{"v": 28, "p": 0.413}, {"v": 29, "p": 0.413}, {"v": 27, "p": 0.087}, {"v": 30, "p": 0.087}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 28.999999999999996, "calibrated_sigma": 0.7478013193453208}
{"city": "taipei", "timestamp": "2026-04-12T10:00:00+08:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 30.689999999999998, "raw_sigma": 2.8574999999999995, "deb_prediction": 30.9, "ensemble": {"p10": 29.7, "median": 30.2, "p90": 30.6}, "multi_model": {"Open-Meteo": 30.9, "ECMWF": 30.9, "GFS": 32.9, "ICON": 30.9, "GEM": 28.9, "JMA": 30.8}, "max_so_far": 29.2, "observation": {"current_temp": 29.2, "humidity": 64.0, "wind_speed_kt": 1.2, "visibility_mi": null, "local_hour": 10.283333333333333}, "peak_status": "before", "prob_snapshot": [{"v": 31, "p": 0.179}, {"v": 30, "p": 0.175}, {"v": 32, "p": 0.163}, {"v": 29, "p": 0.152}], "shadow_prob_snapshot": [{"v": 31, "p": 0.183}, {"v": 30, "p": 0.178}, {"v": 32, "p": 0.165}, {"v": 29, "p": 0.153}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 30.689999999999998, "calibrated_sigma": 2.778122088606427}
{"city": "hong kong", "timestamp": "2026-04-12T10:00:00+08:00", "date": "2026-04-12", "temp_symbol": "°C", "raw_mu": 27.71, "raw_sigma": 0.30937500000000073, "deb_prediction": 27.6, "ensemble": {"p10": 28.1, "median": 28.2, "p90": 28.5}, "multi_model": {"Open-Meteo": 27.5, "HKO(港天文)": 29.0, "ECMWF": 27.0, "GFS": 27.2, "ICON": 27.5, "GEM": 27.1, "JMA": 27.6}, "max_so_far": 27.2, "observation": {"current_temp": 27.2, "humidity": 77.0, "wind_speed_kt": 5.4, "visibility_mi": null, "local_hour": 10.283333333333333}, "peak_status": "before", "prob_snapshot": [{"v": 27, "p": 0.824}, {"v": 28, "p": 0.176}], "shadow_prob_snapshot": [{"v": 27, "p": 0.814}, {"v": 28, "p": 0.186}], "probability_engine": "legacy", "probability_mode": "emos_shadow", "calibration_version": "emos-20260402162744", "calibration_source": "artifacts\\probability_calibration\\default.json", "calibrated_mu": 27.71, "calibrated_sigma": 0.32103944874938267}
+192 -54
View File
@@ -92,8 +92,38 @@ function getMarketScanCacheKey(cityName: string, targetDate?: string | null) {
const SELECTED_CITY_STORAGE_KEY = "polyWeather_selected_city_v1";
const BACKGROUND_SUMMARY_REFRESH_MS = 30_000;
const EAGER_CITY_SUMMARIES_ENABLED =
process.env.NEXT_PUBLIC_POLYWEATHER_EAGER_CITY_SUMMARIES === "true";
const EAGER_CITY_SUMMARIES_DISABLED =
process.env.NEXT_PUBLIC_POLYWEATHER_DISABLE_EAGER_SUMMARIES === "true";
const EAGER_SUMMARY_PRIORITY_CONCURRENCY = 3;
const EAGER_SUMMARY_BACKGROUND_CONCURRENCY = 2;
const EAGER_SUMMARY_BACKGROUND_IDLE_TIMEOUT_MS = 1200;
const EAGER_SUMMARY_PRIORITY_CITY_ORDER = [
"hong kong",
"taipei",
"shanghai",
"beijing",
"wuhan",
"shenzhen",
"chengdu",
"chongqing",
"seoul",
"busan",
"tokyo",
"singapore",
"kuala lumpur",
"jakarta",
"tel aviv",
"jeddah",
"istanbul",
"moscow",
"helsinki",
"warsaw",
"amsterdam",
"london",
"paris",
"milan",
"madrid",
] as const;
type CityDetailDepth = "panel" | "full";
function countAvailableModels(
@@ -146,6 +176,57 @@ function detailSatisfiesDepth(
return normalizeDetailDepth(detail) === "full";
}
function getStoredSelectedCityName(cities: CityListItem[]) {
if (typeof window === "undefined") return null;
const stored = String(
window.localStorage.getItem(SELECTED_CITY_STORAGE_KEY) || "",
)
.trim()
.toLowerCase();
if (!stored) return null;
if (!cities.some((city) => city.name === stored)) return null;
return stored;
}
function getPriorityPreloadCities(cities: CityListItem[]) {
const citySet = new Set(cities.map((city) => city.name));
return EAGER_SUMMARY_PRIORITY_CITY_ORDER.filter((cityName) =>
citySet.has(cityName)
);
}
function scheduleWhenBrowserIdle(callback: () => void) {
if (typeof window === "undefined") {
callback();
return () => {};
}
const browserWindow = window as Window & {
requestIdleCallback?: (
cb: IdleRequestCallback,
options?: IdleRequestOptions,
) => number;
cancelIdleCallback?: (handle: number) => void;
};
if (typeof browserWindow.requestIdleCallback === "function") {
const handle = browserWindow.requestIdleCallback(
() => callback(),
{ timeout: EAGER_SUMMARY_BACKGROUND_IDLE_TIMEOUT_MS },
);
return () => {
if (typeof browserWindow.cancelIdleCallback === "function") {
browserWindow.cancelIdleCallback(handle);
}
};
}
const timeoutId = window.setTimeout(callback, 400);
return () => {
window.clearTimeout(timeoutId);
};
}
export function DashboardStoreProvider({
children,
}: {
@@ -188,6 +269,9 @@ export function DashboardStoreProvider({
const hydratedSelectionRef = useRef(false);
const hydratedProCacheRef = useRef(false);
const backgroundSummaryCheckAtRef = useRef<Record<string, number>>({});
const summaryInflightByCityRef = useRef<Record<string, Promise<CitySummary>>>(
{},
);
const citySummariesRef = useRef<Record<string, CitySummary>>({});
const selectedCityRef = useRef<string | null>(null);
const selectedDetail =
@@ -529,6 +613,54 @@ export function DashboardStoreProvider({
void refreshProAccess();
}, []);
const ensureCitySummary = async (cityName: string, force = false) => {
const existing = citySummariesRef.current[cityName];
if (!force && existing) {
return existing;
}
const inflight = summaryInflightByCityRef.current[cityName];
if (inflight) {
const settled = await inflight;
if (!force) {
return settled;
}
}
const request = dashboardClient
.getCitySummary(cityName, { force })
.then((summary) => {
setCitySummariesByName((current) => {
const currentSummary = current[cityName];
const currentRevision = getCityRevision(currentSummary);
const nextRevision = getCityRevision(summary);
if (
currentSummary &&
currentRevision &&
nextRevision &&
currentRevision === nextRevision
) {
return current;
}
const next = {
...current,
[cityName]: summary,
};
citySummariesRef.current = next;
return next;
});
return summary;
})
.finally(() => {
if (summaryInflightByCityRef.current[cityName] === request) {
delete summaryInflightByCityRef.current[cityName];
}
});
summaryInflightByCityRef.current[cityName] = request;
return request;
};
useEffect(() => {
if (proAccess.loading || !proAccess.authenticated || !proAccess.userId) {
return;
@@ -563,64 +695,72 @@ export function DashboardStoreProvider({
]);
useEffect(() => {
if (!EAGER_CITY_SUMMARIES_ENABLED) return;
if (EAGER_CITY_SUMMARIES_DISABLED) return;
if (!cities.length) return;
if (loadingState.refresh) return;
const queue = cities
.map((city) => city.name)
.filter((cityName) => !citySummariesRef.current[cityName]);
if (!queue.length) return;
let active = true;
const concurrency = 4;
let cursor = 0;
const worker = async () => {
while (active && cursor < queue.length) {
const cityName = queue[cursor];
cursor += 1;
if (citySummariesRef.current[cityName]) continue;
try {
const summary = await dashboardClient.getCitySummary(cityName);
if (!active) return;
setCitySummariesByName((current) => {
if (current[cityName]) return current;
const next = {
...current,
[cityName]: summary,
};
citySummariesRef.current = next;
return next;
});
} catch {}
const priorityQueue: string[] = [];
const backgroundQueue: string[] = [];
const queued = new Set<string>();
const enqueue = (cityName: string | null, queue: string[]) => {
if (!cityName || queued.has(cityName) || citySummariesRef.current[cityName]) {
return;
}
queued.add(cityName);
queue.push(cityName);
};
void Promise.all(
Array.from({ length: Math.min(concurrency, queue.length) }, () =>
worker(),
),
);
enqueue(getStoredSelectedCityName(cities), priorityQueue);
getPriorityPreloadCities(cities).forEach((cityName) => {
enqueue(cityName, priorityQueue);
});
cities.forEach((city) => {
enqueue(city.name, backgroundQueue);
});
if (!priorityQueue.length && !backgroundQueue.length) return;
let active = true;
let cancelIdleSchedule = () => {};
const runQueue = async (queue: string[], concurrency: number) => {
if (!queue.length) return;
let cursor = 0;
const worker = async () => {
while (active && cursor < queue.length) {
const cityName = queue[cursor];
cursor += 1;
if (citySummariesRef.current[cityName]) continue;
try {
await ensureCitySummary(cityName);
} catch {}
}
};
await Promise.all(
Array.from({ length: Math.min(concurrency, queue.length) }, () =>
worker()
),
);
};
void (async () => {
await runQueue(priorityQueue, EAGER_SUMMARY_PRIORITY_CONCURRENCY);
if (!active) return;
cancelIdleSchedule = scheduleWhenBrowserIdle(() => {
if (!active) return;
void runQueue(
backgroundQueue,
EAGER_SUMMARY_BACKGROUND_CONCURRENCY,
);
});
})();
return () => {
active = false;
cancelIdleSchedule();
};
}, [cities]);
const ensureCitySummary = async (cityName: string, force = false) => {
const existing = citySummariesRef.current[cityName];
if (!force && existing) {
return existing;
}
const summary = await dashboardClient.getCitySummary(cityName, { force });
setCitySummariesByName((current) => ({
...current,
[cityName]: summary,
}));
return summary;
};
}, [cities, loadingState.refresh]);
const selectCity = async (cityName: string) => {
setSelectedCity(cityName);
@@ -753,9 +893,7 @@ export function DashboardStoreProvider({
});
setSelectedForecastDate(detail.local_date);
} else {
const summary = await dashboardClient.getCitySummary(selectedCity, {
force: true,
});
const summary = await ensureCitySummary(selectedCity, true);
setCitySummariesByName((current) => ({
...current,
[selectedCity]: summary,
+38 -6
View File
@@ -84,6 +84,36 @@ def _analysis_ttl_for_city(city: str) -> int:
return CACHE_TTL_ANKARA if city.lower() in TURKISH_MGM_CITIES else CACHE_TTL
def _analysis_cache_key(city: str, detail_mode: str = "full") -> str:
normalized_mode = "panel" if str(detail_mode or "").strip().lower() == "panel" else "full"
return f"{city}::{normalized_mode}"
def _get_cached_analysis(
city: str,
ttl: int,
detail_modes: tuple[str, ...] = ("panel", "full"),
) -> Optional[Dict[str, Any]]:
now_ts = _time.time()
freshest_payload: Optional[Dict[str, Any]] = None
freshest_ts = 0.0
for detail_mode in detail_modes:
cached = _cache.get(_analysis_cache_key(city, detail_mode))
if not cached:
continue
cached_ts = float(cached.get("t", 0))
payload = cached.get("d")
if (
cached_ts
and now_ts - cached_ts < ttl
and isinstance(payload, dict)
and cached_ts >= freshest_ts
):
freshest_payload = payload
freshest_ts = cached_ts
return freshest_payload
def _get_cached_summary(city: str, ttl: int) -> Optional[Dict[str, Any]]:
now_ts = _time.time()
with _SUMMARY_CACHE_LOCK:
@@ -1055,9 +1085,11 @@ def _analyze(
"""Fetch, analyse, and return structured weather data for one city."""
# Check cache
ttl = CACHE_TTL_ANKARA if city.lower() in TURKISH_MGM_CITIES else CACHE_TTL
normalized_detail_mode = "panel" if str(detail_mode or "full").strip().lower() == "panel" else "full"
cache_key = _analysis_cache_key(city, normalized_detail_mode)
if not force_refresh:
cached = _cache.get(city)
cached = _cache.get(cache_key)
if cached and _time.time() - cached["t"] < ttl:
if include_llm_commentary:
cached_payload = cached["d"]
@@ -1081,7 +1113,7 @@ def _analyze(
)
# ── 1. Fetch raw data ──
is_panel_mode = str(detail_mode or "full").strip().lower() == "panel"
is_panel_mode = normalized_detail_mode == "panel"
raw = _weather.fetch_all_sources(
city,
@@ -1875,7 +1907,7 @@ def _analyze(
result,
)
_cache[city] = {"t": _time.time(), "d": result}
_cache[cache_key] = {"t": _time.time(), "d": result}
return result
@@ -1891,9 +1923,9 @@ def _analyze_summary(city: str, force_refresh: bool = False) -> Dict[str, Any]:
ttl = _analysis_ttl_for_city(city)
if not force_refresh:
cached_detail = _cache.get(city)
if cached_detail and _time.time() - cached_detail["t"] < ttl:
return cached_detail["d"]
cached_detail = _get_cached_analysis(city, ttl)
if cached_detail:
return cached_detail
cached_summary = _get_cached_summary(city, ttl)
if cached_summary:
return cached_summary