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_PROJECT_ID=
|
||||||
NEXT_PUBLIC_WALLETCONNECT_POLYGON_RPC_URL=https://polygon-bor-rpc.publicnode.com
|
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
|
# 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.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-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": "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 SELECTED_CITY_STORAGE_KEY = "polyWeather_selected_city_v1";
|
||||||
const BACKGROUND_SUMMARY_REFRESH_MS = 30_000;
|
const BACKGROUND_SUMMARY_REFRESH_MS = 30_000;
|
||||||
const EAGER_CITY_SUMMARIES_ENABLED =
|
const EAGER_CITY_SUMMARIES_DISABLED =
|
||||||
process.env.NEXT_PUBLIC_POLYWEATHER_EAGER_CITY_SUMMARIES === "true";
|
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";
|
type CityDetailDepth = "panel" | "full";
|
||||||
|
|
||||||
function countAvailableModels(
|
function countAvailableModels(
|
||||||
@@ -146,6 +176,57 @@ function detailSatisfiesDepth(
|
|||||||
return normalizeDetailDepth(detail) === "full";
|
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({
|
export function DashboardStoreProvider({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
@@ -188,6 +269,9 @@ export function DashboardStoreProvider({
|
|||||||
const hydratedSelectionRef = useRef(false);
|
const hydratedSelectionRef = useRef(false);
|
||||||
const hydratedProCacheRef = useRef(false);
|
const hydratedProCacheRef = useRef(false);
|
||||||
const backgroundSummaryCheckAtRef = useRef<Record<string, number>>({});
|
const backgroundSummaryCheckAtRef = useRef<Record<string, number>>({});
|
||||||
|
const summaryInflightByCityRef = useRef<Record<string, Promise<CitySummary>>>(
|
||||||
|
{},
|
||||||
|
);
|
||||||
const citySummariesRef = useRef<Record<string, CitySummary>>({});
|
const citySummariesRef = useRef<Record<string, CitySummary>>({});
|
||||||
const selectedCityRef = useRef<string | null>(null);
|
const selectedCityRef = useRef<string | null>(null);
|
||||||
const selectedDetail =
|
const selectedDetail =
|
||||||
@@ -529,6 +613,54 @@ export function DashboardStoreProvider({
|
|||||||
void refreshProAccess();
|
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(() => {
|
useEffect(() => {
|
||||||
if (proAccess.loading || !proAccess.authenticated || !proAccess.userId) {
|
if (proAccess.loading || !proAccess.authenticated || !proAccess.userId) {
|
||||||
return;
|
return;
|
||||||
@@ -563,64 +695,72 @@ export function DashboardStoreProvider({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!EAGER_CITY_SUMMARIES_ENABLED) return;
|
if (EAGER_CITY_SUMMARIES_DISABLED) return;
|
||||||
if (!cities.length) return;
|
if (!cities.length) return;
|
||||||
|
if (loadingState.refresh) return;
|
||||||
|
|
||||||
const queue = cities
|
const priorityQueue: string[] = [];
|
||||||
.map((city) => city.name)
|
const backgroundQueue: string[] = [];
|
||||||
.filter((cityName) => !citySummariesRef.current[cityName]);
|
const queued = new Set<string>();
|
||||||
if (!queue.length) return;
|
const enqueue = (cityName: string | null, queue: string[]) => {
|
||||||
|
if (!cityName || queued.has(cityName) || citySummariesRef.current[cityName]) {
|
||||||
let active = true;
|
return;
|
||||||
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 {}
|
|
||||||
}
|
}
|
||||||
|
queued.add(cityName);
|
||||||
|
queue.push(cityName);
|
||||||
};
|
};
|
||||||
|
|
||||||
void Promise.all(
|
enqueue(getStoredSelectedCityName(cities), priorityQueue);
|
||||||
Array.from({ length: Math.min(concurrency, queue.length) }, () =>
|
getPriorityPreloadCities(cities).forEach((cityName) => {
|
||||||
worker(),
|
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 () => {
|
return () => {
|
||||||
active = false;
|
active = false;
|
||||||
|
cancelIdleSchedule();
|
||||||
};
|
};
|
||||||
}, [cities]);
|
}, [cities, loadingState.refresh]);
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectCity = async (cityName: string) => {
|
const selectCity = async (cityName: string) => {
|
||||||
setSelectedCity(cityName);
|
setSelectedCity(cityName);
|
||||||
@@ -753,9 +893,7 @@ export function DashboardStoreProvider({
|
|||||||
});
|
});
|
||||||
setSelectedForecastDate(detail.local_date);
|
setSelectedForecastDate(detail.local_date);
|
||||||
} else {
|
} else {
|
||||||
const summary = await dashboardClient.getCitySummary(selectedCity, {
|
const summary = await ensureCitySummary(selectedCity, true);
|
||||||
force: true,
|
|
||||||
});
|
|
||||||
setCitySummariesByName((current) => ({
|
setCitySummariesByName((current) => ({
|
||||||
...current,
|
...current,
|
||||||
[selectedCity]: summary,
|
[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
|
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]]:
|
def _get_cached_summary(city: str, ttl: int) -> Optional[Dict[str, Any]]:
|
||||||
now_ts = _time.time()
|
now_ts = _time.time()
|
||||||
with _SUMMARY_CACHE_LOCK:
|
with _SUMMARY_CACHE_LOCK:
|
||||||
@@ -1055,9 +1085,11 @@ def _analyze(
|
|||||||
"""Fetch, analyse, and return structured weather data for one city."""
|
"""Fetch, analyse, and return structured weather data for one city."""
|
||||||
# Check cache
|
# Check cache
|
||||||
ttl = CACHE_TTL_ANKARA if city.lower() in TURKISH_MGM_CITIES else CACHE_TTL
|
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:
|
if not force_refresh:
|
||||||
cached = _cache.get(city)
|
cached = _cache.get(cache_key)
|
||||||
if cached and _time.time() - cached["t"] < ttl:
|
if cached and _time.time() - cached["t"] < ttl:
|
||||||
if include_llm_commentary:
|
if include_llm_commentary:
|
||||||
cached_payload = cached["d"]
|
cached_payload = cached["d"]
|
||||||
@@ -1081,7 +1113,7 @@ def _analyze(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# ── 1. Fetch raw data ──
|
# ── 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(
|
raw = _weather.fetch_all_sources(
|
||||||
city,
|
city,
|
||||||
@@ -1875,7 +1907,7 @@ def _analyze(
|
|||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
|
|
||||||
_cache[city] = {"t": _time.time(), "d": result}
|
_cache[cache_key] = {"t": _time.time(), "d": result}
|
||||||
return 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)
|
ttl = _analysis_ttl_for_city(city)
|
||||||
|
|
||||||
if not force_refresh:
|
if not force_refresh:
|
||||||
cached_detail = _cache.get(city)
|
cached_detail = _get_cached_analysis(city, ttl)
|
||||||
if cached_detail and _time.time() - cached_detail["t"] < ttl:
|
if cached_detail:
|
||||||
return cached_detail["d"]
|
return cached_detail
|
||||||
cached_summary = _get_cached_summary(city, ttl)
|
cached_summary = _get_cached_summary(city, ttl)
|
||||||
if cached_summary:
|
if cached_summary:
|
||||||
return cached_summary
|
return cached_summary
|
||||||
|
|||||||
Reference in New Issue
Block a user