@@ -53,30 +53,67 @@ def process_wind_data(lat, lon, date, level=850):
5353 target_date_utc = target_date_utc .astimezone (ZoneInfo ('UTC' ))
5454 print (f"Converted to UTC: { target_date_utc } " )
5555
56+ # Check if target date is in the future - adjust to latest available data
57+ now_utc = datetime .datetime .now (ZoneInfo ('UTC' ))
58+ if target_date_utc > now_utc :
59+ print (f"Target date { target_date_utc } is in the future, using current time: { now_utc } " )
60+ target_date_utc = now_utc
61+
5662 # Find the appropriate GFS initialization time and forecast hour
5763 # GFS runs every 6 hours: 00, 06, 12, 18 UTC
5864 gfs_init_hours = [0 , 6 , 12 , 18 ]
5965
66+ # For future dates or very recent dates, we need to account for GFS processing delay
67+ # GFS data typically becomes available 3-4 hours after initialization time
68+ processing_delay_hours = 4
69+ latest_available_time = now_utc - datetime .timedelta (hours = processing_delay_hours )
70+
71+ # If target date is too recent, adjust it
72+ if target_date_utc > latest_available_time :
73+ print (f"Target date { target_date_utc } may not have GFS data yet, using { latest_available_time } " )
74+ target_date_utc = latest_available_time
75+
6076 # Find the most recent GFS initialization time before or at the target time
6177 target_hour = target_date_utc .hour
6278
63- # Find the closest GFS init time (floor)
64- init_hour = max ( [h for h in gfs_init_hours if h <= target_hour ], default = 18 )
79+ # Find the closest GFS init time (floor) for the target date
80+ suitable_init_hours = [h for h in gfs_init_hours if h <= target_hour ]
6581
66- # If no suitable init hour found for today, use the last run from yesterday
67- if init_hour > target_hour :
68- init_date_utc = (target_date_utc - datetime .timedelta (days = 1 )).replace (hour = 18 , minute = 0 , second = 0 , microsecond = 0 )
69- fxx = target_hour + 6 # Hours from yesterday 18Z
70- else :
82+ if suitable_init_hours :
83+ init_hour = max (suitable_init_hours )
7184 init_date_utc = target_date_utc .replace (hour = init_hour , minute = 0 , second = 0 , microsecond = 0 )
7285 fxx = target_hour - init_hour
86+ else :
87+ # If no suitable init hour found for today, use the last run from yesterday
88+ init_date_utc = (target_date_utc - datetime .timedelta (days = 1 )).replace (hour = 18 , minute = 0 , second = 0 , microsecond = 0 )
89+ fxx = target_hour + 6 # Hours from yesterday 18Z
90+
91+ # Ensure the initialization time is not in the future considering processing delay
92+ if init_date_utc + datetime .timedelta (hours = processing_delay_hours ) > now_utc :
93+ print (f"Calculated init time { init_date_utc } is too recent, falling back to previous run" )
94+ # Fall back to previous 6-hour cycle
95+ if init_hour > 0 :
96+ prev_init_hour = max ([h for h in gfs_init_hours if h < init_hour ])
97+ init_date_utc = target_date_utc .replace (hour = prev_init_hour , minute = 0 , second = 0 , microsecond = 0 )
98+ fxx = target_hour - prev_init_hour
99+ else :
100+ # Fall back to yesterday's 18Z run
101+ init_date_utc = (target_date_utc - datetime .timedelta (days = 1 )).replace (hour = 18 , minute = 0 , second = 0 , microsecond = 0 )
102+ fxx = target_hour + 6
73103
74- print (f"GFS initialization: { init_date_utc } , forecast hour: { fxx } " )
104+ # Cap forecast hours at reasonable limits (GFS forecasts go out to ~384 hours)
105+ if fxx > 120 : # Use max 5-day forecast for better data availability
106+ print (f"Forecast hour { fxx } is too far out, adjusting to more recent data" )
107+ # Adjust to use a more recent initialization with shorter forecast
108+ while fxx > 120 and init_date_utc > target_date_utc - datetime .timedelta (days = 3 ):
109+ init_date_utc -= datetime .timedelta (hours = 6 )
110+ fxx += 6
111+
112+ print (f"Final GFS initialization: { init_date_utc } , forecast hour: { fxx } " )
75113
76114 # Convert to naive datetime for Herbie (it expects naive UTC datetimes)
77115 init_date_naive = init_date_utc .replace (tzinfo = None )
78116
79- # Create data directory relative to script location
80117 script_dir = os .path .dirname (os .path .abspath (__file__ ))
81118 data_dir = os .path .join (script_dir , '..' , 'data' )
82119 os .makedirs (data_dir , exist_ok = True )
@@ -106,6 +143,148 @@ def process_wind_data(lat, lon, date, level=850):
106143
107144 print ("Fetching new GFS data..." )
108145
146+ # Try multiple forecast hours if the exact one fails (for robustness)
147+ for attempt_fxx in [fxx , max (0 , fxx - 1 ), max (0 , fxx - 2 ), fxx + 1 , fxx + 2 ]:
148+ try :
149+ print (f"Attempting to fetch GFS data with fxx={ attempt_fxx } " )
150+ gfs_data = fetch_gfs_data (lat , lon , init_date_naive , attempt_fxx , level )
151+
152+ if gfs_data is not None :
153+ print (f"Successfully fetched GFS data with fxx={ attempt_fxx } , processing..." )
154+
155+ # Check if we have the required variables
156+ if 'u' not in gfs_data or 'v' not in gfs_data :
157+ print ("Error: GFS data missing u or v components" )
158+ print (f"Available variables: { list (gfs_data .keys ())} " )
159+ continue
160+
161+ u = gfs_data ['u' ]
162+ v = gfs_data ['v' ]
163+
164+ lon_step = float (gfs_data .longitude [1 ] - gfs_data .longitude [0 ])
165+ lat_step = float (gfs_data .latitude [0 ] - gfs_data .latitude [1 ]) # lat is decreasing
166+
167+ velocity_u = convert_wind_to_velocity_json (u , "u" , level , target_date_utc , init_date_utc , lon_step , lat_step )
168+ velocity_v = convert_wind_to_velocity_json (v , "v" , level , target_date_utc , init_date_utc , lon_step , lat_step )
169+
170+ result = [velocity_u , velocity_v ]
171+
172+ # Save to cache
173+ try :
174+ with open (filename , "w" ) as f :
175+ json .dump (result , f , indent = 2 )
176+ print (f"Saved weather data to: { filename } " )
177+ except Exception as e :
178+ print (f"Warning: Could not save cached file: { e } " )
179+
180+ print (f"Successfully processed wind data - returning { len (result )} components" )
181+ return result
182+
183+ except Exception as e :
184+ print (f"Attempt with fxx={ attempt_fxx } failed: { e } " )
185+ continue
186+
187+ print ("Failed to fetch GFS data with all attempted forecast hours" )
188+ return None (__file__ )
189+ data_dir = os .path .join (script_dir , '..' , 'data' )
190+ os .makedirs (data_dir , exist_ok = True )
191+
192+ # Generate filename
193+ date_str = init_date_naive .strftime ("%Y%m%d%H" )
194+ if fxx > 0 :
195+ filename = os .path .join (data_dir , f"gfs_velocity_{ date_str } _f{ fxx :03d} _{ level } mb.json" )
196+ else :
197+ filename = os .path .join (data_dir , f"gfs_velocity_{ date_str } _{ level } mb.json" )
198+
199+ print (f"Looking for cached file: { filename } " )
200+
201+ # Check if file already exists and is recent
202+ if os .path .exists (filename ):
203+ try :
204+ # Check if file is recent (less than 6 hours old)
205+ file_age = datetime .datetime .now () - datetime .datetime .fromtimestamp (os .path .getmtime (filename ))
206+ if file_age < datetime .timedelta (hours = 6 ):
207+ print (f"Loading recent cached file: { filename } " )
208+ with open (filename , "r" ) as f :
209+ return json .load (f )
210+ else :
211+ print (f"Cached file is old ({ file_age } ), fetching new data..." )
212+ except Exception as e :
213+ print (f"Error loading cached file: { e } " )
214+
215+ print ("Fetching new GFS data..." )
216+
217+ # Try multiple forecast hours if the exact one fails (for robustness)
218+ for attempt_fxx in [fxx , max (0 , fxx - 1 ), max (0 , fxx - 2 ), fxx + 1 , fxx + 2 ]:
219+ try :
220+ print (f"Attempting to fetch GFS data with fxx={ attempt_fxx } " )
221+ gfs_data = fetch_gfs_data (lat , lon , init_date_naive , attempt_fxx , level )
222+
223+ if gfs_data is not None :
224+ print (f"Successfully fetched GFS data with fxx={ attempt_fxx } , processing..." )
225+
226+ # Check if we have the required variables
227+ if 'u' not in gfs_data or 'v' not in gfs_data :
228+ print ("Error: GFS data missing u or v components" )
229+ print (f"Available variables: { list (gfs_data .keys ())} " )
230+ continue
231+
232+ u = gfs_data ['u' ]
233+ v = gfs_data ['v' ]
234+
235+ lon_step = float (gfs_data .longitude [1 ] - gfs_data .longitude [0 ])
236+ lat_step = float (gfs_data .latitude [0 ] - gfs_data .latitude [1 ]) # lat is decreasing
237+
238+ velocity_u = convert_wind_to_velocity_json (u , "u" , level , target_date_utc , init_date_utc , lon_step , lat_step )
239+ velocity_v = convert_wind_to_velocity_json (v , "v" , level , target_date_utc , init_date_utc , lon_step , lat_step )
240+
241+ result = [velocity_u , velocity_v ]
242+
243+ # Save to cache
244+ try :
245+ with open (filename , "w" ) as f :
246+ json .dump (result , f , indent = 2 )
247+ print (f"Saved weather data to: { filename } " )
248+ except Exception as e :
249+ print (f"Warning: Could not save cached file: { e } " )
250+
251+ print (f"Successfully processed wind data - returning { len (result )} components" )
252+ return result
253+
254+ except Exception as e :
255+ print (f"Attempt with fxx={ attempt_fxx } failed: { e } " )
256+ continue
257+
258+ print ("Failed to fetch GFS data with all attempted forecast hours" )
259+ return None (__file__ )
260+ data_dir = os .path .join (script_dir , '..' , 'data' )
261+ os .makedirs (data_dir , exist_ok = True )
262+
263+ # Generate filename
264+ date_str = init_date_naive .strftime ("%Y%m%d%H" )
265+ if fxx > 0 :
266+ filename = os .path .join (data_dir , f"gfs_velocity_{ date_str } _f{ fxx :03d} _{ level } mb.json" )
267+ else :
268+ filename = os .path .join (data_dir , f"gfs_velocity_{ date_str } _{ level } mb.json" )
269+
270+ print (f"Looking for cached file: { filename } " )
271+
272+ # Check if file already exists and is recent
273+ if os .path .exists (filename ):
274+ try :
275+ # Check if file is recent (less than 6 hours old)
276+ file_age = datetime .datetime .now () - datetime .datetime .fromtimestamp (os .path .getmtime (filename ))
277+ if file_age < datetime .timedelta (hours = 6 ):
278+ print (f"Loading recent cached file: { filename } " )
279+ with open (filename , "r" ) as f :
280+ return json .load (f )
281+ else :
282+ print (f"Cached file is old ({ file_age } ), fetching new data..." )
283+ except Exception as e :
284+ print (f"Error loading cached file: { e } " )
285+
286+ print ("Fetching new GFS data..." )
287+
109288 try :
110289 gfs_data = fetch_gfs_data (lat , lon , init_date_naive , fxx , level )
111290
0 commit comments