"""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()