From 21927133a47d2d052f5cd0d1e74fee81822aef95 Mon Sep 17 00:00:00 2001 From: Arsham Mirehvandi <130353205+arshammi@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:11:45 +0200 Subject: [PATCH] Enhance weather metrics handling and phenology logic - Introduced a new function to round specific weather metric values to two decimal places in assemble.py. - Updated the JSON entry construction in assemble.py to utilize the new rounding function for weather metrics. - Modified the phenology loading logic in phenology.py to use yesterday's phase if today's phase is null after carry-forward. - Clarified documentation in README.md regarding the phenology phase handling. --- README.md | 2 +- pipeline/stages/assemble.py | 18 ++++++++++++++++-- pipeline/stages/phenology.py | 7 +++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 59928b0..e0d28bd 100644 --- a/README.md +++ b/README.md @@ -346,7 +346,7 @@ Run once per `(field, disease)` job by `pipeline/job.py::run_job`: 1. **Worklist** — crop → fields (`AI_agrosupport_an_colture` + `AI_agrosupport_cmp_layers`) → disease model (`AI_agrosupport_agro_models`); organic flag is `cmplay_imp == 3` (`pipeline/worklist.py`, run once per batch, not per job) 2. **Disease model** — per-day `FASE5` > `FASE2` resolution over as_of±5, from the worklist's known `anmod_id` 3. **Weather** — `TDatiMeteo_D` for past days, `TDatiMeteo_D_FRC` for today/future -4. **Phenology** — carry-forward of latest observation ≤ day; future days null +4. **Phenology** — carry-forward of latest observation ≤ day; future days null; if today's phase is missing, use yesterday's 5. **Applied treatments** — products sprayed over `as_of-5 .. as_of`, from `AI_agrosupport_agro_ril_operations*` with `rilop_operation = 10` 6. **LLM** — synthesise 1–2 anonymised search queries from the subset 7. **Product prefilter** — organic / crop / disease / FASE rules against the batch-wide, in-memory `products` index diff --git a/pipeline/stages/assemble.py b/pipeline/stages/assemble.py index bac0edd..9637767 100644 --- a/pipeline/stages/assemble.py +++ b/pipeline/stages/assemble.py @@ -20,6 +20,20 @@ METRIC_KEYS = ( "potential_evapotranspiration_mm", ) +_TWO_DECIMAL_KEYS = frozenset({ + "min_air_temperature_c", + "max_air_temperature_c", + "average_air_temperature_c", + "rainfall_mm", + "potential_evapotranspiration_mm", +}) + + +def _metric_value(key: str, value: Any) -> Any: + if value is None or key not in _TWO_DECIMAL_KEYS: + return value + return round(float(value), 2) + def _day_entry( day: date, @@ -32,7 +46,7 @@ def _day_entry( metrics = weather.get(day, {}) entry: dict[str, Any] = {"date": format_date(day)} for key in METRIC_KEYS: - entry[key] = metrics.get(key) + entry[key] = _metric_value(key, metrics.get(key)) entry["phenology_phase"] = phenology.get(day) entry["disease_forecast"] = forecasts.get(day) # Treatments can only have been applied on or before as_of; future days @@ -93,7 +107,7 @@ def build_json_for_query_synthesis( metrics = weather.get(day, {}) entry: dict[str, Any] = {"date": format_date(day)} for key in METRIC_KEYS: - entry[key] = metrics.get(key) + entry[key] = _metric_value(key, metrics.get(key)) weather_forecasts.append(entry) return { diff --git a/pipeline/stages/phenology.py b/pipeline/stages/phenology.py index 3741670..034d143 100644 --- a/pipeline/stages/phenology.py +++ b/pipeline/stages/phenology.py @@ -56,8 +56,7 @@ def load_phenology( Observations with rilfeno_date <= as_of are carried forward to each day (latest observation at or before that day). Future days are always null. - Today's phase equals yesterday's (which, under carry-forward, is the same - observation as of as_of-1, or the latest available before as_of). + If today's phase is null after carry-forward, yesterday's phase is used. """ window = build_window(as_of) rows = fetch_all(conn, _FENO_SQL, (field_id, as_of)) @@ -92,9 +91,9 @@ def load_phenology( _, ff_id = applicable[-1] result[day] = name_by_id.get(ff_id) - # Explicit rule: today's phase equals yesterday's. + # If today's phase is missing, fall back to yesterday's. yesterday = as_of.fromordinal(as_of.toordinal() - 1) - if yesterday in result: + if result[as_of] is None and yesterday in result: result[as_of] = result[yesterday] return result