AI_Agro_Support/tests/test_advice_summary.py
Arsham Mirehvandi 8a6d48e5a1 Enhance advice generation and context handling
- Updated README.md to reflect changes in the `json_for_advice_generation` structure, including the addition of `date_of_today` and clarification of `last_advice` fields.
- Modified job.py to load and pass the issuance date of the last advice.
- Enhanced advice_context.py to include the issuance date in the last advice retrieval.
- Updated assemble.py to include `date_of_today` in the JSON assembly for advice generation.
- Improved advice.py to sanitize the `advice_summary` by removing relative date references and ensuring compliance with new guidelines.
2026-09-08 09:40:34 +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,
"apply_treatment": 1,
"treatments": ["PROFILER"],
"dosage": ["3.0 kg/ha"],
"apply_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()