Refactor disease prefiltering logic and update documentation

- Updated README.md to clarify the conditions for the `INCUBAZPRIMARIA` rule in the context of disease forecasting.
- Modified disease.py to adjust the order of prefilter rules, ensuring `INCUBAZPRIMARIA` is evaluated correctly.
- Enhanced product prefiltering logic in products.py to incorporate the new conditions for `incubazprimaria` while removing redundant checks.
This commit is contained in:
Arsham Mirehvandi 2026-09-07 15:20:21 +02:00
parent 1e696405ad
commit 28250d6608
3 changed files with 18 additions and 18 deletions

View File

@ -356,11 +356,11 @@ Run once per `(field, disease)` job by `pipeline/job.py::run_job`:
| Rule | Predicates |
|---|---|
| `observation` true on `as_of` or the past 5 days | `systemicity` in `{systemic, mixed}` and `eradicant_action = 1` |
| `INCUBAZPRIMARIA` / `INCUBAZPRIMARIA_<value>` today or in the past 5 days | `systemicity` in `{systemic, mixed}` and `curative_action = 1` and `eradicant_action = 0` |
| `FASE2` anywhere and no `FASE5` anywhere | `systemicity == contact` and `preventive_action = 1` and `curative_action = 0` and `eradicant_action = 0` |
| `FASE5` both past/today and future | `systemicity == mixed` and `preventive_action = 1` and `curative_action = 1` and `eradicant_action = 0` |
| `FASE5` in the future only | same predicates as the `FASE2`-only rule |
| `FASE5` today or in the past only | `systemicity` in `{contact, mixed}` and `preventive_action = 1` and `curative_action = 0` and `eradicant_action = 0` |
| `INCUBAZPRIMARIA` / `INCUBAZPRIMARIA_<value>` today or in the past 5 days | `systemicity` in `{systemic, mixed}` and `curative_action = 1` and `eradicant_action = 0` |
| none of the above | no FASE-specific cut; organic/crop/disease gates only |
9. **Vector search** — Weaviate `near_text` on `ProductProfile`, filtered server-side to the allowlisted `product_id`s

View File

@ -220,11 +220,11 @@ def select_prefilter_rule(
match wins:
1. `observation` True on as_of or any of the past 5 days -> "observation"
2. FASE2 anywhere in the window AND no FASE5 anywhere -> "fase2_only"
3. FASE5 present both past/today AND future -> "fase5_both"
4. FASE5 present in the future only -> "fase5_future"
5. FASE5 present today or in the past only -> "fase5_past"
6. INCUBAZPRIMARIA today or in the past 5 days -> "incubazprimaria"
2. INCUBAZPRIMARIA today or in the past 5 days -> "incubazprimaria"
3. FASE2 anywhere in the window AND no FASE5 anywhere -> "fase2_only"
4. FASE5 present both past/today AND future -> "fase5_both"
5. FASE5 present in the future only -> "fase5_future"
6. FASE5 present today or in the past only -> "fase5_past"
7. otherwise -> None
None means the product prefilter applies only the crop / disease /
@ -236,6 +236,9 @@ def select_prefilter_rule(
if any(observation_by_day.get(d, False) for d in past_today):
return "observation"
if any(_is_incubazprimaria(forecasts_by_day.get(d)) for d in past_today):
return "incubazprimaria"
fase2_anywhere = any(v == "FASE2" for v in forecasts_by_day.values())
fase5_anywhere = any(v == "FASE5" for v in forecasts_by_day.values())
@ -252,7 +255,4 @@ def select_prefilter_rule(
if fase5_past_today:
return "fase5_past"
if any(_is_incubazprimaria(forecasts_by_day.get(d)) for d in past_today):
return "incubazprimaria"
return None

View File

@ -138,6 +138,8 @@ def prefilter_products(
prefilter rule (see pipeline.stages.disease.select_prefilter_rule).
observation -> systemicity in {systemic, mixed} AND eradicant_action = 1
incubazprimaria -> systemicity in {systemic, mixed} AND curative_action = 1
AND eradicant_action = 0
fase2_only -> systemicity == contact AND preventive_action = 1
AND curative_action = 0 AND eradicant_action = 0
fase5_both -> systemicity == mixed AND preventive_action = 1
@ -145,8 +147,6 @@ def prefilter_products(
fase5_future -> same predicates as fase2_only
fase5_past -> systemicity in {contact, mixed} AND preventive_action = 1
AND curative_action = 0 AND eradicant_action = 0
incubazprimaria -> systemicity in {systemic, mixed} AND curative_action = 1
AND eradicant_action = 0
None -> no FASE-specific cut; crop/disease/organic gates only
"""
candidates: list[ProductCandidate] = []
@ -162,6 +162,13 @@ def prefilter_products(
continue
if not row.eradicant_action:
continue
elif rule == "incubazprimaria":
if systemicity not in {"systemic", "mixed"}:
continue
if not row.curative_action:
continue
if row.eradicant_action:
continue
elif rule in ("fase2_only", "fase5_future"):
if systemicity != "contact":
continue
@ -189,13 +196,6 @@ def prefilter_products(
continue
if row.eradicant_action:
continue
elif rule == "incubazprimaria":
if systemicity not in {"systemic", "mixed"}:
continue
if not row.curative_action:
continue
if row.eradicant_action:
continue
# rule is None: no FASE-specific cut, only the gates above apply.
candidates.append(row.as_candidate())