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)
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 12 anonymised search queries from the subset
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",
)
_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 {

View File

@ -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