Add adaptive medication timing, Discord presence tracking, and nagging system
This commit is contained in:
@@ -11,6 +11,7 @@ from datetime import datetime, timezone, timedelta
|
||||
|
||||
import core.postgres as postgres
|
||||
import core.notifications as notifications
|
||||
import core.adaptive_meds as adaptive_meds
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -155,9 +156,226 @@ def check_refills():
|
||||
logger.error(f"Error checking refills: {e}")
|
||||
|
||||
|
||||
def create_daily_adaptive_schedules():
|
||||
"""Create today's medication schedules with adaptive timing."""
|
||||
try:
|
||||
from datetime import date as date_type
|
||||
|
||||
meds = postgres.select("medications", where={"active": True})
|
||||
|
||||
for med in meds:
|
||||
user_uuid = med.get("user_uuid")
|
||||
med_id = med.get("id")
|
||||
times = med.get("times", [])
|
||||
|
||||
if not times:
|
||||
continue
|
||||
|
||||
# Create daily schedule with adaptive adjustments
|
||||
adaptive_meds.create_daily_schedule(user_uuid, med_id, times)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating daily adaptive schedules: {e}")
|
||||
|
||||
|
||||
def check_adaptive_medication_reminders():
|
||||
"""Check for medications due now with adaptive timing."""
|
||||
try:
|
||||
from datetime import date as date_type
|
||||
|
||||
meds = postgres.select("medications", where={"active": True})
|
||||
|
||||
# Group by user
|
||||
user_meds = {}
|
||||
for med in meds:
|
||||
uid = med.get("user_uuid")
|
||||
if uid not in user_meds:
|
||||
user_meds[uid] = []
|
||||
user_meds[uid].append(med)
|
||||
|
||||
for user_uuid, user_med_list in user_meds.items():
|
||||
now = _user_now_for(user_uuid)
|
||||
current_time = now.strftime("%H:%M")
|
||||
today = now.date()
|
||||
|
||||
# Check if adaptive timing is enabled
|
||||
settings = adaptive_meds.get_adaptive_settings(user_uuid)
|
||||
adaptive_enabled = settings and settings.get("adaptive_timing_enabled")
|
||||
|
||||
for med in user_med_list:
|
||||
freq = med.get("frequency", "daily")
|
||||
|
||||
if freq == "as_needed":
|
||||
continue
|
||||
|
||||
# Day-of-week check
|
||||
if freq == "specific_days":
|
||||
current_day = now.strftime("%a").lower()
|
||||
med_days = med.get("days_of_week", [])
|
||||
if current_day not in med_days:
|
||||
continue
|
||||
|
||||
# Interval check
|
||||
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()
|
||||
)
|
||||
if (today - start_d).days < 0 or (
|
||||
today - start_d
|
||||
).days % interval != 0:
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
||||
# Get today's schedule
|
||||
schedules = postgres.select(
|
||||
"medication_schedules",
|
||||
where={
|
||||
"user_uuid": user_uuid,
|
||||
"medication_id": med["id"],
|
||||
"adjustment_date": today,
|
||||
"status": "pending",
|
||||
},
|
||||
)
|
||||
|
||||
for sched in schedules:
|
||||
# Check if it's time to take this med
|
||||
if adaptive_enabled:
|
||||
# Use adjusted time
|
||||
check_time = sched.get("adjusted_time")
|
||||
else:
|
||||
# Use base time
|
||||
check_time = sched.get("base_time")
|
||||
|
||||
if check_time != current_time:
|
||||
continue
|
||||
|
||||
# Check if already taken
|
||||
logs = postgres.select(
|
||||
"med_logs",
|
||||
where={
|
||||
"medication_id": med["id"],
|
||||
"user_uuid": user_uuid,
|
||||
"action": "taken",
|
||||
},
|
||||
)
|
||||
|
||||
already_taken = any(
|
||||
str(log.get("created_at", ""))[:10] == today.isoformat()
|
||||
for log in logs
|
||||
)
|
||||
|
||||
if already_taken:
|
||||
continue
|
||||
|
||||
# Send notification
|
||||
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)"
|
||||
else:
|
||||
msg = f"⏰ Time to take {med['name']} ({med['dosage']} {med['unit']}) · {check_time}"
|
||||
|
||||
notifications._sendToEnabledChannels(
|
||||
user_settings, msg, user_uuid=user_uuid
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking adaptive medication reminders: {e}")
|
||||
|
||||
|
||||
def check_nagging():
|
||||
"""Check for missed medications and send nag notifications."""
|
||||
try:
|
||||
from datetime import date as date_type
|
||||
|
||||
# Get all active medications
|
||||
meds = postgres.select("medications", where={"active": True})
|
||||
|
||||
for med in meds:
|
||||
user_uuid = med.get("user_uuid")
|
||||
med_id = med.get("id")
|
||||
|
||||
# Get user's settings
|
||||
settings = adaptive_meds.get_adaptive_settings(user_uuid)
|
||||
if not settings or not settings.get("nagging_enabled"):
|
||||
continue
|
||||
|
||||
now = datetime.utcnow()
|
||||
today = now.date()
|
||||
|
||||
# Get today's schedules
|
||||
schedules = postgres.select(
|
||||
"medication_schedules",
|
||||
where={
|
||||
"user_uuid": user_uuid,
|
||||
"medication_id": med_id,
|
||||
"adjustment_date": today,
|
||||
"status": "pending",
|
||||
},
|
||||
)
|
||||
|
||||
for sched in schedules:
|
||||
# Check if we should nag
|
||||
should_nag, reason = adaptive_meds.should_send_nag(
|
||||
user_uuid, med_id, sched.get("adjusted_time"), now
|
||||
)
|
||||
|
||||
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")
|
||||
|
||||
# Send nag notification
|
||||
user_settings = notifications.getNotificationSettings(user_uuid)
|
||||
if user_settings:
|
||||
nag_count = sched.get("nag_count", 0) + 1
|
||||
max_nags = settings.get("max_nag_count", 4)
|
||||
|
||||
msg = f"🔔 {med['name']} reminder {nag_count}/{max_nags}: You missed your {display_time} dose. Please take it now!"
|
||||
|
||||
notifications._sendToEnabledChannels(
|
||||
user_settings, msg, user_uuid=user_uuid
|
||||
)
|
||||
|
||||
# Record that we sent a nag
|
||||
adaptive_meds.record_nag_sent(
|
||||
user_uuid, med_id, sched.get("adjusted_time")
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Sent nag {nag_count}/{max_nags} for {med['name']} to user {user_uuid}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking nags: {e}")
|
||||
|
||||
|
||||
def poll_callback():
|
||||
"""Called every POLL_INTERVAL seconds."""
|
||||
check_medication_reminders()
|
||||
# Create daily schedules at midnight
|
||||
now = datetime.utcnow()
|
||||
if now.hour == 0 and now.minute < POLL_INTERVAL / 60:
|
||||
create_daily_adaptive_schedules()
|
||||
|
||||
# Check reminders with adaptive timing
|
||||
check_adaptive_medication_reminders()
|
||||
|
||||
# Check for nags
|
||||
check_nagging()
|
||||
|
||||
# Original checks
|
||||
check_routine_reminders()
|
||||
check_refills()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user