nakas commited on
Commit
b7629db
·
1 Parent(s): fa540b8

Improve global snapshot robustness when target time missing

Browse files
Files changed (1) hide show
  1. app.py +46 -20
app.py CHANGED
@@ -144,8 +144,20 @@ def fetch_wave_history(
144
  raise ValueError("Provide either coordinates or a grid definition.")
145
 
146
  if from_time and until_time:
147
- if until_time <= from_time:
148
- raise ValueError("until_time must be after from_time.")
 
 
 
 
 
 
 
 
 
 
 
 
149
  elif not times_list:
150
  raise ValueError("Provide either a time range or an explicit times list.")
151
 
@@ -432,24 +444,34 @@ def run_global_download(
432
  alias_map[name] = alias
433
  variables_payload.append({"name": name, "level": DEFAULT_LEVEL, "alias": alias})
434
 
435
- df = fetch_wave_history(
436
- token=token,
437
- model=model,
438
- variables=variables_payload,
439
- times_list=[valid_iso],
440
- min_horizon=lower,
441
- max_horizon=upper,
442
- grid={
443
- "minLatitude": -90,
444
- "maxLatitude": 90,
445
- "minLongitude": -180,
446
- "maxLongitude": 180,
447
- "step": float(grid_step),
448
- },
449
- members=members,
450
- accept="text/csv",
451
- timeout=240,
452
- )
 
 
 
 
 
 
 
 
 
 
453
 
454
  if df.empty:
455
  raise ValueError("No data returned for the requested global configuration.")
@@ -514,6 +536,10 @@ def default_time_window(hours_back: int = 6, hours_forward: int = 24) -> Tuple[s
514
 
515
  def default_valid_time(offset_hours: int = 0) -> str:
516
  now = dt.datetime.utcnow().replace(minute=0, second=0, microsecond=0)
 
 
 
 
517
  return (now + dt.timedelta(hours=offset_hours)).isoformat() + "Z"
518
 
519
 
 
144
  raise ValueError("Provide either coordinates or a grid definition.")
145
 
146
  if from_time and until_time:
147
+ def parse_iso(text: str) -> dt.datetime:
148
+ text = text.strip()
149
+ if text.endswith("Z"):
150
+ text = text[:-1] + "+00:00"
151
+ return dt.datetime.fromisoformat(text)
152
+
153
+ start_dt = parse_iso(from_time)
154
+ end_dt = parse_iso(until_time)
155
+ if end_dt < start_dt:
156
+ raise ValueError("until_time must not be before from_time.")
157
+ # If equal, nudge the end forward by a small epsilon to keep API happy.
158
+ if end_dt == start_dt:
159
+ end_dt += dt.timedelta(minutes=1)
160
+ until_time = end_dt.isoformat().replace("+00:00", "Z")
161
  elif not times_list:
162
  raise ValueError("Provide either a time range or an explicit times list.")
163
 
 
444
  alias_map[name] = alias
445
  variables_payload.append({"name": name, "level": DEFAULT_LEVEL, "alias": alias})
446
 
447
+ def request_payload(as_times_list: bool) -> pd.DataFrame:
448
+ kwargs = dict(
449
+ token=token,
450
+ model=model,
451
+ variables=variables_payload,
452
+ min_horizon=lower,
453
+ max_horizon=upper,
454
+ grid={
455
+ "minLatitude": -90,
456
+ "maxLatitude": 90,
457
+ "minLongitude": -180,
458
+ "maxLongitude": 180,
459
+ "step": float(grid_step),
460
+ },
461
+ members=members,
462
+ accept="text/csv",
463
+ timeout=240,
464
+ )
465
+ if as_times_list:
466
+ kwargs["times_list"] = [valid_iso]
467
+ else:
468
+ kwargs["from_time"] = valid_iso
469
+ kwargs["until_time"] = valid_iso
470
+ return fetch_wave_history(**kwargs)
471
+
472
+ df = request_payload(as_times_list=True)
473
+ if df.empty:
474
+ df = request_payload(as_times_list=False)
475
 
476
  if df.empty:
477
  raise ValueError("No data returned for the requested global configuration.")
 
536
 
537
  def default_valid_time(offset_hours: int = 0) -> str:
538
  now = dt.datetime.utcnow().replace(minute=0, second=0, microsecond=0)
539
+ # Align to the previous 3-hour boundary, which matches ECMWF wave output cadence.
540
+ hours_since_cycle = now.hour % 3
541
+ if hours_since_cycle != 0:
542
+ now -= dt.timedelta(hours=hours_since_cycle)
543
  return (now + dt.timedelta(hours=offset_hours)).isoformat() + "Z"
544
 
545