Fix issues #6, #7, #11, #12, #13: med reminders, push notifications, auth persistence, scheduling conflicts

- Fix TIME object vs string comparison in scheduler preventing adaptive med
  reminders from ever firing (#12, #6)
- Add frequency filtering to midnight schedule creation for every_n_days meds
- Require start_date and interval_days for every_n_days medications
- Add refresh token support (30-day) to API and bot for persistent sessions (#13)
- Add "trusted device" checkbox to frontend login for long-lived sessions (#7)
- Auto-refresh expired tokens in both bot (apiRequest) and frontend (api.ts)
- Restore bot sessions from cache on restart using refresh tokens
- Duration-aware routine scheduling conflict detection (#11)
- Add conflict check when starting routine sessions against medication times
- Add diagnostic logging to notification delivery channels

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 13:05:48 -06:00
parent 6850abf7d2
commit d4adbde3df
10 changed files with 474 additions and 69 deletions

View File

@@ -5,9 +5,9 @@ Override poll_callback() with your domain-specific logic.
"""
import os
import time
import time as time_module
import logging
from datetime import datetime, timezone, timedelta
from datetime import datetime, timezone, timedelta, time as time_type
import core.postgres as postgres
import core.notifications as notifications
@@ -249,6 +249,12 @@ def check_adaptive_medication_reminders():
# Use base time
check_time = sched.get("base_time")
# Normalize TIME objects to "HH:MM" strings for comparison
if isinstance(check_time, time_type):
check_time = check_time.strftime("%H:%M")
elif check_time is not None:
check_time = str(check_time)[:5]
if check_time != current_time:
continue
@@ -367,6 +373,11 @@ def check_nagging():
display_time = sched.get("adjusted_time")
else:
display_time = sched.get("base_time")
# Normalize TIME objects for display
if isinstance(display_time, time_type):
display_time = display_time.strftime("%H:%M")
elif display_time is not None:
display_time = str(display_time)[:5]
# Send nag notification
user_settings = notifications.getNotificationSettings(user_uuid)
@@ -446,6 +457,39 @@ def _get_distinct_user_uuids():
return uuids
def _is_med_due_today(med, today):
"""Check if a medication is due on the given date based on its frequency."""
from datetime import date as date_type
freq = med.get("frequency", "daily")
if freq == "as_needed":
return False
if freq == "specific_days":
current_day = today.strftime("%a").lower()
med_days = med.get("days_of_week", [])
if current_day not in med_days:
return False
if freq == "every_n_days":
start = med.get("start_date")
interval = med.get("interval_days")
if start and interval:
start_d = (
start
if isinstance(start, date_type)
else datetime.strptime(str(start), "%Y-%m-%d").date()
)
days_since = (today - start_d).days
if days_since < 0 or days_since % interval != 0:
return False
else:
return False
return True
def _check_per_user_midnight_schedules():
"""Create daily adaptive schedules for each user when it's midnight in
their timezone (within the poll window)."""
@@ -453,10 +497,13 @@ def _check_per_user_midnight_schedules():
try:
now = _user_now_for(user_uuid)
if now.hour == 0 and now.minute < POLL_INTERVAL / 60:
today = now.date()
user_meds = postgres.select(
"medications", where={"user_uuid": user_uuid, "active": True}
)
for med in user_meds:
if not _is_med_due_today(med, today):
continue
times = med.get("times", [])
if times:
adaptive_meds.create_daily_schedule(
@@ -499,7 +546,7 @@ def daemon_loop():
poll_callback()
except Exception as e:
logger.error(f"Poll callback error: {e}")
time.sleep(POLL_INTERVAL)
time_module.sleep(POLL_INTERVAL)
if __name__ == "__main__":