Spaces:
Sleeping
Sleeping
Improve global snapshot robustness when target time missing
Browse files
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 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 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 |
|