from __future__ import annotations import unittest from datetime import datetime from mr_reachy.medication import ( build_plan, interval_dose_times, is_confirmation_intent, parse_medication_instruction, ) class FakeOG: chat_enabled = True def __init__(self) -> None: self.calls: list[str] = [] def complete_json(self, system_prompt: str, user_text: str, *, temperature: float = 0.0) -> str: self.calls.append(system_prompt) if "Extract a medication reminder schedule" in system_prompt: return ( '{"accepted": true, "medication_name": "prednisone", ' '"frequency_per_day": 1, "duration_days": 5, "dose_times": [], ' '"advisory_note": "", "advisory_level": "routine", "reason": "ok"}' ) return ( '{"advisory_level": "caution", ' '"advisory_note": "Small reminder: follow the label exactly and ask your pharmacist whether to take prednisone with food."}' ) class MedicationParsingTest(unittest.TestCase): def test_three_times_daily_without_start_time_asks_only_for_start(self) -> None: result = parse_medication_instruction( "Take metformin three times a day for five days.", now=datetime(2026, 5, 28, 8, 0).astimezone(), ) self.assertFalse(result.accepted) self.assertEqual(result.reason, "Got it, so when are you planning to start the medication?") def test_three_times_daily_from_start_time_schedules_eight_hourly(self) -> None: result = parse_medication_instruction( "Take metformin three times a day for five days starting 8am.", now=datetime(2026, 5, 28, 8, 0).astimezone(), ) self.assertTrue(result.accepted) self.assertIsNotNone(result.plan) assert result.plan is not None self.assertEqual(result.plan.dose_times, ["08:00", "16:00", "00:00"]) self.assertEqual(len(result.plan.doses), 15) def test_one_pill_every_morning_for_week(self) -> None: result = parse_medication_instruction( "Take amoxicillin every morning for a week.", now=datetime(2026, 5, 28, 8, 0).astimezone(), ) self.assertTrue(result.accepted) self.assertIsNotNone(result.plan) assert result.plan is not None self.assertEqual(result.plan.frequency_per_day, 1) self.assertEqual(result.plan.duration_days, 7) self.assertEqual(result.plan.dose_times, ["09:00"]) self.assertEqual(len(result.plan.doses), 7) def test_vague_instruction_is_rejected(self) -> None: result = parse_medication_instruction("Take this whenever I feel bad.") self.assertFalse(result.accepted) self.assertIsNone(result.plan) def test_naproxen_with_ulcer_gets_cross_check_advisory(self) -> None: result = parse_medication_instruction( "I have ulcer and they gave me naproxen twice a day for five days starting now.", now=datetime(2026, 5, 28, 8, 0).astimezone(), ) self.assertTrue(result.accepted) self.assertIsNotNone(result.plan) assert result.plan is not None self.assertEqual(result.plan.medication_name.lower(), "naproxen") self.assertEqual(result.plan.advisory_level, "cross_check") self.assertIn("cross-check", result.plan.advisory_note) def test_naproxen_gets_general_stomach_caution(self) -> None: result = parse_medication_instruction( "Take naproxen twice a day for five days starting now.", now=datetime(2026, 5, 28, 8, 0).astimezone(), ) self.assertTrue(result.accepted) self.assertIsNotNone(result.plan) assert result.plan is not None self.assertEqual(result.plan.advisory_level, "caution") self.assertIn("stomach", result.plan.advisory_note) def test_at_night_maps_to_single_night_dose(self) -> None: result = parse_medication_instruction( "Take cetirizine at night for five days.", now=datetime(2026, 5, 28, 8, 0).astimezone(), ) self.assertTrue(result.accepted) self.assertIsNotNone(result.plan) assert result.plan is not None self.assertEqual(result.plan.frequency_per_day, 1) self.assertEqual(result.plan.dose_times, ["21:00"]) def test_interval_dose_times(self) -> None: self.assertEqual(interval_dose_times("08:00", 3), ["08:00", "16:00", "00:00"]) self.assertEqual(interval_dose_times("06:00", 4), ["06:00", "12:00", "18:00", "00:00"]) def test_og_advisory_is_not_limited_to_nsaids(self) -> None: og = FakeOG() result = parse_medication_instruction( "They gave me prednisone once a day for five days starting now.", og=og, now=datetime(2026, 5, 28, 8, 0).astimezone(), ) self.assertTrue(result.accepted) self.assertIsNotNone(result.plan) assert result.plan is not None self.assertEqual(result.plan.medication_name.lower(), "prednisone") self.assertEqual(result.plan.advisory_level, "caution") self.assertIn("prednisone", result.plan.advisory_note) self.assertEqual(len(og.calls), 1) def test_confirmation_intent(self) -> None: self.assertTrue(is_confirmation_intent("I took it")) self.assertTrue(is_confirmation_intent("yes I took my medicine")) self.assertFalse(is_confirmation_intent("what time is my medicine")) def test_build_plan_rejects_vague_name(self) -> None: with self.assertRaises(ValueError): build_plan( raw_instruction="Take this 3 times a day for 5 days", medication_name="this", frequency_per_day=3, duration_days=5, ) if __name__ == "__main__": unittest.main()