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)
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: