diff --git a/core/adaptive_meds.py b/core/adaptive_meds.py index 17be95b..ef9845d 100644 --- a/core/adaptive_meds.py +++ b/core/adaptive_meds.py @@ -395,9 +395,11 @@ def should_send_nag( int(scheduled_time[:2]), int(scheduled_time[3:5]), ) - diff_minutes = abs( - (log_hour * 60 + log_min) - (sched_hour * 60 + sched_min) - ) + log_mins = log_hour * 60 + log_min + sched_mins = sched_hour * 60 + sched_min + diff_minutes = abs(log_mins - sched_mins) + # Handle midnight wraparound (e.g. 23:00 vs 00:42) + diff_minutes = min(diff_minutes, 1440 - diff_minutes) if diff_minutes <= proximity_window: return False, f"Already {action} today" @@ -463,8 +465,33 @@ def create_daily_schedule(user_uuid: str, med_id: str, base_times: List[str], re # Calculate adjusted times adjusted_times = calculate_adjusted_times(user_uuid, base_times) + # Check recent med logs to skip doses already taken/skipped. + # Handles cross-midnight: if adaptive offset shifts 23:00 → 00:42 today, + # but the user already took the 23:00 dose last night, don't schedule it. + user_tz = tz_for_user(user_uuid) + yesterday = today - timedelta(days=1) + recent_logs = postgres.select("med_logs", {"medication_id": med_id, "user_uuid": user_uuid}) + taken_base_times = set() + for log in recent_logs: + if log.get("action") not in ("taken", "skipped"): + continue + created_at = log.get("created_at") + if not created_at: + continue + if created_at.tzinfo is None: + created_at = created_at.replace(tzinfo=timezone.utc) + log_date = created_at.astimezone(user_tz).date() + if log_date not in (today, yesterday): + continue + log_sched = _normalize_time(log.get("scheduled_time")) + if log_sched: + taken_base_times.add(log_sched) + # Create schedule records for each time for base_time, (adjusted_time, offset) in zip(base_times, adjusted_times): + if base_time in taken_base_times: + continue + data = { "id": str(uuid.uuid4()), "user_uuid": user_uuid, diff --git a/scheduler/daemon.py b/scheduler/daemon.py index a152c5a..16754bd 100644 --- a/scheduler/daemon.py +++ b/scheduler/daemon.py @@ -342,14 +342,20 @@ def check_adaptive_medication_reminders(): if already_handled: continue - # Send notification + # Send notification — display base_time to the user + base_display = sched.get("base_time") + if isinstance(base_display, time_type): + base_display = base_display.strftime("%H:%M") + elif base_display is not None: + base_display = str(base_display)[:5] + user_settings = notifications.getNotificationSettings(user_uuid) if user_settings: offset = sched.get("adjustment_minutes", 0) if offset > 0: - msg = f"⏰ Time to take {med['name']} ({med['dosage']} {med['unit']}) · {check_time} (adjusted +{offset}min)" + msg = f"⏰ Time to take {med['name']} ({med['dosage']} {med['unit']}) · {base_display} (adjusted +{offset}min)" else: - msg = f"⏰ Time to take {med['name']} ({med['dosage']} {med['unit']}) · {check_time}" + msg = f"⏰ Time to take {med['name']} ({med['dosage']} {med['unit']}) · {base_display}" notifications._sendToEnabledChannels( user_settings, msg, user_uuid=user_uuid @@ -443,12 +449,9 @@ def check_nagging(): if not should_nag: continue - # Get the time to display - adaptive_enabled = settings.get("adaptive_timing_enabled") - if adaptive_enabled: - display_time = sched.get("adjusted_time") - else: - display_time = sched.get("base_time") + # Always display the base_time (the user's actual dose time), + # not the internal adjusted_time used for scheduling. + display_time = sched.get("base_time") # Normalize TIME objects for display if isinstance(display_time, time_type): display_time = display_time.strftime("%H:%M")