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.
This commit is contained in:
Arsham Mirehvandi 2026-09-05 14:11:45 +02:00
parent e588d97e0f
commit 21927133a4
3 changed files with 20 additions and 7 deletions

View File

@ -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) 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` 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 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` 5. **Applied treatments** — products sprayed over `as_of-5 .. as_of`, from `AI_agrosupport_agro_ril_operations*` with `rilop_operation = 10`
6. **LLM** — synthesise 12 anonymised search queries from the subset 6. **LLM** — synthesise 12 anonymised search queries from the subset
7. **Product prefilter** — organic / crop / disease / FASE rules against the batch-wide, in-memory `products` index 7. **Product prefilter** — organic / crop / disease / FASE rules against the batch-wide, in-memory `products` index

View File

@ -20,6 +20,20 @@ METRIC_KEYS = (
"potential_evapotranspiration_mm", "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( def _day_entry(
day: date, day: date,
@ -32,7 +46,7 @@ def _day_entry(
metrics = weather.get(day, {}) metrics = weather.get(day, {})
entry: dict[str, Any] = {"date": format_date(day)} entry: dict[str, Any] = {"date": format_date(day)}
for key in METRIC_KEYS: 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["phenology_phase"] = phenology.get(day)
entry["disease_forecast"] = forecasts.get(day) entry["disease_forecast"] = forecasts.get(day)
# Treatments can only have been applied on or before as_of; future days # 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, {}) metrics = weather.get(day, {})
entry: dict[str, Any] = {"date": format_date(day)} entry: dict[str, Any] = {"date": format_date(day)}
for key in METRIC_KEYS: for key in METRIC_KEYS:
entry[key] = metrics.get(key) entry[key] = _metric_value(key, metrics.get(key))
weather_forecasts.append(entry) weather_forecasts.append(entry)
return { return {

View File

@ -56,8 +56,7 @@ def load_phenology(
Observations with rilfeno_date <= as_of are carried forward to each day Observations with rilfeno_date <= as_of are carried forward to each day
(latest observation at or before that day). Future days are always null. (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 If today's phase is null after carry-forward, yesterday's phase is used.
observation as of as_of-1, or the latest available before as_of).
""" """
window = build_window(as_of) window = build_window(as_of)
rows = fetch_all(conn, _FENO_SQL, (field_id, as_of)) rows = fetch_all(conn, _FENO_SQL, (field_id, as_of))
@ -92,9 +91,9 @@ def load_phenology(
_, ff_id = applicable[-1] _, ff_id = applicable[-1]
result[day] = name_by_id.get(ff_id) 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) 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] result[as_of] = result[yesterday]
return result return result