Hweh-446k
Summary
Task: Weather Forecasting
Inputs: 72 hours time-series
Outputs: 12h multivariate forecast
Params: 446k
Framework: PyTorch
Author: Paul Courneya (Harley-ml)
Description
Hweh-446k is a 446-thousand-parameter LSTM model distillation of Hweh-6M (a ~92% reduction in params!!), trained to predict the next 12 hours of weather, including temperature, humidity, pressure, precipitation, and more, using the previous 72 hours of weather context.
We recommend using this model as a backup to a weather API or for fast offline forecasting when internet access is unavailable.
We would also like to give a shoutout to Open-Meteo for providing a free-to-use weather forecasting API.
Why “Hweh”?
In Proto-Indo-European, the root *h₂weh₁- means “to blow.” We chose it as the name for a weather forecasting model because of its connection to wind and air.
Architecture
The model uses a multitask LSTM setup:
| Parameter | Value |
|---|---|
input_dim |
22 |
seq_len |
72 |
num_predict |
12 |
hidden_dim |
128 |
num_layers |
3 |
dropout |
0.1 |
encoder_type |
lstm |
num_locations |
82 |
location_emb_dim |
32 |
num_weather_classes |
7 |
Training
We trained Hweh-446k on 4.06 million rows of weather data from 82 locations with the supervision of Hweh-6M for one epoch, using a batch size of 16 and gradient accumulation of 5. Training ran for 4.3 hours on an RTX 2060 6GB GPU.
Input Features
temperature_2m_normrelative_humidity_2m_normapparent_temperature_normprecipitation_log_normsea_level_pressure_normsurface_pressure_normcloud_cover_total_normvisibility_normwind_speed_10m_normwind_direction_10m_sinwind_direction_10m_coshour_sinhour_cosday_of_year_sinday_of_year_cosweather_code_onehot_clearweather_code_onehot_cloudyweather_code_onehot_fogweather_code_onehot_drizzleweather_code_onehot_rainweather_code_onehot_snowweather_code_onehot_thunderstorm
Output Features
y_temp_c: continuous regressiony_humidity: continuous regressiony_apparent_temperature: continuous regressiony_precipitation_mm: continuous regressiony_sea_level_pressure_hpa: continuous regressiony_surface_pressure_hpa: continuous regressiony_cloud_cover_total: continuous regressiony_wind_speed_10m: continuous regressiony_wind_direction_sin: continuous regressiony_wind_direction_cos: continuous regressiony_rain_prob: binary classificationy_weather_class: multiclass classification
Training Results
Training & Evaluation Metrics
| Step | Train Loss | Eval Loss | Weather Acc | Rain Acc | Rain Recall | Weather Recall |
|---|---|---|---|---|---|---|
| 1k | 8.4609 | 8.8471 | 0.6317 | 0.7451 | 0.7640 | 0.2574 |
| 5k | 5.1420 | 5.0602 | 0.6247 | 0.7531 | 0.8025 | 0.5648 |
| 10k | 4.1733 | 3.9198 | 0.6117 | 0.7876 | 0.8016 | 0.6297 |
| 15k | 3.8354 | 3.6310 | 0.6140 | 0.7920 | 0.8009 | 0.6187 |
| 20k | 3.6206 | 3.4365 | 0.6083 | 0.7881 | 0.8140 | 0.6179 |
| 25k | 3.5378 | 3.3251 | 0.6083 | 0.7859 | 0.8173 | 0.6245 |
| 30k | 3.4534 | 3.2846 | 0.6041 | 0.7812 | 0.8272 | 0.6398 |
| 35k | 3.4272 | 3.2324 | 0.6061 | 0.7860 | 0.8194 | 0.6289 |
| 40k | 3.4143 | 3.2230 | 0.6080 | 0.7862 | 0.8200 | 0.6339 |
| 42.6k | — | 3.2180 | 0.6081 | 0.7857 | 0.8212 | 0.6340 |
Note: Loss looks higher than Hweh-6M's because of KL + train/val loss.
Regression Error Metrics (MAE)
| Step | Apparent | Cloud | Humidity | Precip (mm) | Sea Level P | Surface P | Temp | Wind |
|---|---|---|---|---|---|---|---|---|
| 1k | 212.80 | 2179.70 | 1476.42 | 0.140 | 7571.45 | 83590.98 | 172.79 | 60.49 |
| 5k | 2.28 | 25.58 | 9.04 | 0.107 | 3.50 | 14.55 | 1.90 | 3.78 |
| 10k | 2.06 | 25.31 | 8.08 | 0.100 | 3.31 | 9.63 | 1.72 | 3.37 |
| 15k | 1.91 | 25.00 | 7.88 | 0.101 | 3.18 | 7.93 | 1.61 | 3.25 |
| 20k | 1.88 | 25.12 | 7.60 | 0.101 | 3.13 | 7.41 | 1.56 | 3.18 |
| 25k | 1.84 | 25.01 | 7.53 | 0.102 | 3.09 | 6.61 | 1.53 | 3.13 |
| 30k | 1.81 | 25.03 | 7.45 | 0.102 | 3.12 | 6.60 | 1.51 | 3.12 |
| 35k | 1.81 | 24.94 | 7.42 | 0.101 | 3.07 | 6.39 | 1.52 | 3.12 |
| 40k | 1.79 | 24.94 | 7.39 | 0.101 | 3.06 | 6.37 | 1.50 | 3.11 |
| 42.6k | 1.79 | 24.92 | 7.39 | 0.101 | 3.06 | 6.38 | 1.50 | 3.11 |
This model did better than the teacher on MAE and accuracy, but the real-world accuracy is 5-10% worse.
Generation Examples
| ID | Class |
|---|---|
| 0 | clear |
| 1 | cloudy |
| 2 | fog |
| 3 | drizzle |
| 4 | rain |
| 5 | snow |
| 6 | thunderstorm |
City=Seattle
{
"city": "Seattle",
"location_id": "1",
"model_location_id": 0,
"data_source": "open-meteo forecast api (past-hours context only)",
"requested_at_utc": "2026-05-08T19:57:14.429521+00:00",
"context": {
"hours": 72,
"start_utc": "2026-05-05T19:00:00+00:00",
"end_utc": "2026-05-08T18:00:00+00:00",
"start_local": "2026-05-05T12:00:00-07:00",
"end_local": "2026-05-08T11:00:00-07:00"
},
"model": {
"encoder_type": "lstm",
"seq_len": 72,
"input_dim": 22,
"num_weather_classes": 7
},
"forecast": [
{
"lead_hours": 1,
"target_utc": "2026-05-08T19:00:00+00:00",
"target_local": "2026-05-08T12:00:00-07:00",
"temperature_2m_c": 12.21396255493164,
"relative_humidity_2m_pct": 72.33454895019531,
"apparent_temperature_c": 10.097986221313477,
"precipitation_mm": 0.015628309920430183,
"pressure_msl_hpa": 1022.0569458007812,
"surface_pressure_hpa": 1014.205078125,
"cloud_cover_pct": 94.34225463867188,
"wind_speed_10m_kmh": 12.568346977233887,
"rain_probability": 0.19356799125671387,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.020174680277705193,
"class_1": 0.9282320737838745,
"class_2": 0.0022441258188337088,
"class_3": 0.04022064805030823,
"class_4": 0.008552632294595242,
"class_5": 0.0005501594278030097,
"class_6": 2.556406798248645e-05
}
},
{
"lead_hours": 2,
"target_utc": "2026-05-08T20:00:00+00:00",
"target_local": "2026-05-08T13:00:00-07:00",
"temperature_2m_c": 12.8738374710083,
"relative_humidity_2m_pct": 70.51017761230469,
"apparent_temperature_c": 10.80291748046875,
"precipitation_mm": 0.011432276107370853,
"pressure_msl_hpa": 1022.0043334960938,
"surface_pressure_hpa": 1014.2881469726562,
"cloud_cover_pct": 89.5630111694336,
"wind_speed_10m_kmh": 12.822803497314453,
"rain_probability": 0.2689012587070465,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.04770936816930771,
"class_1": 0.8698592185974121,
"class_2": 0.0019157826900482178,
"class_3": 0.057819921523332596,
"class_4": 0.02183685451745987,
"class_5": 0.0008237561560235918,
"class_6": 3.5158725950168446e-05
}
},
{
"lead_hours": 3,
"target_utc": "2026-05-08T21:00:00+00:00",
"target_local": "2026-05-08T14:00:00-07:00",
"temperature_2m_c": 13.51952075958252,
"relative_humidity_2m_pct": 68.44591522216797,
"apparent_temperature_c": 11.500271797180176,
"precipitation_mm": 0.006943895947188139,
"pressure_msl_hpa": 1021.80859375,
"surface_pressure_hpa": 1014.2529296875,
"cloud_cover_pct": 84.49480438232422,
"wind_speed_10m_kmh": 12.941960334777832,
"rain_probability": 0.30342426896095276,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.07170139998197556,
"class_1": 0.8286910057067871,
"class_2": 0.0013093570014461875,
"class_3": 0.06433302909135818,
"class_4": 0.03300558775663376,
"class_5": 0.0009036734118126333,
"class_6": 5.590655928244814e-05
}
},
{
"lead_hours": 4,
"target_utc": "2026-05-08T22:00:00+00:00",
"target_local": "2026-05-08T15:00:00-07:00",
"temperature_2m_c": 13.970871925354004,
"relative_humidity_2m_pct": 66.8187026977539,
"apparent_temperature_c": 11.959537506103516,
"precipitation_mm": 0.009790810756385326,
"pressure_msl_hpa": 1021.4691162109375,
"surface_pressure_hpa": 1014.1052856445312,
"cloud_cover_pct": 80.34271240234375,
"wind_speed_10m_kmh": 13.050889015197754,
"rain_probability": 0.33110707998275757,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.10919982939958572,
"class_1": 0.7759758830070496,
"class_2": 0.0011831748997792602,
"class_3": 0.07015678286552429,
"class_4": 0.042580746114254,
"class_5": 0.000857028178870678,
"class_6": 4.6529316023224965e-05
}
},
{
"lead_hours": 5,
"target_utc": "2026-05-08T23:00:00+00:00",
"target_local": "2026-05-08T16:00:00-07:00",
"temperature_2m_c": 14.132287979125977,
"relative_humidity_2m_pct": 66.1208267211914,
"apparent_temperature_c": 12.156023025512695,
"precipitation_mm": 0.008600466884672642,
"pressure_msl_hpa": 1021.0518188476562,
"surface_pressure_hpa": 1013.7891845703125,
"cloud_cover_pct": 76.06925201416016,
"wind_speed_10m_kmh": 12.926268577575684,
"rain_probability": 0.3409281373023987,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.1305426061153412,
"class_1": 0.7395681142807007,
"class_2": 0.0007046378450468183,
"class_3": 0.07573042809963226,
"class_4": 0.052331216633319855,
"class_5": 0.0010767169296741486,
"class_6": 4.629969771485776e-05
}
},
{
"lead_hours": 6,
"target_utc": "2026-05-09T00:00:00+00:00",
"target_local": "2026-05-08T17:00:00-07:00",
"temperature_2m_c": 13.963343620300293,
"relative_humidity_2m_pct": 66.67638397216797,
"apparent_temperature_c": 11.971813201904297,
"precipitation_mm": 0.010375693440437317,
"pressure_msl_hpa": 1020.6505126953125,
"surface_pressure_hpa": 1013.4520874023438,
"cloud_cover_pct": 73.21341705322266,
"wind_speed_10m_kmh": 12.721481323242188,
"rain_probability": 0.35606324672698975,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.15235546231269836,
"class_1": 0.7133304476737976,
"class_2": 0.0007980632944963872,
"class_3": 0.07519367337226868,
"class_4": 0.05724283307790756,
"class_5": 0.0010369947412982583,
"class_6": 4.249428093316965e-05
}
},
{
"lead_hours": 7,
"target_utc": "2026-05-09T01:00:00+00:00",
"target_local": "2026-05-08T18:00:00-07:00",
"temperature_2m_c": 13.449448585510254,
"relative_humidity_2m_pct": 68.29602813720703,
"apparent_temperature_c": 11.426795959472656,
"precipitation_mm": 0.012202607467770576,
"pressure_msl_hpa": 1020.3434448242188,
"surface_pressure_hpa": 1013.139892578125,
"cloud_cover_pct": 70.92017364501953,
"wind_speed_10m_kmh": 12.359071731567383,
"rain_probability": 0.3651714026927948,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.16448773443698883,
"class_1": 0.695494532585144,
"class_2": 0.0008648976217955351,
"class_3": 0.07075871527194977,
"class_4": 0.06722015887498856,
"class_5": 0.0011408632853999734,
"class_6": 3.3135267585748807e-05
}
},
{
"lead_hours": 8,
"target_utc": "2026-05-09T02:00:00+00:00",
"target_local": "2026-05-08T19:00:00-07:00",
"temperature_2m_c": 12.755823135375977,
"relative_humidity_2m_pct": 70.44146728515625,
"apparent_temperature_c": 10.662925720214844,
"precipitation_mm": 0.014662384055554867,
"pressure_msl_hpa": 1020.1875610351562,
"surface_pressure_hpa": 1012.8895874023438,
"cloud_cover_pct": 69.15129852294922,
"wind_speed_10m_kmh": 11.787208557128906,
"rain_probability": 0.3489035665988922,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.19570188224315643,
"class_1": 0.6735588908195496,
"class_2": 0.000978420372121036,
"class_3": 0.06184739992022514,
"class_4": 0.0664038360118866,
"class_5": 0.0014616982080042362,
"class_6": 4.789666854776442e-05
}
},
{
"lead_hours": 9,
"target_utc": "2026-05-09T03:00:00+00:00",
"target_local": "2026-05-08T20:00:00-07:00",
"temperature_2m_c": 11.955390930175781,
"relative_humidity_2m_pct": 73.05667877197266,
"apparent_temperature_c": 9.77428913116455,
"precipitation_mm": 0.015376528725028038,
"pressure_msl_hpa": 1020.2242431640625,
"surface_pressure_hpa": 1012.7984619140625,
"cloud_cover_pct": 67.46344757080078,
"wind_speed_10m_kmh": 11.127586364746094,
"rain_probability": 0.36496502161026,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.20349998772144318,
"class_1": 0.6480608582496643,
"class_2": 0.0010103220120072365,
"class_3": 0.06373731791973114,
"class_4": 0.08206527680158615,
"class_5": 0.0015911461086943746,
"class_6": 3.510143869789317e-05
}
},
{
"lead_hours": 10,
"target_utc": "2026-05-09T04:00:00+00:00",
"target_local": "2026-05-08T21:00:00-07:00",
"temperature_2m_c": 11.182319641113281,
"relative_humidity_2m_pct": 75.5196304321289,
"apparent_temperature_c": 8.978246688842773,
"precipitation_mm": 0.016823438927531242,
"pressure_msl_hpa": 1020.421875,
"surface_pressure_hpa": 1012.8126831054688,
"cloud_cover_pct": 65.73115539550781,
"wind_speed_10m_kmh": 10.359850883483887,
"rain_probability": 0.35924479365348816,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.21845510601997375,
"class_1": 0.6389062404632568,
"class_2": 0.0018773162737488747,
"class_3": 0.059891991317272186,
"class_4": 0.07879848033189774,
"class_5": 0.002034474164247513,
"class_6": 3.633901724242605e-05
}
},
{
"lead_hours": 11,
"target_utc": "2026-05-09T05:00:00+00:00",
"target_local": "2026-05-08T22:00:00-07:00",
"temperature_2m_c": 10.499757766723633,
"relative_humidity_2m_pct": 77.536865234375,
"apparent_temperature_c": 8.306406021118164,
"precipitation_mm": 0.01860857754945755,
"pressure_msl_hpa": 1020.7318725585938,
"surface_pressure_hpa": 1012.9368896484375,
"cloud_cover_pct": 65.10720825195312,
"wind_speed_10m_kmh": 9.62015151977539,
"rain_probability": 0.3694237470626831,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.22774139046669006,
"class_1": 0.6242526173591614,
"class_2": 0.0028147574048489332,
"class_3": 0.06025313213467598,
"class_4": 0.0822005346417427,
"class_5": 0.0026846849359571934,
"class_6": 5.289047476253472e-05
}
},
{
"lead_hours": 12,
"target_utc": "2026-05-09T06:00:00+00:00",
"target_local": "2026-05-08T23:00:00-07:00",
"temperature_2m_c": 9.956731796264648,
"relative_humidity_2m_pct": 79.21904754638672,
"apparent_temperature_c": 7.87125301361084,
"precipitation_mm": 0.017959173768758774,
"pressure_msl_hpa": 1021.0579833984375,
"surface_pressure_hpa": 1013.093994140625,
"cloud_cover_pct": 64.14817810058594,
"wind_speed_10m_kmh": 8.923616409301758,
"rain_probability": 0.3691202700138092,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.23541118204593658,
"class_1": 0.618242621421814,
"class_2": 0.0044443667866289616,
"class_3": 0.06440076231956482,
"class_4": 0.0741729885339737,
"class_5": 0.003270090091973543,
"class_6": 5.8019890275318176e-05
}
}
],
"sanity": {
"sequence_shape": [
72,
22
],
"finite_features": true
}
}
PS C:\Users\Paulc> python3.12 weather_infer.py --model_dir "C:\Users\Paulc\weather_model\student_distilled" --city Seattle
Warning: unexpected keys while loading checkpoint: ['distill_proj.weight']
{
"city": "Seattle",
"location_id": "1",
"model_location_id": 0,
"data_source": "open-meteo forecast api (past-hours context only)",
"requested_at_utc": "2026-05-08T19:57:47.439276+00:00",
"context": {
"hours": 72,
"start_utc": "2026-05-05T19:00:00+00:00",
"end_utc": "2026-05-08T18:00:00+00:00",
"start_local": "2026-05-05T12:00:00-07:00",
"end_local": "2026-05-08T11:00:00-07:00"
},
"model": {
"encoder_type": "lstm",
"seq_len": 72,
"input_dim": 22,
"num_weather_classes": 7
},
"forecast": [
{
"lead_hours": 1,
"target_utc": "2026-05-08T19:00:00+00:00",
"target_local": "2026-05-08T12:00:00-07:00",
"temperature_2m_c": 13.681238174438477,
"relative_humidity_2m_pct": 69.90876770019531,
"apparent_temperature_c": 11.687149047851562,
"precipitation_mm": 0.0012515264097601175,
"pressure_msl_hpa": 1019.3030395507812,
"surface_pressure_hpa": 1018.5359497070312,
"cloud_cover_pct": 88.76920318603516,
"wind_speed_10m_kmh": 13.126839637756348,
"rain_probability": 0.1457637995481491,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.02097843959927559,
"class_1": 0.9304905533790588,
"class_2": 0.0025352241937071085,
"class_3": 0.03495039418339729,
"class_4": 0.01054275780916214,
"class_5": 0.0004654920194298029,
"class_6": 3.7044908822281286e-05
}
},
{
"lead_hours": 2,
"target_utc": "2026-05-08T20:00:00+00:00",
"target_local": "2026-05-08T13:00:00-07:00",
"temperature_2m_c": 14.486506462097168,
"relative_humidity_2m_pct": 67.52698516845703,
"apparent_temperature_c": 12.55270767211914,
"precipitation_mm": 0.0008608415955677629,
"pressure_msl_hpa": 1019.0198364257812,
"surface_pressure_hpa": 1018.4589233398438,
"cloud_cover_pct": 84.31889343261719,
"wind_speed_10m_kmh": 13.241435050964355,
"rain_probability": 0.19527363777160645,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.053049832582473755,
"class_1": 0.8794819712638855,
"class_2": 0.0021074640098959208,
"class_3": 0.0436088852584362,
"class_4": 0.02111213095486164,
"class_5": 0.0005800225771963596,
"class_6": 5.971627979306504e-05
}
},
{
"lead_hours": 3,
"target_utc": "2026-05-08T21:00:00+00:00",
"target_local": "2026-05-08T14:00:00-07:00",
"temperature_2m_c": 15.089279174804688,
"relative_humidity_2m_pct": 65.6773681640625,
"apparent_temperature_c": 13.181319236755371,
"precipitation_mm": 0.002190209459513426,
"pressure_msl_hpa": 1018.709716796875,
"surface_pressure_hpa": 1018.2867431640625,
"cloud_cover_pct": 80.14619445800781,
"wind_speed_10m_kmh": 13.32516860961914,
"rain_probability": 0.21867528557777405,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.08254168927669525,
"class_1": 0.8380688428878784,
"class_2": 0.0014072611229494214,
"class_3": 0.04709410294890404,
"class_4": 0.030200183391571045,
"class_5": 0.0006088378722779453,
"class_6": 7.912428554845974e-05
}
},
{
"lead_hours": 4,
"target_utc": "2026-05-08T22:00:00+00:00",
"target_local": "2026-05-08T15:00:00-07:00",
"temperature_2m_c": 15.403922080993652,
"relative_humidity_2m_pct": 64.67561340332031,
"apparent_temperature_c": 13.48654556274414,
"precipitation_mm": 0.0021571130491793156,
"pressure_msl_hpa": 1018.3944702148438,
"surface_pressure_hpa": 1018.0625,
"cloud_cover_pct": 76.4104995727539,
"wind_speed_10m_kmh": 13.275524139404297,
"rain_probability": 0.2299734503030777,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.11604393273591995,
"class_1": 0.7922014594078064,
"class_2": 0.0011422870447859168,
"class_3": 0.05158008262515068,
"class_4": 0.03833876550197601,
"class_5": 0.000621610670350492,
"class_6": 7.179530075518414e-05
}
},
{
"lead_hours": 5,
"target_utc": "2026-05-08T23:00:00+00:00",
"target_local": "2026-05-08T16:00:00-07:00",
"temperature_2m_c": 15.407997131347656,
"relative_humidity_2m_pct": 64.65668487548828,
"apparent_temperature_c": 13.48292350769043,
"precipitation_mm": 0.0026813943404704332,
"pressure_msl_hpa": 1018.1220703125,
"surface_pressure_hpa": 1017.7960205078125,
"cloud_cover_pct": 72.77561950683594,
"wind_speed_10m_kmh": 13.133893966674805,
"rain_probability": 0.23628145456314087,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.14982207119464874,
"class_1": 0.7523030638694763,
"class_2": 0.0008282885537482798,
"class_3": 0.05172016844153404,
"class_4": 0.04454538971185684,
"class_5": 0.0007084720418788493,
"class_6": 7.257604011101648e-05
}
},
{
"lead_hours": 6,
"target_utc": "2026-05-09T00:00:00+00:00",
"target_local": "2026-05-08T17:00:00-07:00",
"temperature_2m_c": 15.139252662658691,
"relative_humidity_2m_pct": 65.5518798828125,
"apparent_temperature_c": 13.192585945129395,
"precipitation_mm": 0.0026606114115566015,
"pressure_msl_hpa": 1017.9351196289062,
"surface_pressure_hpa": 1017.5604858398438,
"cloud_cover_pct": 69.60166931152344,
"wind_speed_10m_kmh": 12.789706230163574,
"rain_probability": 0.23617199063301086,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.17920561134815216,
"class_1": 0.7213184237480164,
"class_2": 0.0008871846948750317,
"class_3": 0.05144878104329109,
"class_4": 0.046307649463415146,
"class_5": 0.0007639037212356925,
"class_6": 6.843609298812225e-05
}
},
{
"lead_hours": 7,
"target_utc": "2026-05-09T01:00:00+00:00",
"target_local": "2026-05-08T18:00:00-07:00",
"temperature_2m_c": 14.66390609741211,
"relative_humidity_2m_pct": 67.1559066772461,
"apparent_temperature_c": 12.692171096801758,
"precipitation_mm": 0.002722225384786725,
"pressure_msl_hpa": 1017.8424072265625,
"surface_pressure_hpa": 1017.3685913085938,
"cloud_cover_pct": 66.97268676757812,
"wind_speed_10m_kmh": 12.329818725585938,
"rain_probability": 0.23410728573799133,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.20177967846393585,
"class_1": 0.697116494178772,
"class_2": 0.001064434414729476,
"class_3": 0.04921679198741913,
"class_4": 0.049886710941791534,
"class_5": 0.0008780939970165491,
"class_6": 5.7844863476930186e-05
}
},
{
"lead_hours": 8,
"target_utc": "2026-05-09T02:00:00+00:00",
"target_local": "2026-05-08T19:00:00-07:00",
"temperature_2m_c": 14.042488098144531,
"relative_humidity_2m_pct": 69.15681457519531,
"apparent_temperature_c": 12.045327186584473,
"precipitation_mm": 0.003981542307883501,
"pressure_msl_hpa": 1017.853759765625,
"surface_pressure_hpa": 1017.2957763671875,
"cloud_cover_pct": 64.85920715332031,
"wind_speed_10m_kmh": 11.780016899108887,
"rain_probability": 0.23837216198444366,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.23561197519302368,
"class_1": 0.6629505753517151,
"class_2": 0.0012489539803937078,
"class_3": 0.04817439988255501,
"class_4": 0.050942711532115936,
"class_5": 0.0010061608627438545,
"class_6": 6.52461385470815e-05
}
},
{
"lead_hours": 9,
"target_utc": "2026-05-09T03:00:00+00:00",
"target_local": "2026-05-08T20:00:00-07:00",
"temperature_2m_c": 13.325971603393555,
"relative_humidity_2m_pct": 71.42361450195312,
"apparent_temperature_c": 11.300540924072266,
"precipitation_mm": 0.0030152045655995607,
"pressure_msl_hpa": 1017.9505004882812,
"surface_pressure_hpa": 1017.2774047851562,
"cloud_cover_pct": 63.037109375,
"wind_speed_10m_kmh": 11.165238380432129,
"rain_probability": 0.23051044344902039,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.24986012279987335,
"class_1": 0.6375465989112854,
"class_2": 0.0016154011245816946,
"class_3": 0.04874761775135994,
"class_4": 0.06096767634153366,
"class_5": 0.0012142135528847575,
"class_6": 4.836557855014689e-05
}
},
{
"lead_hours": 10,
"target_utc": "2026-05-09T04:00:00+00:00",
"target_local": "2026-05-08T21:00:00-07:00",
"temperature_2m_c": 12.574642181396484,
"relative_humidity_2m_pct": 73.7841796875,
"apparent_temperature_c": 10.549814224243164,
"precipitation_mm": 0.004971037618815899,
"pressure_msl_hpa": 1018.10400390625,
"surface_pressure_hpa": 1017.2828979492188,
"cloud_cover_pct": 61.4162483215332,
"wind_speed_10m_kmh": 10.5538911819458,
"rain_probability": 0.23788221180438995,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.27152255177497864,
"class_1": 0.6182448863983154,
"class_2": 0.0025821358431130648,
"class_3": 0.04885515570640564,
"class_4": 0.05706281587481499,
"class_5": 0.0016854261048138142,
"class_6": 4.704758248408325e-05
}
},
{
"lead_hours": 11,
"target_utc": "2026-05-09T05:00:00+00:00",
"target_local": "2026-05-08T22:00:00-07:00",
"temperature_2m_c": 11.85836124420166,
"relative_humidity_2m_pct": 75.99488830566406,
"apparent_temperature_c": 9.845465660095215,
"precipitation_mm": 0.0059099141508340836,
"pressure_msl_hpa": 1018.2722778320312,
"surface_pressure_hpa": 1017.3274536132812,
"cloud_cover_pct": 60.944053649902344,
"wind_speed_10m_kmh": 10.019789695739746,
"rain_probability": 0.24793456494808197,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.27306604385375977,
"class_1": 0.6107510328292847,
"class_2": 0.004449337720870972,
"class_3": 0.0498417466878891,
"class_4": 0.05973493307828903,
"class_5": 0.002092213137075305,
"class_6": 6.465442857006565e-05
}
},
{
"lead_hours": 12,
"target_utc": "2026-05-09T06:00:00+00:00",
"target_local": "2026-05-08T23:00:00-07:00",
"temperature_2m_c": 11.196554183959961,
"relative_humidity_2m_pct": 78.09349060058594,
"apparent_temperature_c": 9.231935501098633,
"precipitation_mm": 0.00701399240642786,
"pressure_msl_hpa": 1018.4185791015625,
"surface_pressure_hpa": 1017.3386840820312,
"cloud_cover_pct": 60.26295471191406,
"wind_speed_10m_kmh": 9.483912467956543,
"rain_probability": 0.2565533518791199,
"weather_class": 1,
"weather_class_name": "class_1",
"weather_class_probabilities": {
"class_0": 0.27720507979393005,
"class_1": 0.6052833795547485,
"class_2": 0.007175811566412449,
"class_3": 0.04983551800251007,
"class_4": 0.05781771242618561,
"class_5": 0.0026247103232890368,
"class_6": 5.782112566521391e-05
}
}
],
"sanity": {
"sequence_shape": [
72,
22
],
"finite_features": true
}
}
City=Nuuk
{
"city": "Nuuk",
"location_id": "83",
"model_location_id": 0,
"data_source": "open-meteo forecast api (past-hours context only)",
"requested_at_utc": "2026-05-08T20:40:35.109779+00:00",
"context": {
"hours": 72,
"start_utc": "2026-05-05T20:00:00+00:00",
"end_utc": "2026-05-08T19:00:00+00:00",
"start_local": "2026-05-05T19:00:00-01:00",
"end_local": "2026-05-08T18:00:00-01:00"
},
"model": {
"encoder_type": "lstm",
"seq_len": 72,
"input_dim": 22,
"num_weather_classes": 7
},
"forecast": [
{
"lead_hours": 1,
"target_utc": "2026-05-08T20:00:00+00:00",
"target_local": "2026-05-08T19:00:00-01:00",
"temperature_2m_c": 5.2753753662109375,
"relative_humidity_2m_pct": 93.01068115234375,
"apparent_temperature_c": 1.6396684646606445,
"precipitation_mm": 0.3556472063064575,
"pressure_msl_hpa": 1005.5432739257812,
"surface_pressure_hpa": 973.415771484375,
"cloud_cover_pct": 98.54638671875,
"wind_speed_10m_kmh": 13.717008590698242,
"rain_probability": 0.9789170026779175,
"weather_class": 3,
"weather_class_name": "class_3",
"weather_class_probabilities": {
"class_0": 0.000619232130702585,
"class_1": 0.057769663631916046,
"class_2": 0.003395488252863288,
"class_3": 0.401492714881897,
"class_4": 0.18206973373889923,
"class_5": 0.3545896112918854,
"class_6": 6.364739965647459e-05
}
},
{
"lead_hours": 2,
"target_utc": "2026-05-08T21:00:00+00:00",
"target_local": "2026-05-08T20:00:00-01:00",
"temperature_2m_c": 4.986588478088379,
"relative_humidity_2m_pct": 93.84243774414062,
"apparent_temperature_c": 1.352757453918457,
"precipitation_mm": 0.2776345908641815,
"pressure_msl_hpa": 1005.599853515625,
"surface_pressure_hpa": 973.536376953125,
"cloud_cover_pct": 98.45586395263672,
"wind_speed_10m_kmh": 13.445389747619629,
"rain_probability": 0.9556259512901306,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.00150326790753752,
"class_1": 0.09517476707696915,
"class_2": 0.004558710381388664,
"class_3": 0.32409900426864624,
"class_4": 0.1846529245376587,
"class_5": 0.38996437191963196,
"class_6": 4.702423757407814e-05
}
},
{
"lead_hours": 3,
"target_utc": "2026-05-08T22:00:00+00:00",
"target_local": "2026-05-08T21:00:00-01:00",
"temperature_2m_c": 4.788308143615723,
"relative_humidity_2m_pct": 94.0885238647461,
"apparent_temperature_c": 1.1667909622192383,
"precipitation_mm": 0.23039932548999786,
"pressure_msl_hpa": 1005.703857421875,
"surface_pressure_hpa": 973.5965576171875,
"cloud_cover_pct": 97.65797424316406,
"wind_speed_10m_kmh": 13.209211349487305,
"rain_probability": 0.9299042820930481,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.002542331349104643,
"class_1": 0.12809209525585175,
"class_2": 0.0058285691775381565,
"class_3": 0.3043138086795807,
"class_4": 0.17225369811058044,
"class_5": 0.3869440257549286,
"class_6": 2.5515650122542866e-05
}
},
{
"lead_hours": 4,
"target_utc": "2026-05-08T23:00:00+00:00",
"target_local": "2026-05-08T22:00:00-01:00",
"temperature_2m_c": 4.660131454467773,
"relative_humidity_2m_pct": 94.03594970703125,
"apparent_temperature_c": 1.0469179153442383,
"precipitation_mm": 0.19706439971923828,
"pressure_msl_hpa": 1005.7493896484375,
"surface_pressure_hpa": 973.7083740234375,
"cloud_cover_pct": 97.17306518554688,
"wind_speed_10m_kmh": 13.017566680908203,
"rain_probability": 0.9050359129905701,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.004407276399433613,
"class_1": 0.15539932250976562,
"class_2": 0.009657401591539383,
"class_3": 0.2794102430343628,
"class_4": 0.1521354615688324,
"class_5": 0.3989632725715637,
"class_6": 2.7043566660722718e-05
}
},
{
"lead_hours": 5,
"target_utc": "2026-05-09T00:00:00+00:00",
"target_local": "2026-05-08T23:00:00-01:00",
"temperature_2m_c": 4.5112457275390625,
"relative_humidity_2m_pct": 93.88682556152344,
"apparent_temperature_c": 0.9274702072143555,
"precipitation_mm": 0.1685791015625,
"pressure_msl_hpa": 1005.7725830078125,
"surface_pressure_hpa": 973.7322387695312,
"cloud_cover_pct": 96.03288269042969,
"wind_speed_10m_kmh": 12.944330215454102,
"rain_probability": 0.8804075121879578,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.006306177470833063,
"class_1": 0.17262385785579681,
"class_2": 0.00996350683271885,
"class_3": 0.2658991515636444,
"class_4": 0.1401163786649704,
"class_5": 0.4050598442554474,
"class_6": 3.1046529329614714e-05
}
},
{
"lead_hours": 6,
"target_utc": "2026-05-09T01:00:00+00:00",
"target_local": "2026-05-09T00:00:00-01:00",
"temperature_2m_c": 4.33610725402832,
"relative_humidity_2m_pct": 94.00520324707031,
"apparent_temperature_c": 0.7778654098510742,
"precipitation_mm": 0.14649224281311035,
"pressure_msl_hpa": 1005.8167114257812,
"surface_pressure_hpa": 973.7780151367188,
"cloud_cover_pct": 95.56141662597656,
"wind_speed_10m_kmh": 12.845012664794922,
"rain_probability": 0.8599434494972229,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.007768102455884218,
"class_1": 0.18894362449645996,
"class_2": 0.011767406016588211,
"class_3": 0.2329735904932022,
"class_4": 0.12322919070720673,
"class_5": 0.43529438972473145,
"class_6": 2.3752647393848747e-05
}
},
{
"lead_hours": 7,
"target_utc": "2026-05-09T02:00:00+00:00",
"target_local": "2026-05-09T01:00:00-01:00",
"temperature_2m_c": 4.140122413635254,
"relative_humidity_2m_pct": 94.25415802001953,
"apparent_temperature_c": 0.5866508483886719,
"precipitation_mm": 0.13218756020069122,
"pressure_msl_hpa": 1005.855712890625,
"surface_pressure_hpa": 973.7844848632812,
"cloud_cover_pct": 95.55270385742188,
"wind_speed_10m_kmh": 12.77564811706543,
"rain_probability": 0.8421469330787659,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.009295819327235222,
"class_1": 0.20261473953723907,
"class_2": 0.012845687568187714,
"class_3": 0.21367891132831573,
"class_4": 0.11506513506174088,
"class_5": 0.4464803636074066,
"class_6": 1.9363311366760172e-05
}
},
{
"lead_hours": 8,
"target_utc": "2026-05-09T03:00:00+00:00",
"target_local": "2026-05-09T02:00:00-01:00",
"temperature_2m_c": 3.953939437866211,
"relative_humidity_2m_pct": 94.34648895263672,
"apparent_temperature_c": 0.3805828094482422,
"precipitation_mm": 0.11994519084692001,
"pressure_msl_hpa": 1005.9537353515625,
"surface_pressure_hpa": 973.9803466796875,
"cloud_cover_pct": 95.32868957519531,
"wind_speed_10m_kmh": 12.735028266906738,
"rain_probability": 0.8208134174346924,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.011135051026940346,
"class_1": 0.21503449976444244,
"class_2": 0.015187171287834644,
"class_3": 0.18286095559597015,
"class_4": 0.1143169179558754,
"class_5": 0.46144354343414307,
"class_6": 2.182190291932784e-05
}
},
{
"lead_hours": 9,
"target_utc": "2026-05-09T04:00:00+00:00",
"target_local": "2026-05-09T03:00:00-01:00",
"temperature_2m_c": 3.826430320739746,
"relative_humidity_2m_pct": 94.16539001464844,
"apparent_temperature_c": 0.16225242614746094,
"precipitation_mm": 0.11211992800235748,
"pressure_msl_hpa": 1006.1436767578125,
"surface_pressure_hpa": 974.2029418945312,
"cloud_cover_pct": 94.6148681640625,
"wind_speed_10m_kmh": 12.761141777038574,
"rain_probability": 0.8099679350852966,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.013121353462338448,
"class_1": 0.23102521896362305,
"class_2": 0.017765367403626442,
"class_3": 0.1780213713645935,
"class_4": 0.12005121260881424,
"class_5": 0.4399879574775696,
"class_6": 2.7478810807224363e-05
}
},
{
"lead_hours": 10,
"target_utc": "2026-05-09T05:00:00+00:00",
"target_local": "2026-05-09T04:00:00-01:00",
"temperature_2m_c": 3.8089590072631836,
"relative_humidity_2m_pct": 93.53528594970703,
"apparent_temperature_c": 0.12103080749511719,
"precipitation_mm": 0.10691206157207489,
"pressure_msl_hpa": 1006.42529296875,
"surface_pressure_hpa": 974.4669189453125,
"cloud_cover_pct": 93.8226318359375,
"wind_speed_10m_kmh": 12.700864791870117,
"rain_probability": 0.797008216381073,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.014465805143117905,
"class_1": 0.22260957956314087,
"class_2": 0.018675586208701134,
"class_3": 0.15951745212078094,
"class_4": 0.10494350641965866,
"class_5": 0.47975844144821167,
"class_6": 2.9636566978297196e-05
}
},
{
"lead_hours": 11,
"target_utc": "2026-05-09T06:00:00+00:00",
"target_local": "2026-05-09T05:00:00-01:00",
"temperature_2m_c": 3.9785900115966797,
"relative_humidity_2m_pct": 92.22869110107422,
"apparent_temperature_c": 0.24558448791503906,
"precipitation_mm": 0.10196752846240997,
"pressure_msl_hpa": 1006.7659301757812,
"surface_pressure_hpa": 974.8555297851562,
"cloud_cover_pct": 92.74380493164062,
"wind_speed_10m_kmh": 12.71017837524414,
"rain_probability": 0.7881969213485718,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.01727476716041565,
"class_1": 0.232261523604393,
"class_2": 0.019182473421096802,
"class_3": 0.15716883540153503,
"class_4": 0.10425136238336563,
"class_5": 0.46981269121170044,
"class_6": 4.833983985008672e-05
}
},
{
"lead_hours": 12,
"target_utc": "2026-05-09T07:00:00+00:00",
"target_local": "2026-05-09T06:00:00-01:00",
"temperature_2m_c": 4.317435264587402,
"relative_humidity_2m_pct": 90.45832824707031,
"apparent_temperature_c": 0.6276102066040039,
"precipitation_mm": 0.09439859539270401,
"pressure_msl_hpa": 1007.0864868164062,
"surface_pressure_hpa": 975.1940307617188,
"cloud_cover_pct": 91.2315673828125,
"wind_speed_10m_kmh": 12.740204811096191,
"rain_probability": 0.7812836170196533,
"weather_class": 5,
"weather_class_name": "class_5",
"weather_class_probabilities": {
"class_0": 0.019589632749557495,
"class_1": 0.23847728967666626,
"class_2": 0.020689163357019424,
"class_3": 0.159035325050354,
"class_4": 0.08879318088293076,
"class_5": 0.47335243225097656,
"class_6": 6.291128374869004e-05
}
}
],
"sanity": {
"sequence_shape": [
72,
22
],
"finite_features": true
}
}
Note
In observed outputs, the model is often within 1°C of the actual value, which is 0.7 more than Hweh-6M.
Furthermore, you can pass locations that are not present in the model’s location embedding table. We’ve observed that the model can generalize to out-of-distribution (OOD) cities, with an estimated accuracy drop of only about 2–5%. However, this figure is an estimate and does not reflect a true ground-truth measurement.
Use Cases
Intended for:
- Backup to API
- Offline forecasting if you have the data
- Research
- Or more simply, for fun
Not intended for:
- Safety-critical forecasting (aviation, emergency response)
- Replacing meteorological or API services
Limitations
- The model is not perfectly accurate and will produce approximate forecasts rather than exact real-world weather conditions.
- Prediction accuracy decreases as the forecast horizon increases up to 12 hours.
- Performance may degrade on unseen or underrepresented geographic regions and climate types.
- The model does not enforce physical laws of atmospheric dynamics and may produce physically inconsistent outputs.
- Forecast quality is sensitive to the quality and completeness of input weather data.
- Rare or extreme weather events are underrepresented in training data and may be poorly predicted.
- Weather class outputs are simplified and do not capture fine-grained meteorological distinctions.
Inference
#!/usr/bin/env python3
from __future__ import annotations
import json
import time
from pathlib import Path
from typing import Any
import numpy as np
import pandas as pd
import requests
import torch
from transformers import AutoConfig, AutoModel
from zoneinfo import ZoneInfo
# ----------------------------
# Change these values here
# ----------------------------
MODEL_ID = r"Harley-ml/Hweh-446k" # HF repo id or local path
CITY = "New York"
SEQUENCE_META_PATH = "Harley-ml/Hweh-446k/weather_sequences.metadata.json"
CONTEXT_HOURS = 72
FORECAST_HOURS = 12
DEVICE = None # "cpu", "cuda", "cuda:0", or None for auto
API_BASE_URL = "https://api.open-meteo.com/v1/forecast"
MAX_RETRIES = 6
REQUEST_TIMEOUT_S = 60
HOURLY_VARS = [
"temperature_2m",
"relative_humidity_2m",
"apparent_temperature",
"precipitation",
"weather_code",
"pressure_msl",
"surface_pressure",
"cloud_cover",
"visibility",
"wind_speed_10m",
"wind_direction_10m",
]
WEATHER_CODE_BUCKETS = 7
TEMP_SCALE = 50.0
HUMIDITY_SCALE = 100.0
WIND_SCALE = 100.0
# ----------------------------
# City metadata (82 locations)
# ----------------------------
CITY_SPECS: dict[str, dict[str, Any]] = {
"Seattle": {"location_id": "1", "latitude": 47.6062, "longitude": -122.3321, "continent": "North America", "climate_tag": "temperate_oceanic", "elevation": 56},
"Portland": {"location_id": "2", "latitude": 45.5152, "longitude": -122.6784, "continent": "North America", "climate_tag": "temperate_oceanic", "elevation": 15},
"San Francisco": {"location_id": "3", "latitude": 37.7749, "longitude": -122.4194, "continent": "North America", "climate_tag": "foggy_mediterranean", "elevation": 16},
"Los Angeles": {"location_id": "4", "latitude": 34.0522, "longitude": -118.2437, "continent": "North America", "climate_tag": "sunny_mediterranean", "elevation": 71},
"Denver": {"location_id": "5", "latitude": 39.7392, "longitude": -104.9903, "continent": "North America", "climate_tag": "semi_arid_highland", "elevation": 1609},
"Chicago": {"location_id": "6", "latitude": 41.8781, "longitude": -87.6298, "continent": "North America", "climate_tag": "humid_continental", "elevation": 181},
"Dallas": {"location_id": "7", "latitude": 32.7767, "longitude": -96.7970, "continent": "North America", "climate_tag": "hot_subhumid", "elevation": 131},
"Atlanta": {"location_id": "8", "latitude": 33.7490, "longitude": -84.3880, "continent": "North America", "climate_tag": "humid_subtropical", "elevation": 320},
"New York": {"location_id": "9", "latitude": 40.7128, "longitude": -74.0060, "continent": "North America", "climate_tag": "humid_subtropical", "elevation": 10},
"Miami": {"location_id": "10", "latitude": 25.7617, "longitude": -80.1918, "continent": "North America", "climate_tag": "tropical_humid", "elevation": 2},
"Phoenix": {"location_id": "11", "latitude": 33.4484, "longitude": -112.0740, "continent": "North America", "climate_tag": "hot_arid", "elevation": 331},
"Salt Lake City": {"location_id": "12", "latitude": 40.7608, "longitude": -111.8910, "continent": "North America", "climate_tag": "semi_arid", "elevation": 1288},
"Anchorage": {"location_id": "13", "latitude": 61.2181, "longitude": -149.9003, "continent": "North America", "climate_tag": "subarctic_snowy", "elevation": 31},
"Minneapolis": {"location_id": "14", "latitude": 44.9778, "longitude": -93.2650, "continent": "North America", "climate_tag": "cold_snowy", "elevation": 264},
"Toronto": {"location_id": "15", "latitude": 43.6532, "longitude": -79.3832, "continent": "North America", "climate_tag": "humid_continental", "elevation": 76},
"Montreal": {"location_id": "16", "latitude": 45.5017, "longitude": -73.5673, "continent": "North America", "climate_tag": "cold_snowy", "elevation": 233},
"Vancouver": {"location_id": "17", "latitude": 49.2827, "longitude": -123.1207, "continent": "North America", "climate_tag": "temperate_oceanic", "elevation": 70},
"Mexico City": {"location_id": "18", "latitude": 19.4326, "longitude": -99.1332, "continent": "North America", "climate_tag": "highland_subtropical", "elevation": 2240},
"Havana": {"location_id": "19", "latitude": 23.1136, "longitude": -82.3666, "continent": "North America", "climate_tag": "tropical_humid", "elevation": 59},
"San Juan": {"location_id": "20", "latitude": 18.4655, "longitude": -66.1057, "continent": "North America", "climate_tag": "tropical_humid", "elevation": 8},
"Lima": {"location_id": "21", "latitude": -12.0464, "longitude": -77.0428, "continent": "South America", "climate_tag": "coastal_arid", "elevation": 154},
"Santiago": {"location_id": "22", "latitude": -33.4489, "longitude": -70.6693, "continent": "South America", "climate_tag": "mediterranean", "elevation": 520},
"Buenos Aires": {"location_id": "23", "latitude": -34.6037, "longitude": -58.3816, "continent": "South America", "climate_tag": "humid_subtropical", "elevation": 25},
"Bogotá": {"location_id": "24", "latitude": 4.7110, "longitude": -74.0721, "continent": "South America", "climate_tag": "highland_cool", "elevation": 2640},
"Quito": {"location_id": "25", "latitude": -0.1807, "longitude": -78.4678, "continent": "South America", "climate_tag": "highland_equatorial", "elevation": 2850},
"Caracas": {"location_id": "26", "latitude": 10.4806, "longitude": -66.9036, "continent": "South America", "climate_tag": "tropical_humid", "elevation": 900},
"Rio de Janeiro": {"location_id": "27", "latitude": -22.9068, "longitude": -43.1729, "continent": "South America", "climate_tag": "tropical_humid", "elevation": 5},
"São Paulo": {"location_id": "28", "latitude": -23.5505, "longitude": -46.6333, "continent": "South America", "climate_tag": "humid_subtropical", "elevation": 760},
"La Paz": {"location_id": "29", "latitude": -16.4897, "longitude": -68.1193, "continent": "South America", "climate_tag": "highland_cold", "elevation": 3640},
"Cusco": {"location_id": "30", "latitude": -13.5319, "longitude": -71.9675, "continent": "South America", "climate_tag": "highland_cool", "elevation": 3399},
"Montevideo": {"location_id": "31", "latitude": -34.9011, "longitude": -56.1645, "continent": "South America", "climate_tag": "temperate_oceanic", "elevation": 43},
"Asunción": {"location_id": "32", "latitude": -25.2637, "longitude": -57.5759, "continent": "South America", "climate_tag": "humid_subtropical", "elevation": 43},
"Manaus": {"location_id": "33", "latitude": -3.1190, "longitude": -60.0217, "continent": "South America", "climate_tag": "tropical_humid", "elevation": 92},
"Recife": {"location_id": "34", "latitude": -8.0476, "longitude": -34.8770, "continent": "South America", "climate_tag": "tropical_coastal", "elevation": 4},
"Punta Arenas": {"location_id": "35", "latitude": -53.1638, "longitude": -70.9171, "continent": "South America", "climate_tag": "cold_windy", "elevation": 34},
"London": {"location_id": "36", "latitude": 51.5074, "longitude": -0.1278, "continent": "Europe", "climate_tag": "temperate_oceanic", "elevation": 11},
"Paris": {"location_id": "37", "latitude": 48.8566, "longitude": 2.3522, "continent": "Europe", "climate_tag": "temperate_oceanic", "elevation": 35},
"Madrid": {"location_id": "38", "latitude": 40.4168, "longitude": -3.7038, "continent": "Europe", "climate_tag": "hot_summer_mediterranean", "elevation": 667},
"Rome": {"location_id": "39", "latitude": 41.9028, "longitude": 12.4964, "continent": "Europe", "climate_tag": "hot_summer_mediterranean", "elevation": 21},
"Berlin": {"location_id": "40", "latitude": 52.52, "longitude": 13.4050, "continent": "Europe", "climate_tag": "temperate_continental", "elevation": 34},
"Stockholm": {"location_id": "41", "latitude": 59.3293, "longitude": 18.0686, "continent": "Europe", "climate_tag": "cold_marine", "elevation": 28},
"Oslo": {"location_id": "42", "latitude": 59.9139, "longitude": 10.7522, "continent": "Europe", "climate_tag": "cold_snowy", "elevation": 23},
"Helsinki": {"location_id": "43", "latitude": 60.1699, "longitude": 24.9384, "continent": "Europe", "climate_tag": "cold_snowy", "elevation": 25},
"Reykjavik": {"location_id": "44", "latitude": 64.1466, "longitude": -21.9426, "continent": "Europe", "climate_tag": "cold_windy", "elevation": 12},
"Kyiv": {"location_id": "45", "latitude": 50.4501, "longitude": 30.5234, "continent": "Europe", "climate_tag": "humid_continental", "elevation": 179},
"Lisbon": {"location_id": "46", "latitude": 38.7223, "longitude": -9.1393, "continent": "Europe", "climate_tag": "sunny_mediterranean", "elevation": 7},
"Athens": {"location_id": "47", "latitude": 37.9838, "longitude": 23.7275, "continent": "Europe", "climate_tag": "sunny_mediterranean", "elevation": 70},
"Zurich": {"location_id": "48", "latitude": 47.3769, "longitude": 8.5417, "continent": "Europe", "climate_tag": "temperate_continental", "elevation": 408},
"Dublin": {"location_id": "49", "latitude": 53.3498, "longitude": -6.2603, "continent": "Europe", "climate_tag": "temperate_oceanic", "elevation": 20},
"Vienna": {"location_id": "50", "latitude": 48.2082, "longitude": 16.3738, "continent": "Europe", "climate_tag": "temperate_continental", "elevation": 171},
"Dubai": {"location_id": "51", "latitude": 25.2048, "longitude": 55.2708, "continent": "Asia", "climate_tag": "hot_arid", "elevation": 16},
"Riyadh": {"location_id": "52", "latitude": 24.7136, "longitude": 46.6753, "continent": "Asia", "climate_tag": "hot_arid", "elevation": 612},
"Delhi": {"location_id": "53", "latitude": 28.7041, "longitude": 77.1025, "continent": "Asia", "climate_tag": "hot_semi_arid", "elevation": 216},
"Mumbai": {"location_id": "54", "latitude": 19.0760, "longitude": 72.8777, "continent": "Asia", "climate_tag": "tropical_humid", "elevation": 14},
"Bangkok": {"location_id": "55", "latitude": 13.7563, "longitude": 100.5018, "continent": "Asia", "climate_tag": "tropical_monsoon", "elevation": 2},
"Singapore": {"location_id": "56", "latitude": 1.3521, "longitude": 103.8198, "continent": "Asia", "climate_tag": "tropical_humid", "elevation": 15},
"Tokyo": {"location_id": "57", "latitude": 35.6762, "longitude": 139.6503, "continent": "Asia", "climate_tag": "humid_subtropical", "elevation": 40},
"Seoul": {"location_id": "58", "latitude": 37.5665, "longitude": 126.9780, "continent": "Asia", "climate_tag": "humid_continental", "elevation": 38},
"Ulaanbaatar": {"location_id": "59", "latitude": 47.8864, "longitude": 106.9057, "continent": "Asia", "climate_tag": "cold_steppe", "elevation": 1350},
"Kathmandu": {"location_id": "60", "latitude": 27.7172, "longitude": 85.3240, "continent": "Asia", "climate_tag": "highland_subtropical", "elevation": 1400},
"Chiang Mai": {"location_id": "61", "latitude": 18.7883, "longitude": 98.9853, "continent": "Asia", "climate_tag": "tropical_seasonal", "elevation": 300},
"Lhasa": {"location_id": "62", "latitude": 29.6520, "longitude": 91.1721, "continent": "Asia", "climate_tag": "high_altitude_cold", "elevation": 3656},
"Jakarta": {"location_id": "63", "latitude": -6.2088, "longitude": 106.8456, "continent": "Asia", "climate_tag": "tropical_humid", "elevation": 8},
"Manila": {"location_id": "64", "latitude": 14.5995, "longitude": 120.9842, "continent": "Asia", "climate_tag": "tropical_humid", "elevation": 16},
"Karachi": {"location_id": "65", "latitude": 24.8607, "longitude": 67.0011, "continent": "Asia", "climate_tag": "hot_arid", "elevation": 10},
"Cairo": {"location_id": "66", "latitude": 30.0444, "longitude": 31.2357, "continent": "Africa", "climate_tag": "hot_arid", "elevation": 23},
"Alexandria": {"location_id": "67", "latitude": 31.2001, "longitude": 29.9187, "continent": "Africa", "climate_tag": "coastal_mediterranean", "elevation": 5},
"Casablanca": {"location_id": "68", "latitude": 33.5731, "longitude": -7.5898, "continent": "Africa", "climate_tag": "coastal_mediterranean", "elevation": 56},
"Marrakech": {"location_id": "69", "latitude": 31.6295, "longitude": -7.9811, "continent": "Africa", "climate_tag": "hot_semi_arid", "elevation": 466},
"Lagos": {"location_id": "70", "latitude": 6.5244, "longitude": 3.3792, "continent": "Africa", "climate_tag": "tropical_humid", "elevation": 41},
"Nairobi": {"location_id": "71", "latitude": -1.2921, "longitude": 36.8219, "continent": "Africa", "climate_tag": "temperate_highland", "elevation": 1795},
"Addis Ababa": {"location_id": "72", "latitude": 8.9806, "longitude": 38.7578, "continent": "Africa", "climate_tag": "temperate_highland", "elevation": 2355},
"Cape Town": {"location_id": "73", "latitude": -33.9249, "longitude": 18.4241, "continent": "Africa", "climate_tag": "mediterranean", "elevation": 25},
"Johannesburg": {"location_id": "74", "latitude": -26.2041, "longitude": 28.0473, "continent": "Africa", "climate_tag": "subtropical_highland", "elevation": 1753},
"Windhoek": {"location_id": "75", "latitude": -22.5609, "longitude": 17.0658, "continent": "Africa", "climate_tag": "semi_arid", "elevation": 1650},
"Accra": {"location_id": "76", "latitude": 5.6037, "longitude": -0.1870, "continent": "Africa", "climate_tag": "tropical_humid", "elevation": 61},
"Kigali": {"location_id": "77", "latitude": -1.9441, "longitude": 30.0619, "continent": "Africa", "climate_tag": "highland_tropical", "elevation": 1567},
"Tunis": {"location_id": "78", "latitude": 36.8065, "longitude": 10.1815, "continent": "Africa", "climate_tag": "mediterranean", "elevation": 4},
"Dakar": {"location_id": "79", "latitude": -14.7167, "longitude": -17.4677, "continent": "Africa", "climate_tag": "hot_coastal", "elevation": 25},
"Mombasa": {"location_id": "80", "latitude": -4.0435, "longitude": 39.6682, "continent": "Africa", "climate_tag": "tropical_coastal", "elevation": 17},
"Sydney": {"location_id": "81", "latitude": -33.8688, "longitude": 151.2093, "continent": "Oceania", "climate_tag": "humid_subtropical", "elevation": 58},
"Melbourne": {"location_id": "82", "latitude": -37.8136, "longitude": 144.9631, "continent": "Oceania", "climate_tag": "temperate_oceanic", "elevation": 31},
}
CITY_TIMEZONES: dict[str, str] = {
"Seattle": "America/Los_Angeles",
"Portland": "America/Los_Angeles",
"San Francisco": "America/Los_Angeles",
"Los Angeles": "America/Los_Angeles",
"Denver": "America/Denver",
"Chicago": "America/Chicago",
"Dallas": "America/Chicago",
"Atlanta": "America/New_York",
"New York": "America/New_York",
"Miami": "America/New_York",
"Phoenix": "America/Phoenix",
"Salt Lake City": "America/Denver",
"Anchorage": "America/Anchorage",
"Minneapolis": "America/Chicago",
"Toronto": "America/Toronto",
"Montreal": "America/Toronto",
"Vancouver": "America/Vancouver",
"Mexico City": "America/Mexico_City",
"Havana": "America/Havana",
"San Juan": "America/Puerto_Rico",
"Lima": "America/Lima",
"Santiago": "America/Santiago",
"Buenos Aires": "America/Argentina/Buenos_Aires",
"Bogotá": "America/Bogota",
"Quito": "America/Guayaquil",
"Caracas": "America/Caracas",
"Rio de Janeiro": "America/Sao_Paulo",
"São Paulo": "America/Sao_Paulo",
"La Paz": "America/La_Paz",
"Cusco": "America/Lima",
"Montevideo": "America/Montevideo",
"Asunción": "America/Asuncion",
"Manaus": "America/Manaus",
"Recife": "America/Recife",
"Punta Arenas": "America/Punta_Arenas",
"London": "Europe/London",
"Paris": "Europe/Paris",
"Madrid": "Europe/Madrid",
"Rome": "Europe/Rome",
"Berlin": "Europe/Berlin",
"Stockholm": "Europe/Stockholm",
"Oslo": "Europe/Oslo",
"Helsinki": "Europe/Helsinki",
"Reykjavik": "Atlantic/Reykjavik",
"Kyiv": "Europe/Kyiv",
"Lisbon": "Europe/Lisbon",
"Athens": "Europe/Athens",
"Zurich": "Europe/Zurich",
"Dublin": "Europe/Dublin",
"Vienna": "Europe/Vienna",
"Dubai": "Asia/Dubai",
"Riyadh": "Asia/Riyadh",
"Delhi": "Asia/Kolkata",
"Mumbai": "Asia/Kolkata",
"Bangkok": "Asia/Bangkok",
"Singapore": "Asia/Singapore",
"Tokyo": "Asia/Tokyo",
"Seoul": "Asia/Seoul",
"Ulaanbaatar": "Asia/Ulaanbaatar",
"Kathmandu": "Asia/Kathmandu",
"Chiang Mai": "Asia/Bangkok",
"Lhasa": "Asia/Shanghai",
"Jakarta": "Asia/Jakarta",
"Manila": "Asia/Manila",
"Karachi": "Asia/Karachi",
"Cairo": "Africa/Cairo",
"Alexandria": "Africa/Cairo",
"Casablanca": "Africa/Casablanca",
"Marrakech": "Africa/Casablanca",
"Lagos": "Africa/Lagos",
"Nairobi": "Africa/Nairobi",
"Addis Ababa": "Africa/Addis_Ababa",
"Cape Town": "Africa/Johannesburg",
"Johannesburg": "Africa/Johannesburg",
"Windhoek": "Africa/Windhoek",
"Accra": "Africa/Accra",
"Kigali": "Africa/Kigali",
"Tunis": "Africa/Tunis",
"Dakar": "Africa/Dakar",
"Mombasa": "Africa/Nairobi",
"Sydney": "Australia/Sydney",
"Melbourne": "Australia/Melbourne",
}
# ----------------------------
# Helpers
# ----------------------------
def weather_code_to_bucket(code) -> int:
if code is None:
return 1
try:
if pd.isna(code):
return 1
except Exception:
pass
code = int(code)
if code == 0:
return 0
if code in (1, 2, 3):
return 1
if code in (45, 48):
return 2
if code in (51, 53, 55, 56, 57):
return 3
if code in (61, 63, 65, 66, 67, 80, 81, 82):
return 4
if code in (71, 73, 75, 77, 85, 86):
return 5
if code in (95, 96, 99):
return 6
return 1
def cyc(x: np.ndarray, period: float) -> tuple[np.ndarray, np.ndarray]:
angle = 2.0 * np.pi * (x / period)
return np.sin(angle), np.cos(angle)
def clamp_array(x: np.ndarray, lo: float | None = None, hi: float | None = None) -> np.ndarray:
return np.clip(x, lo, hi)
def request_with_backoff(session: requests.Session, url: str, params: dict[str, Any]) -> dict[str, Any]:
last_exc: Exception | None = None
for attempt in range(MAX_RETRIES):
try:
resp = session.get(url, params=params, timeout=REQUEST_TIMEOUT_S)
if resp.status_code == 429:
retry_after = resp.headers.get("Retry-After")
sleep_s = float(retry_after) if retry_after else min(60.0, 2**attempt)
print(f"Rate limited. Sleeping {sleep_s:.1f}s and retrying.", flush=True)
time.sleep(sleep_s)
continue
resp.raise_for_status()
return resp.json()
except Exception as e:
last_exc = e
sleep_s = min(60.0, 2**attempt)
print(f"Request failed: {e}. Sleeping {sleep_s:.1f}s and retrying.", flush=True)
time.sleep(sleep_s)
raise RuntimeError(f"Failed after {MAX_RETRIES} retries: {params}") from last_exc
def load_sequence_meta(path: str) -> dict[str, Any]:
p = Path(path)
if not p.exists():
return {"location_to_id": {}}
with open(p, "r", encoding="utf-8") as f:
meta = json.load(f)
meta.setdefault("location_to_id", {})
return meta
def load_model():
config = AutoConfig.from_pretrained(MODEL_ID, trust_remote_code=True)
model = AutoModel.from_pretrained(MODEL_ID, config=config, trust_remote_code=True)
model.eval()
return model, config
def fetch_recent_history(city: str, context_hours: int) -> pd.DataFrame:
if city not in CITY_SPECS:
raise ValueError(f"Unknown city: {city}")
spec = CITY_SPECS[city]
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0"})
params = {
"latitude": spec["latitude"],
"longitude": spec["longitude"],
"hourly": ",".join(HOURLY_VARS),
"timezone": "UTC",
"temperature_unit": "celsius",
"wind_speed_unit": "kmh",
"precipitation_unit": "mm",
"past_hours": int(context_hours) + 2,
"forecast_hours": 0,
}
data = request_with_backoff(session, API_BASE_URL, params=params)
hourly = data.get("hourly", {})
if "time" not in hourly:
raise ValueError(f"No hourly data returned for {city}: {data}")
df = pd.DataFrame(hourly)
if df.empty:
raise ValueError(f"Empty hourly response for {city}.")
df["time"] = pd.to_datetime(df["time"], errors="coerce", utc=True)
df = df.dropna(subset=["time"]).sort_values("time").drop_duplicates(subset=["time"]).reset_index(drop=True)
needed = HOURLY_VARS
missing = [c for c in needed if c not in df.columns]
if missing:
raise ValueError(f"Missing hourly columns in API response: {missing}")
for c in needed:
df[c] = pd.to_numeric(df[c], errors="coerce")
df["weather_code"] = df["weather_code"].fillna(1)
df["precipitation"] = df["precipitation"].fillna(0.0)
for c in [
"temperature_2m",
"relative_humidity_2m",
"apparent_temperature",
"precipitation",
"pressure_msl",
"surface_pressure",
"cloud_cover",
"visibility",
"wind_speed_10m",
"wind_direction_10m",
]:
df[c] = df[c].interpolate(limit_direction="both").ffill().bfill()
now_utc = pd.Timestamp.now(tz="UTC")
df = df[df["time"] <= now_utc].copy()
if len(df) < context_hours:
raise ValueError(f"Not enough observed rows: got {len(df)}, need {context_hours}")
return df.tail(context_hours).reset_index(drop=True)
def build_single_sequence(df: pd.DataFrame) -> np.ndarray:
hour = df["time"].dt.hour.to_numpy()
doy = df["time"].dt.dayofyear.to_numpy()
hour_sin, hour_cos = cyc(hour.astype(float), 24.0)
doy_sin, doy_cos = cyc(doy.astype(float), 365.25)
temp = np.nan_to_num(df["temperature_2m"].astype(float).to_numpy(), nan=0.0)
humidity = np.nan_to_num(df["relative_humidity_2m"].astype(float).to_numpy(), nan=0.0)
apparent = np.nan_to_num(df["apparent_temperature"].astype(float).to_numpy(), nan=0.0)
precip = np.nan_to_num(df["precipitation"].astype(float).to_numpy(), nan=0.0)
pressure = np.nan_to_num(df["pressure_msl"].astype(float).to_numpy(), nan=0.0)
surface_pressure = np.nan_to_num(df["surface_pressure"].astype(float).to_numpy(), nan=0.0)
cloud_cover = np.nan_to_num(df["cloud_cover"].astype(float).to_numpy(), nan=0.0)
visibility = np.nan_to_num(df["visibility"].astype(float).to_numpy(), nan=0.0)
wind = np.nan_to_num(df["wind_speed_10m"].astype(float).to_numpy(), nan=0.0)
wind_dir = np.nan_to_num(df["wind_direction_10m"].astype(float).to_numpy(), nan=0.0)
humidity = clamp_array(humidity, 0.0, 100.0)
cloud_cover = clamp_array(cloud_cover, 0.0, 100.0)
precip = clamp_array(precip, 0.0, None)
wind = clamp_array(wind, 0.0, None)
visibility = clamp_array(visibility, 0.0, None)
wind_dir_sin, wind_dir_cos = cyc(wind_dir, 360.0)
weather_bucket = df["weather_code"].fillna(1).apply(weather_code_to_bucket).to_numpy(dtype=np.int64)
rows = []
for i in range(len(df)):
wc_oh = np.zeros(WEATHER_CODE_BUCKETS, dtype=np.float32)
wc_oh[weather_bucket[i]] = 1.0
row = np.concatenate(
[
np.array(
[
temp[i] / TEMP_SCALE,
humidity[i] / HUMIDITY_SCALE,
apparent[i] / TEMP_SCALE,
np.log1p(max(precip[i], 0.0)) / 3.0,
pressure[i] / 1100.0,
surface_pressure[i] / 1100.0,
cloud_cover[i] / 100.0,
visibility[i] / 50000.0,
wind[i] / WIND_SCALE,
wind_dir_sin[i],
wind_dir_cos[i],
hour_sin[i],
hour_cos[i],
doy_sin[i],
doy_cos[i],
],
dtype=np.float32,
),
wc_oh,
]
)
rows.append(row)
seq = np.asarray(rows, dtype=np.float32)
if not np.isfinite(seq).all():
bad = np.argwhere(~np.isfinite(seq))
raise ValueError(f"Non-finite values remain in sequence at positions like: {bad[:10].tolist()}")
return seq
def to_iso(ts: pd.Timestamp, tz_name: str | None = None) -> str:
if tz_name:
try:
return ts.tz_convert(ZoneInfo(tz_name)).isoformat()
except Exception:
pass
return ts.isoformat()
def get_logits(out):
if isinstance(out, dict) and "logits" in out:
return out["logits"]
if hasattr(out, "logits"):
return out.logits
return out
def resolve_location_index(seq_meta: dict[str, Any], city_location_id: str) -> int:
location_to_id = seq_meta.get("location_to_id", {})
if city_location_id in location_to_id:
return int(location_to_id[city_location_id])
try:
as_int = int(city_location_id)
if as_int in location_to_id:
return int(location_to_id[as_int])
if str(as_int) in location_to_id:
return int(location_to_id[str(as_int)])
except Exception:
pass
for unk_key in ("UNK", "<UNK>", "unknown", "UNKNOWN"):
if unk_key in location_to_id:
return int(location_to_id[unk_key])
return 0
def predict():
seq_meta = load_sequence_meta(SEQUENCE_META_PATH)
model, config = load_model()
if CITY not in CITY_SPECS:
raise ValueError(f"Unknown city: {CITY}")
if CONTEXT_HOURS <= 0:
raise ValueError("CONTEXT_HOURS must be > 0")
if hasattr(config, "seq_len") and int(config.seq_len) != CONTEXT_HOURS:
raise ValueError(f"Set CONTEXT_HOURS to {int(config.seq_len)} for this model.")
city_spec = CITY_SPECS[CITY]
city_tz = CITY_TIMEZONES.get(CITY, "UTC")
model_location_id = resolve_location_index(seq_meta, str(city_spec["location_id"]))
df = fetch_recent_history(CITY, CONTEXT_HOURS)
seq = build_single_sequence(df)
X = torch.from_numpy(seq).unsqueeze(0)
loc = torch.tensor([model_location_id], dtype=torch.long)
target_device = torch.device(
DEVICE if DEVICE else ("cuda" if torch.cuda.is_available() else "cpu")
)
model = model.to(target_device)
X = X.to(target_device)
loc = loc.to(target_device)
weather_class_names = getattr(config, "weather_class_names", None)
if not weather_class_names:
weather_class_names = [f"class_{i}" for i in range(int(getattr(config, "num_weather_classes", 7)))]
with torch.no_grad():
out = model(X=X, location_id=loc)
logits = get_logits(out)
(
temp_pred,
humidity_pred,
apparent_pred,
precip_pred,
sea_level_pressure_pred,
surface_pressure_pred,
cloud_cover_pred,
wind_pred,
wind_dir_sin_pred,
wind_dir_cos_pred,
rain_logit,
weather_logits,
) = logits
temp_pred = temp_pred.squeeze(0).detach().cpu().numpy()
humidity_pred = humidity_pred.squeeze(0).detach().cpu().numpy()
apparent_pred = apparent_pred.squeeze(0).detach().cpu().numpy()
precip_pred = precip_pred.squeeze(0).detach().cpu().numpy()
sea_level_pressure_pred = sea_level_pressure_pred.squeeze(0).detach().cpu().numpy()
surface_pressure_pred = surface_pressure_pred.squeeze(0).detach().cpu().numpy()
cloud_cover_pred = cloud_cover_pred.squeeze(0).detach().cpu().numpy()
wind_pred = wind_pred.squeeze(0).detach().cpu().numpy()
rain_prob = torch.sigmoid(rain_logit).squeeze(0).detach().cpu().numpy()
weather_probs = torch.softmax(weather_logits, dim=-1).squeeze(0).detach().cpu().numpy()
weather_idx = np.argmax(weather_probs, axis=-1).astype(np.int64)
humidity_pred = np.clip(humidity_pred, 0.0, 100.0)
cloud_cover_pred = np.clip(cloud_cover_pred, 0.0, 100.0)
precip_pred = np.clip(precip_pred, 0.0, None)
wind_pred = np.clip(wind_pred, 0.0, None)
rain_prob = np.clip(rain_prob, 0.0, 1.0)
context_start = df["time"].iloc[0]
context_end = df["time"].iloc[-1]
requested_at_utc = pd.Timestamp.now(tz="UTC")
horizon = min(
int(FORECAST_HOURS),
int(temp_pred.shape[0]),
int(humidity_pred.shape[0]),
int(weather_idx.shape[0]),
)
forecast = []
for lead in range(1, horizon + 1):
target_time = context_end + pd.Timedelta(hours=lead)
idx = lead - 1
w_idx = int(weather_idx[idx])
forecast.append(
{
"lead_hours": lead,
"target_utc": target_time.isoformat(),
"target_local": to_iso(target_time, city_tz),
"temperature_2m_c": float(temp_pred[idx]),
"relative_humidity_2m_pct": float(humidity_pred[idx]),
"apparent_temperature_c": float(apparent_pred[idx]),
"precipitation_mm": float(precip_pred[idx]),
"pressure_msl_hpa": float(sea_level_pressure_pred[idx]),
"surface_pressure_hpa": float(surface_pressure_pred[idx]),
"cloud_cover_pct": float(cloud_cover_pred[idx]),
"wind_speed_10m_kmh": float(wind_pred[idx]),
"rain_probability": float(rain_prob[idx]),
"weather_class": w_idx,
"weather_class_name": weather_class_names[w_idx] if w_idx < len(weather_class_names) else f"class_{w_idx}",
"weather_class_probabilities": {
name: float(prob) for name, prob in zip(weather_class_names, weather_probs[idx])
},
}
)
result = {
"city": CITY,
"location_id": str(city_spec["location_id"]),
"model_location_id": int(model_location_id),
"data_source": "open-meteo forecast api (past-hours context only)",
"requested_at_utc": requested_at_utc.isoformat(),
"context": {
"hours": int(len(df)),
"start_utc": context_start.isoformat(),
"end_utc": context_end.isoformat(),
"start_local": to_iso(context_start, city_tz),
"end_local": to_iso(context_end, city_tz),
},
"model": {
"model_id": MODEL_ID,
"encoder_type": getattr(config, "encoder_type", None),
"seq_len": int(getattr(config, "seq_len", CONTEXT_HOURS)),
"input_dim": int(getattr(config, "input_dim", seq.shape[1])),
"num_weather_classes": int(getattr(config, "num_weather_classes", len(weather_class_names))),
},
"forecast": forecast,
"sanity": {
"sequence_shape": list(seq.shape),
"finite_features": bool(np.isfinite(seq).all()),
},
}
print(json.dumps(result, indent=2))
if __name__ == "__main__":
predict()
Citation
@misc{distilhweh-446k,
title = {DistilHweh-446k: Knowledge Distillation in Short-Term Multivariate Weather Forecasting},
author = {Paul Courneya; Harley-ml},
year = {2026},
url = {https://huggingface.co/Harley-ml/DistilHweh-446k}
}
- Downloads last month
- 66