Prioritize homepage summary preloading for Asia and Europe
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user