AI_Agro_Support/tests/test_advice_summary.py
Arsham Mirehvandi d42fe18a52 Refactor advisory generation and update database schema
- Updated `config.yaml` to change the advice storage table from `advice` to `advisories` and adjusted the `thinking_budget` from 11264 to 9216.
- Modified `README.md` to reflect the new `advisories` table in the advisory generation process.
- Refactored SQL queries in `batch.py`, `job.py`, and `advice_context.py` to align with the new table structure.
- Enhanced the advice generation logic in `advice.py` to accommodate changes in the data model, including updates to the JSON structure for advice output.
- Updated relevant functions and documentation to ensure consistency across the codebase regarding the new advisory schema.
2026-09-30 14:43:08 +02:00

139 lines
5.9 KiB
Python

"""Unit tests for advice_summary sanitization and parse_advice wiring."""
from __future__ import annotations
import json
import unittest
from datetime import date, datetime
from pipeline.errors import PipelineError
from pipeline.stages.advice import SUMMARY_MAX_CHARS, parse_advice
from pipeline.stages.advice_summary import sanitize_advice_summary
_APR_13 = date(2026, 4, 13)
_APR13_SUMMARY = (
"Attenzione: il modello segnala un'infezione primaria di peronospora "
"(FASE5) per oggi, 13 aprile 2026. La vite si trova in una fase fenologica "
"di elevata suscettibilità (BBCH 15) e non risulta protetta. È "
"indispensabile intervenire per bloccare l'infezione in corso. Si raccomanda "
"di effettuare un trattamento con azione curativa domani, 14 aprile 2026. "
"Si consiglia l'impiego del prodotto PROFILER, dotato di attività sistemica "
"e curativa, alla dose di 3.0 kg/ha. Questo intervento è fondamentale per "
"impedire che l'infezione si stabilisca e per prevenire l'inizio "
"dell'epidemia stagionale. Seguire attentamente le istruzioni in etichetta."
)
def _advice_json(*, summary: str, full_advice: str = "Testo completo.") -> str:
return json.dumps(
{
"full_advice": full_advice,
"advice_summary": summary,
"is_treatment_recommended": 1,
"recommended_product_names": ["PROFILER"],
"recommended_doses": ["3.0 kg/ha"],
"recommended_application_date": "2026-04-14",
},
ensure_ascii=False,
)
class SanitizeAdviceSummaryTests(unittest.TestCase):
def test_apr13_summary_strips_prefix_deictics_and_etichetta(self) -> None:
cleaned = sanitize_advice_summary(_APR13_SUMMARY, _APR_13)
self.assertFalse(cleaned.lower().startswith("attenzione"))
self.assertNotIn("oggi", cleaned.casefold())
self.assertNotIn("domani", cleaned.casefold())
self.assertNotIn("etichetta", cleaned.casefold())
self.assertIn("13 aprile 2026", cleaned)
self.assertIn("14 aprile 2026", cleaned)
self.assertIn("PROFILER", cleaned)
def test_english_today_tomorrow_before_date(self) -> None:
text = "Spray today, Apr 13th and again tomorrow, Apr 15th."
cleaned = sanitize_advice_summary(text, _APR_13)
self.assertEqual(cleaned, "Spray Apr 13th and again Apr 15th.")
self.assertNotIn("today", cleaned.casefold())
self.assertNotIn("tomorrow", cleaned.casefold())
def test_standalone_oggi_and_domani(self) -> None:
text = "Le condizioni di oggi restano favorevoli e domani peggiorano."
cleaned = sanitize_advice_summary(text, _APR_13)
self.assertEqual(
cleaned,
"Le condizioni di 13-04-2026 restano favorevoli e 14-04-2026 peggiorano.",
)
def test_standalone_ieri(self) -> None:
cleaned = sanitize_advice_summary("Pioggia ieri.", _APR_13)
self.assertEqual(cleaned, "Pioggia 12-04-2026.")
def test_etichetta_closer_variants(self) -> None:
variants = (
"Intervenire subito. Seguire attentamente le istruzioni in etichetta.",
"Intervenire subito. Seguire attentamente le istruzioni riportate in etichetta.",
"Intervenire subito. Rispettare scrupolosamente le indicazioni riportate in etichetta.",
)
for text in variants:
with self.subTest(text=text):
cleaned = sanitize_advice_summary(text, _APR_13)
self.assertEqual(cleaned, "Intervenire subito.")
self.assertNotIn("etichetta", cleaned.casefold())
def test_etichetta_with_real_content_is_kept(self) -> None:
text = (
"Non superare i 2 trattamenti per stagione previsti in etichetta "
"per questo formulato."
)
cleaned = sanitize_advice_summary(text, _APR_13)
self.assertEqual(cleaned, text)
def test_prefix_only(self) -> None:
cleaned = sanitize_advice_summary(
"Attenzione: trattare il 13 aprile 2026.",
_APR_13,
)
self.assertEqual(cleaned, "trattare il 13 aprile 2026.")
def test_already_clean_is_idempotent(self) -> None:
text = "Trattare il 13 aprile 2026 con PROFILER a 3.0 kg/ha."
once = sanitize_advice_summary(text, _APR_13)
twice = sanitize_advice_summary(once, _APR_13)
self.assertEqual(once, text)
self.assertEqual(twice, text)
def test_apr13_summary_is_idempotent(self) -> None:
once = sanitize_advice_summary(_APR13_SUMMARY, _APR_13)
twice = sanitize_advice_summary(once, _APR_13)
self.assertEqual(once, twice)
def test_datetime_issued_on(self) -> None:
cleaned = sanitize_advice_summary("Trattare oggi.", datetime(2026, 4, 13, 8, 0))
self.assertEqual(cleaned, "Trattare 13-04-2026.")
class ParseAdviceSanitizeTests(unittest.TestCase):
def test_parse_advice_sanitizes_summary(self) -> None:
result = parse_advice(_advice_json(summary=_APR13_SUMMARY), as_of=_APR_13)
self.assertNotIn("oggi", result.advice_summary.casefold())
self.assertFalse(result.advice_summary.lower().startswith("attenzione"))
self.assertIn("13 aprile 2026", result.advice_summary)
def test_empty_after_sanitize_raises(self) -> None:
summary = "Attenzione: Seguire attentamente le istruzioni in etichetta."
with self.assertRaises(PipelineError) as ctx:
parse_advice(_advice_json(summary=summary), as_of=_APR_13)
self.assertIn("empty after sanitization", str(ctx.exception))
def test_truncation_after_sanitize(self) -> None:
body = "Trattare il 13 aprile 2026. " * 80
self.assertGreater(len(body), SUMMARY_MAX_CHARS)
result = parse_advice(_advice_json(summary=body), as_of=_APR_13)
self.assertEqual(len(result.advice_summary), SUMMARY_MAX_CHARS)
self.assertTrue(result.advice_summary.startswith("Trattare il 13 aprile 2026."))
if __name__ == "__main__":
unittest.main()