Port 983bceb Extract the grading day-of-year window helpers from monorepo (drift reconciliation)
Claude-Session: https://claude.ai/code/session_01RdARHDJaYC1wSQRTYM6t3Z
This commit is contained in:
parent
b1869af70a
commit
8cec935b06
1 changed files with 20 additions and 14 deletions
|
|
@ -144,6 +144,21 @@ def window_mask(doys: np.ndarray, target_doy: int, half: int = HALF_WINDOW) -> n
|
|||
return circular <= half
|
||||
|
||||
|
||||
def _window(df: pl.DataFrame, target_doy: int) -> pl.DataFrame:
|
||||
"""The ±7-day seasonal sub-frame around a day-of-year — the reference sample
|
||||
every per-day-of-year summary (climatology, day detail, grading) draws from."""
|
||||
return df.filter(window_mask(df["doy"].to_numpy(), target_doy))
|
||||
|
||||
|
||||
def _window_meta(sub: pl.DataFrame) -> dict:
|
||||
"""Sample size and the [min, max] calendar years covered by a window sub-frame."""
|
||||
years = sub["date"].dt.year()
|
||||
return {
|
||||
"n_samples": int(len(sub)),
|
||||
"year_range": [int(years.min()), int(years.max())] if len(sub) else None,
|
||||
}
|
||||
|
||||
|
||||
def empirical_percentile(samples: np.ndarray, value) -> float | None:
|
||||
"""Mid-rank percentile of `value` within `samples` (handles ties correctly)."""
|
||||
n = samples.size
|
||||
|
|
@ -156,14 +171,8 @@ def empirical_percentile(samples: np.ndarray, value) -> float | None:
|
|||
|
||||
def climatology(df: pl.DataFrame, target_doy: int) -> dict:
|
||||
"""Summarize the +/-7 day historical distribution for one day of the year."""
|
||||
doys = df["doy"].to_numpy()
|
||||
sub = df.filter(window_mask(doys, target_doy))
|
||||
years = sub["date"].dt.year()
|
||||
out = {
|
||||
"target_doy": int(target_doy),
|
||||
"n_samples": int(len(sub)),
|
||||
"year_range": [int(years.min()), int(years.max())] if len(sub) else None,
|
||||
}
|
||||
sub = _window(df, target_doy)
|
||||
out = {"target_doy": int(target_doy), **_window_meta(sub)}
|
||||
for var in CLIMO_METRICS:
|
||||
if var not in sub.columns:
|
||||
out[var] = None
|
||||
|
|
@ -400,8 +409,7 @@ def day_detail(df: pl.DataFrame, date, obs: dict | None) -> dict:
|
|||
in the record — then only the climatological ladders are returned."""
|
||||
d = _as_date(date)
|
||||
doy = d.timetuple().tm_yday
|
||||
sub = df.filter(window_mask(df["doy"].to_numpy(), doy))
|
||||
years = sub["date"].dt.year()
|
||||
sub = _window(df, doy)
|
||||
|
||||
metrics = {}
|
||||
for var in TEMP_METRICS:
|
||||
|
|
@ -420,8 +428,7 @@ def day_detail(df: pl.DataFrame, date, obs: dict | None) -> dict:
|
|||
return {
|
||||
"date": d.isoformat(),
|
||||
"doy": doy,
|
||||
"n_samples": int(len(sub)),
|
||||
"year_range": [int(years.min()), int(years.max())] if len(sub) else None,
|
||||
**_window_meta(sub),
|
||||
"metrics": metrics,
|
||||
}
|
||||
|
||||
|
|
@ -430,8 +437,7 @@ def grade_day(df: pl.DataFrame, date, obs: dict) -> dict:
|
|||
"""Grade one observed day against its own day-of-year +/-7 window."""
|
||||
d = _as_date(date)
|
||||
doy = d.timetuple().tm_yday
|
||||
doys = df["doy"].to_numpy()
|
||||
sub = df.filter(window_mask(doys, doy))
|
||||
sub = _window(df, doy)
|
||||
|
||||
result = {"date": d.isoformat(), "doy": doy}
|
||||
for var in TEMP_METRICS:
|
||||
|
|
|
|||
Loading…
Reference in a new issue