From cc0b1e298fc2580642a0df39318ea6f692a93d9a Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Wed, 22 Jul 2026 12:08:13 -0700 Subject: [PATCH] Port a0ec4bc Extract the shared graded-window tail in views.py from monorepo (drift reconciliation) Claude-Session: https://claude.ai/code/session_01RdARHDJaYC1wSQRTYM6t3Z --- api/payloads.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/api/payloads.py b/api/payloads.py index 82d4e35..3e90e3d 100644 --- a/api/payloads.py +++ b/api/payloads.py @@ -143,6 +143,19 @@ def _attach_dry_streaks(graded: list[dict], *precip_frames) -> None: g["dsr"] = dsr_map.get(g["date"]) +def _graded_window(history, rows, precip_frames, doy) -> tuple[list[dict], dict]: + """The shared tail of the /grade and /forecast payloads: grade `rows` against + history, attach dry streaks over `precip_frames`, order newest-first, and pair + the result with the target day-of-year's climatology summary. The window + *assembly* (recent+archive merge vs future-only filter) differs per builder and + stays there; only this post-processing is shared.""" + graded = _grade_rows(history, rows) + _attach_dry_streaks(graded, *precip_frames) + graded.reverse() # newest-first for display (the chart reverses to L→R chronological) + climo = grading.climatology(history, doy) + return graded, climo + + def build_grade(cell, target, days, history, recent, cache_meta, place, run=None, after=14) -> dict: """/grade payload: a window of days centered on the target — `days` of history before it, the target itself, then up to `after` days after it — plus the @@ -167,10 +180,8 @@ def build_grade(cell, target, days, history, recent, cache_meta, place, run=None # old pandas concat when the two sources' columns differ (e.g. a reduced # recent bundle); grading treats an absent column's value as None. window = pl.concat([rwin, hwin], how="diagonal_relaxed").sort("date") - graded = _grade_rows(history, window) - _attach_dry_streaks(graded, history, recent) - graded.reverse() # newest first for display - climo = grading.climatology(history, target.timetuple().tm_yday) + graded, climo = _graded_window(history, window, (history, recent), + target.timetuple().tm_yday) # Summarize what this run actually covered. A fresh history fetch pulls the # full ~45-year archive (run_type "full"); a cache hit only fetched the @@ -274,10 +285,8 @@ def build_forecast(cell, days, history, fc, today, place, run=None) -> dict: # Strictly future days (tomorrow onward), earliest→latest, capped at `days`. future = fc.filter(pl.col("date") > today).sort("date").head(days) with run.phase("grading"): - graded = _grade_rows(history, future) - _attach_dry_streaks(graded, history, fc) - graded.reverse() # furthest-out first, so the chart reverses to L→R chronological - climo = grading.climatology(history, today.timetuple().tm_yday) + graded, climo = _graded_window(history, future, (history, fc), + today.timetuple().tm_yday) run.set(graded_days=len(graded), place_found=place is not None) return { "api_version": "v2",