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
|
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:
|
def empirical_percentile(samples: np.ndarray, value) -> float | None:
|
||||||
"""Mid-rank percentile of `value` within `samples` (handles ties correctly)."""
|
"""Mid-rank percentile of `value` within `samples` (handles ties correctly)."""
|
||||||
n = samples.size
|
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:
|
def climatology(df: pl.DataFrame, target_doy: int) -> dict:
|
||||||
"""Summarize the +/-7 day historical distribution for one day of the year."""
|
"""Summarize the +/-7 day historical distribution for one day of the year."""
|
||||||
doys = df["doy"].to_numpy()
|
sub = _window(df, target_doy)
|
||||||
sub = df.filter(window_mask(doys, target_doy))
|
out = {"target_doy": int(target_doy), **_window_meta(sub)}
|
||||||
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,
|
|
||||||
}
|
|
||||||
for var in CLIMO_METRICS:
|
for var in CLIMO_METRICS:
|
||||||
if var not in sub.columns:
|
if var not in sub.columns:
|
||||||
out[var] = None
|
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."""
|
in the record — then only the climatological ladders are returned."""
|
||||||
d = _as_date(date)
|
d = _as_date(date)
|
||||||
doy = d.timetuple().tm_yday
|
doy = d.timetuple().tm_yday
|
||||||
sub = df.filter(window_mask(df["doy"].to_numpy(), doy))
|
sub = _window(df, doy)
|
||||||
years = sub["date"].dt.year()
|
|
||||||
|
|
||||||
metrics = {}
|
metrics = {}
|
||||||
for var in TEMP_METRICS:
|
for var in TEMP_METRICS:
|
||||||
|
|
@ -420,8 +428,7 @@ def day_detail(df: pl.DataFrame, date, obs: dict | None) -> dict:
|
||||||
return {
|
return {
|
||||||
"date": d.isoformat(),
|
"date": d.isoformat(),
|
||||||
"doy": doy,
|
"doy": doy,
|
||||||
"n_samples": int(len(sub)),
|
**_window_meta(sub),
|
||||||
"year_range": [int(years.min()), int(years.max())] if len(sub) else None,
|
|
||||||
"metrics": metrics,
|
"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."""
|
"""Grade one observed day against its own day-of-year +/-7 window."""
|
||||||
d = _as_date(date)
|
d = _as_date(date)
|
||||||
doy = d.timetuple().tm_yday
|
doy = d.timetuple().tm_yday
|
||||||
doys = df["doy"].to_numpy()
|
sub = _window(df, doy)
|
||||||
sub = df.filter(window_mask(doys, doy))
|
|
||||||
|
|
||||||
result = {"date": d.isoformat(), "doy": doy}
|
result = {"date": d.isoformat(), "doy": doy}
|
||||||
for var in TEMP_METRICS:
|
for var in TEMP_METRICS:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue