From a19e30db68f6e153c85cb1916adf93086ceb8939 Mon Sep 17 00:00:00 2001 From: chelsea Date: Thu, 19 Feb 2026 20:12:22 -0600 Subject: [PATCH] Fix medication reminders for already-taken meds - Convert created_at from UTC to user's local timezone before comparing dates - Add scheduled_time check in adaptive reminders (was only checking if any dose was taken today) - Prevents duplicate reminders when user is in a different timezone than UTC --- scheduler/daemon.py | 66 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/scheduler/daemon.py b/scheduler/daemon.py index 7d5a57d..2c1968b 100644 --- a/scheduler/daemon.py +++ b/scheduler/daemon.py @@ -26,6 +26,17 @@ def _user_now_for(user_uuid): return tz.user_now_for(user_uuid) +def _utc_to_local_date(created_at, user_tz): + """Convert a DB created_at (naive UTC datetime) to a local date string YYYY-MM-DD.""" + if created_at is None: + return "" + if isinstance(created_at, datetime): + if created_at.tzinfo is None: + created_at = created_at.replace(tzinfo=timezone.utc) + return created_at.astimezone(user_tz).date().isoformat() + return str(created_at)[:10] + + def check_medication_reminders(): """Check for medications due now and send notifications.""" try: @@ -47,6 +58,7 @@ def check_medication_reminders(): current_day = now.strftime("%a").lower() today = now.date() today_str = today.isoformat() + user_tz = tz.tz_for_user(user_uuid) for med in user_med_list: freq = med.get("frequency", "daily") @@ -83,13 +95,13 @@ def check_medication_reminders(): if current_time not in times: continue - # Already taken today? Check by created_at date + # Already taken today? Check by created_at date in user's timezone logs = postgres.select( "med_logs", where={"medication_id": med["id"], "action": "taken"} ) already_taken = any( log.get("scheduled_time") == current_time - and str(log.get("created_at", ""))[:10] == today_str + and _utc_to_local_date(log.get("created_at"), user_tz) == today_str for log in logs ) if already_taken: @@ -134,7 +146,9 @@ def check_routine_reminders(): if isinstance(start, date_type) else datetime.strptime(str(start), "%Y-%m-%d").date() ) - if (today - start_d).days < 0 or (today - start_d).days % interval != 0: + if (today - start_d).days < 0 or ( + today - start_d + ).days % interval != 0: continue else: continue @@ -213,6 +227,7 @@ def check_adaptive_medication_reminders(): now = _user_now_for(user_uuid) current_time = now.strftime("%H:%M") today = now.date() + user_tz = tz.tz_for_user(user_uuid) # Check if adaptive timing is enabled settings = adaptive_meds.get_adaptive_settings(user_uuid) @@ -277,7 +292,7 @@ def check_adaptive_medication_reminders(): if check_time != current_time: continue - # Check if already taken + # Check if already taken for this specific time slot today logs = postgres.select( "med_logs", where={ @@ -288,7 +303,9 @@ def check_adaptive_medication_reminders(): ) already_taken = any( - str(log.get("created_at", ""))[:10] == today.isoformat() + log.get("scheduled_time") == check_time + and _utc_to_local_date(log.get("created_at"), user_tz) + == today.isoformat() for log in logs ) @@ -352,7 +369,9 @@ def check_nagging(): }, ) except Exception as e: - logger.warning(f"Could not query medication_schedules for {med_id}: {e}") + logger.warning( + f"Could not query medication_schedules for {med_id}: {e}" + ) # Table may not exist yet continue @@ -360,7 +379,9 @@ def check_nagging(): if not schedules: if not _is_med_due_today(med, today): continue - logger.info(f"No schedules found for medication {med_id}, attempting to create") + logger.info( + f"No schedules found for medication {med_id}, attempting to create" + ) times = med.get("times", []) if times: try: @@ -531,9 +552,7 @@ def _check_per_user_midnight_schedules(): continue times = med.get("times", []) if times: - adaptive_meds.create_daily_schedule( - user_uuid, med["id"], times - ) + adaptive_meds.create_daily_schedule(user_uuid, med["id"], times) except Exception as e: logger.warning( f"Could not create adaptive schedules for user {user_uuid}: {e}" @@ -543,6 +562,7 @@ def _check_per_user_midnight_schedules(): def check_task_reminders(): """Check one-off tasks for advance and at-time reminders.""" from datetime import timedelta + try: tasks = postgres.select("tasks", where={"status": "pending"}) if not tasks: @@ -575,15 +595,24 @@ def check_task_reminders(): # Advance reminder if reminder_min > 0 and not task.get("advance_notified"): adv_dt = sched_dt - timedelta(minutes=reminder_min) - if adv_dt.date() == current_date and adv_dt.strftime("%H:%M") == current_hhmm: + if ( + adv_dt.date() == current_date + and adv_dt.strftime("%H:%M") == current_hhmm + ): if user_settings is None: - user_settings = notifications.getNotificationSettings(user_uuid) + user_settings = notifications.getNotificationSettings( + user_uuid + ) if user_settings: msg = f"⏰ In {reminder_min} min: {task['title']}" if task.get("description"): msg += f" — {task['description']}" - notifications._sendToEnabledChannels(user_settings, msg, user_uuid=user_uuid) - postgres.update("tasks", {"advance_notified": True}, {"id": task["id"]}) + notifications._sendToEnabledChannels( + user_settings, msg, user_uuid=user_uuid + ) + postgres.update( + "tasks", {"advance_notified": True}, {"id": task["id"]} + ) # At-time reminder if sched_date == current_date and sched_hhmm == current_hhmm: @@ -593,10 +622,15 @@ def check_task_reminders(): msg = f"📋 Now: {task['title']}" if task.get("description"): msg += f"\n{task['description']}" - notifications._sendToEnabledChannels(user_settings, msg, user_uuid=user_uuid) + notifications._sendToEnabledChannels( + user_settings, msg, user_uuid=user_uuid + ) postgres.update( "tasks", - {"status": "notified", "updated_at": datetime.utcnow().isoformat()}, + { + "status": "notified", + "updated_at": datetime.utcnow().isoformat(), + }, {"id": task["id"]}, ) except Exception as e: