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
This commit is contained in:
2026-02-19 20:12:22 -06:00
parent e9a2f96f91
commit a19e30db68

View File

@@ -26,6 +26,17 @@ def _user_now_for(user_uuid):
return tz.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(): def check_medication_reminders():
"""Check for medications due now and send notifications.""" """Check for medications due now and send notifications."""
try: try:
@@ -47,6 +58,7 @@ def check_medication_reminders():
current_day = now.strftime("%a").lower() current_day = now.strftime("%a").lower()
today = now.date() today = now.date()
today_str = today.isoformat() today_str = today.isoformat()
user_tz = tz.tz_for_user(user_uuid)
for med in user_med_list: for med in user_med_list:
freq = med.get("frequency", "daily") freq = med.get("frequency", "daily")
@@ -83,13 +95,13 @@ def check_medication_reminders():
if current_time not in times: if current_time not in times:
continue continue
# Already taken today? Check by created_at date # Already taken today? Check by created_at date in user's timezone
logs = postgres.select( logs = postgres.select(
"med_logs", where={"medication_id": med["id"], "action": "taken"} "med_logs", where={"medication_id": med["id"], "action": "taken"}
) )
already_taken = any( already_taken = any(
log.get("scheduled_time") == current_time 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 for log in logs
) )
if already_taken: if already_taken:
@@ -134,7 +146,9 @@ def check_routine_reminders():
if isinstance(start, date_type) if isinstance(start, date_type)
else datetime.strptime(str(start), "%Y-%m-%d").date() 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 continue
else: else:
continue continue
@@ -213,6 +227,7 @@ def check_adaptive_medication_reminders():
now = _user_now_for(user_uuid) now = _user_now_for(user_uuid)
current_time = now.strftime("%H:%M") current_time = now.strftime("%H:%M")
today = now.date() today = now.date()
user_tz = tz.tz_for_user(user_uuid)
# Check if adaptive timing is enabled # Check if adaptive timing is enabled
settings = adaptive_meds.get_adaptive_settings(user_uuid) settings = adaptive_meds.get_adaptive_settings(user_uuid)
@@ -277,7 +292,7 @@ def check_adaptive_medication_reminders():
if check_time != current_time: if check_time != current_time:
continue continue
# Check if already taken # Check if already taken for this specific time slot today
logs = postgres.select( logs = postgres.select(
"med_logs", "med_logs",
where={ where={
@@ -288,7 +303,9 @@ def check_adaptive_medication_reminders():
) )
already_taken = any( 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 for log in logs
) )
@@ -352,7 +369,9 @@ def check_nagging():
}, },
) )
except Exception as e: 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 # Table may not exist yet
continue continue
@@ -360,7 +379,9 @@ def check_nagging():
if not schedules: if not schedules:
if not _is_med_due_today(med, today): if not _is_med_due_today(med, today):
continue 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", []) times = med.get("times", [])
if times: if times:
try: try:
@@ -531,9 +552,7 @@ def _check_per_user_midnight_schedules():
continue continue
times = med.get("times", []) times = med.get("times", [])
if times: if times:
adaptive_meds.create_daily_schedule( adaptive_meds.create_daily_schedule(user_uuid, med["id"], times)
user_uuid, med["id"], times
)
except Exception as e: except Exception as e:
logger.warning( logger.warning(
f"Could not create adaptive schedules for user {user_uuid}: {e}" 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(): def check_task_reminders():
"""Check one-off tasks for advance and at-time reminders.""" """Check one-off tasks for advance and at-time reminders."""
from datetime import timedelta from datetime import timedelta
try: try:
tasks = postgres.select("tasks", where={"status": "pending"}) tasks = postgres.select("tasks", where={"status": "pending"})
if not tasks: if not tasks:
@@ -575,15 +595,24 @@ def check_task_reminders():
# Advance reminder # Advance reminder
if reminder_min > 0 and not task.get("advance_notified"): if reminder_min > 0 and not task.get("advance_notified"):
adv_dt = sched_dt - timedelta(minutes=reminder_min) 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: if user_settings is None:
user_settings = notifications.getNotificationSettings(user_uuid) user_settings = notifications.getNotificationSettings(
user_uuid
)
if user_settings: if user_settings:
msg = f"⏰ In {reminder_min} min: {task['title']}" msg = f"⏰ In {reminder_min} min: {task['title']}"
if task.get("description"): if task.get("description"):
msg += f"{task['description']}" msg += f"{task['description']}"
notifications._sendToEnabledChannels(user_settings, msg, user_uuid=user_uuid) notifications._sendToEnabledChannels(
postgres.update("tasks", {"advance_notified": True}, {"id": task["id"]}) user_settings, msg, user_uuid=user_uuid
)
postgres.update(
"tasks", {"advance_notified": True}, {"id": task["id"]}
)
# At-time reminder # At-time reminder
if sched_date == current_date and sched_hhmm == current_hhmm: if sched_date == current_date and sched_hhmm == current_hhmm:
@@ -593,10 +622,15 @@ def check_task_reminders():
msg = f"📋 Now: {task['title']}" msg = f"📋 Now: {task['title']}"
if task.get("description"): if task.get("description"):
msg += f"\n{task['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( postgres.update(
"tasks", "tasks",
{"status": "notified", "updated_at": datetime.utcnow().isoformat()}, {
"status": "notified",
"updated_at": datetime.utcnow().isoformat(),
},
{"id": task["id"]}, {"id": task["id"]},
) )
except Exception as e: except Exception as e: