Spaces:
Sleeping
Sleeping
| """Tests for weekly report scheduling and generation.""" | |
| import unittest | |
| from datetime import date, datetime, timedelta | |
| from pathlib import Path | |
| from unittest.mock import MagicMock, patch | |
| from db import queries | |
| from db.database import close_connection, initialize_database, set_db_path | |
| from vital_types.db import FoodLogEntry, ProfileInput, WeeklyReport | |
| from core.weekly_startup import ( | |
| is_weekly_report_due, | |
| pending_weekly_report_start, | |
| ) | |
| from llm.weekly_report import ( | |
| validate_weekly_report_response, | |
| generate_weekly_report, | |
| ) | |
| from llm import daily_schedule as daily_schedule_module | |
| class WeeklyReportTests(unittest.TestCase): | |
| """Cover weekly report validation, due logic, and persistence.""" | |
| def setUp(self) -> None: | |
| """Use an isolated database.""" | |
| import tempfile | |
| self.temp_dir = tempfile.TemporaryDirectory() | |
| db_path = Path(self.temp_dir.name) / "test_weekly.db" | |
| close_connection() | |
| set_db_path(db_path) | |
| initialize_database() | |
| queries.save_profile( | |
| ProfileInput( | |
| name="Eddy", | |
| age=30, | |
| city="Port Harcourt", | |
| profession="Engineer", | |
| goal="Manage wellness", | |
| conditions=["sickle cell"], | |
| medications=[], | |
| triggers=["dehydration"], | |
| wake_time="07:00", | |
| sleep_time="23:00", | |
| desk_worker=True, | |
| exercise_level="light", | |
| dietary_notes="", | |
| local_foods="beans, plantain", | |
| ) | |
| ) | |
| queries.set_onboarding_complete(True) | |
| queries.save_weekly_check_structure( | |
| { | |
| "report_day": "Sunday", | |
| "report_time": "20:00", | |
| "replan_day": "Sunday", | |
| "replan_time": "20:30", | |
| } | |
| ) | |
| def tearDown(self) -> None: | |
| """Close the database.""" | |
| close_connection() | |
| self.temp_dir.cleanup() | |
| def _seed_report_for_week(self, week_start: date) -> None: | |
| """Insert a placeholder report so catch-up logic skips that week.""" | |
| queries.insert_weekly_report( | |
| WeeklyReport( | |
| week_start=week_start, | |
| report_text="Seeded report.", | |
| water_goals_hit=0, | |
| medication_adherence=0.0, | |
| exercises_completed=0, | |
| generated_at=datetime.now().astimezone(), | |
| ) | |
| ) | |
| def test_validate_weekly_report_response(self) -> None: | |
| """Valid JSON should produce a narrative with highlights.""" | |
| text = validate_weekly_report_response( | |
| { | |
| "report_text": "You had a steady week with good hydration.", | |
| "highlights": ["Logged lunch twice", "Pain stayed low"], | |
| "focus_next_week": "Keep lunch logging consistent.", | |
| } | |
| ) | |
| self.assertIn("steady week", text) | |
| self.assertIn("Highlights", text) | |
| self.assertIn("Focus next week", text) | |
| def test_pending_week_after_sunday_deadline(self) -> None: | |
| """A report should be pending after Sunday 20:00 if none exists.""" | |
| week_start = queries.monday_of_week(date(2026, 6, 8)) | |
| prior_week = week_start - timedelta(days=7) | |
| self._seed_report_for_week(prior_week) | |
| sunday_evening = datetime(2026, 6, 14, 21, 0) | |
| self.assertTrue(is_weekly_report_due(week_start, sunday_evening)) | |
| pending = pending_weekly_report_start(sunday_evening) | |
| self.assertEqual(pending, week_start) | |
| def test_no_pending_before_sunday_deadline(self) -> None: | |
| """No report should be pending before this week's Sunday deadline.""" | |
| week_start = queries.monday_of_week(date(2026, 6, 8)) | |
| prior_week = week_start - timedelta(days=7) | |
| self._seed_report_for_week(prior_week) | |
| sunday_morning = datetime(2026, 6, 14, 10, 0) | |
| pending = pending_weekly_report_start(sunday_morning) | |
| self.assertIsNone(pending) | |
| def test_generate_weekly_report_fallback_only(self) -> None: | |
| """fallback_only should skip the LLM entirely.""" | |
| week_start = queries.monday_of_week(date.today()) | |
| report = generate_weekly_report(week_start, force=True, fallback_only=True) | |
| self.assertIsNotNone(report) | |
| assert report is not None | |
| self.assertIn("week in numbers", report.report_text.lower()) | |
| def test_generate_weekly_report_fallback_on_llm_failure(self) -> None: | |
| """Template fallback should still persist a report when the LLM fails.""" | |
| week_start = queries.monday_of_week(date.today()) | |
| mock_client = MagicMock() | |
| mock_client.generate_onboarding_json.side_effect = ValueError("LLM unavailable") | |
| with patch("llm.weekly_report.get_llm_client", return_value=mock_client): | |
| report = generate_weekly_report(week_start, force=True) | |
| self.assertIsNotNone(report) | |
| assert report is not None | |
| self.assertIn("week in numbers", report.report_text.lower()) | |
| def test_ensure_weekly_report_force(self) -> None: | |
| """Force mode should generate even when the Sunday deadline has not passed.""" | |
| from core.weekly_startup import ensure_weekly_report | |
| wednesday = datetime(2026, 6, 10, 12, 0) | |
| mock_client = MagicMock() | |
| mock_client.generate_onboarding_json.return_value = { | |
| "report_text": "A solid mid-week test report with enough length to pass validation checks.", | |
| "highlights": ["Stayed hydrated"], | |
| "focus_next_week": "Log one meal per day.", | |
| } | |
| with patch("llm.weekly_report.get_llm_client", return_value=mock_client): | |
| generated = ensure_weekly_report(force=True, now=wednesday) | |
| self.assertTrue(generated) | |
| week_start = queries.monday_of_week(wednesday.date()) | |
| self.assertIsNotNone(queries.get_weekly_report(week_start)) | |
| def test_generate_weekly_report_with_mock_llm(self) -> None: | |
| """Mocked LLM output should persist a weekly report row.""" | |
| week_start = queries.monday_of_week(date.today()) | |
| queries.insert_food_log( | |
| FoodLogEntry( | |
| date=date.today(), | |
| meal_type="lunch", | |
| food_description="beans and plantain", | |
| logged_at=datetime.now().astimezone(), | |
| ) | |
| ) | |
| mock_client = MagicMock() | |
| mock_client.generate_onboarding_json.return_value = { | |
| "report_text": "Eddy, you logged one meal and stayed consistent this week.", | |
| "highlights": ["Logged lunch with beans and plantain"], | |
| "focus_next_week": "Log dinner twice next week.", | |
| } | |
| with patch("llm.weekly_report.get_llm_client", return_value=mock_client): | |
| report = generate_weekly_report(week_start, force=True) | |
| self.assertIsNotNone(report) | |
| assert report is not None | |
| self.assertIn("beans and plantain", report.report_text) | |
| stored = queries.get_weekly_report(week_start) | |
| self.assertIsNotNone(stored) | |
| def test_daily_schedule_context_includes_prior_week_meals(self) -> None: | |
| """Daily planner context should list meals eaten earlier in the week.""" | |
| today = date.today() | |
| monday = queries.monday_of_week(today) | |
| if today <= monday: | |
| self.skipTest("Need a mid-week date to test prior-week meals.") | |
| queries.insert_food_log( | |
| FoodLogEntry( | |
| date=monday, | |
| meal_type="lunch", | |
| food_description="yam and egg", | |
| logged_at=datetime.now().astimezone(), | |
| ) | |
| ) | |
| profile = queries.get_profile() | |
| assert profile is not None | |
| context = daily_schedule_module.build_daily_schedule_context(profile, today) | |
| self.assertIn("yam and egg", context) | |
| self.assertIn("earlier this week", context) | |
| if __name__ == "__main__": | |
| unittest.main() | |