diff --git a/api/routes/adaptive_meds.py b/api/routes/adaptive_meds.py index b61f41a..98d3610 100644 --- a/api/routes/adaptive_meds.py +++ b/api/routes/adaptive_meds.py @@ -2,12 +2,15 @@ api/routes/adaptive_meds.py - API endpoints for adaptive medication settings """ +import logging import flask import jwt import os import core.postgres as postgres import core.adaptive_meds as adaptive_meds +logger = logging.getLogger(__name__) + JWT_SECRET = os.getenv("JWT_SECRET") @@ -99,18 +102,25 @@ def register(app): "quiet_hours_end": data.get("quiet_hours_end"), } - # Check if settings exist - existing = adaptive_meds.get_adaptive_settings(user_uuid) + try: + # Check if settings exist + existing = adaptive_meds.get_adaptive_settings(user_uuid) - if existing: - postgres.update( - "adaptive_med_settings", update_data, {"user_uuid": user_uuid} - ) - else: - update_data["user_uuid"] = user_uuid - postgres.insert("adaptive_med_settings", update_data) + if existing: + postgres.update( + "adaptive_med_settings", update_data, {"user_uuid": user_uuid} + ) + else: + update_data["user_uuid"] = user_uuid + postgres.insert("adaptive_med_settings", update_data) - return flask.jsonify({"success": True}), 200 + return flask.jsonify({"success": True}), 200 + except Exception as e: + logger.error(f"Failed to save adaptive settings: {e}") + return flask.jsonify({ + "error": "Failed to save settings", + "details": str(e) + }), 500 @app.route("/api/adaptive-meds/presence", methods=["GET"]) def get_presence_status(): diff --git a/api/routes/medications.py b/api/routes/medications.py index 266490e..23bdc3c 100644 --- a/api/routes/medications.py +++ b/api/routes/medications.py @@ -2,6 +2,7 @@ Medications API - medication scheduling, logging, and adherence tracking """ +import json import os import uuid from datetime import datetime, date, timedelta, timezone @@ -144,6 +145,35 @@ def register(app): meds = postgres.select("medications", where={"user_uuid": user_uuid}, order_by="name") return flask.jsonify(meds), 200 + def _check_med_schedule_conflicts(user_uuid, new_times, new_days=None, exclude_med_id=None): + """Check if the proposed medication schedule conflicts with existing routines or medications. + Returns (has_conflict, conflict_message) tuple. + """ + if not new_times: + return False, None + + # Check conflicts with routines + user_routines = postgres.select("routines", {"user_uuid": user_uuid}) + for r in user_routines: + sched = postgres.select_one("routine_schedules", {"routine_id": r["id"]}) + if sched and sched.get("time") in new_times: + routine_days = json.loads(sched.get("days", "[]")) + if not new_days or any(d in routine_days for d in new_days): + return True, f"Time conflicts with routine: {r.get('name', 'Unnamed routine')}" + + # Check conflicts with other medications + user_meds = postgres.select("medications", {"user_uuid": user_uuid, "active": True}) + for med in user_meds: + if med["id"] == exclude_med_id: + continue + med_times = med.get("times", []) + if isinstance(med_times, str): + med_times = json.loads(med_times) + if any(t in med_times for t in new_times): + return True, f"Time conflicts with medication: {med.get('name', 'Unnamed medication')}" + + return False, None + @app.route("/api/medications", methods=["POST"]) def api_addMedication(): """Add a medication. Body: {name, dosage, unit, frequency, times?, days_of_week?, interval_days?, start_date?, notes?}""" @@ -158,6 +188,15 @@ def register(app): if missing: return flask.jsonify({"error": f"missing required fields: {', '.join(missing)}"}), 400 + # Check for schedule conflicts + new_times = data.get("times", []) + new_days = data.get("days_of_week", []) + has_conflict, conflict_msg = _check_med_schedule_conflicts( + user_uuid, new_times, new_days + ) + if has_conflict: + return flask.jsonify({"error": conflict_msg}), 409 + row = { "id": str(uuid.uuid4()), "user_uuid": user_uuid, @@ -217,6 +256,17 @@ def register(app): "name", "dosage", "unit", "frequency", "times", "notes", "active", "days_of_week", "interval_days", "start_date", "next_dose_date", ] + + # Check for schedule conflicts if times are being updated + if "times" in data: + new_times = data.get("times", []) + new_days = data.get("days_of_week") or existing.get("days_of_week", []) + has_conflict, conflict_msg = _check_med_schedule_conflicts( + user_uuid, new_times, new_days, exclude_med_id=med_id + ) + if has_conflict: + return flask.jsonify({"error": conflict_msg}), 409 + updates = {k: v for k, v in data.items() if k in allowed} if not updates: return flask.jsonify({"error": "no valid fields to update"}), 400 diff --git a/api/routes/routines.py b/api/routes/routines.py index cf16c9a..1f92e01 100644 --- a/api/routes/routines.py +++ b/api/routes/routines.py @@ -649,6 +649,40 @@ def register(app): ) return flask.jsonify(result), 200 + def _check_schedule_conflicts(user_uuid, new_days, new_time, exclude_routine_id=None): + """Check if the proposed schedule conflicts with existing routines or medications. + Returns (has_conflict, conflict_message) tuple. + """ + if not new_days or not new_time: + return False, None + + # Check conflicts with other routines + user_routines = postgres.select("routines", {"user_uuid": user_uuid}) + for r in user_routines: + if r["id"] == exclude_routine_id: + continue + other_sched = postgres.select_one("routine_schedules", {"routine_id": r["id"]}) + if other_sched and other_sched.get("time") == new_time: + other_days = json.loads(other_sched.get("days", "[]")) + if any(d in other_days for d in new_days): + return True, f"Time conflicts with routine: {r.get('name', 'Unnamed routine')}" + + # Check conflicts with medications + user_meds = postgres.select("medications", {"user_uuid": user_uuid, "active": True}) + for med in user_meds: + med_times = med.get("times", []) + if isinstance(med_times, str): + med_times = json.loads(med_times) + if new_time in med_times: + # Check if medication runs on any of the same days + med_days = med.get("days_of_week", []) + if isinstance(med_days, str): + med_days = json.loads(med_days) + if not med_days or any(d in med_days for d in new_days): + return True, f"Time conflicts with medication: {med.get('name', 'Unnamed medication')}" + + return False, None + @app.route("/api/routines//schedule", methods=["PUT"]) def api_setRoutineSchedule(routine_id): """Set when this routine should run. Body: {days: ["mon","tue",...], time: "08:00", remind: true}""" @@ -663,6 +697,16 @@ def register(app): data = flask.request.get_json() if not data: return flask.jsonify({"error": "missing body"}), 400 + + # Check for schedule conflicts + new_days = data.get("days", []) + new_time = data.get("time") + has_conflict, conflict_msg = _check_schedule_conflicts( + user_uuid, new_days, new_time, exclude_routine_id=routine_id + ) + if has_conflict: + return flask.jsonify({"error": conflict_msg}), 409 + existing = postgres.select_one("routine_schedules", {"routine_id": routine_id}) schedule_data = { "routine_id": routine_id, diff --git a/scheduler/daemon.py b/scheduler/daemon.py index ad66a8f..247107b 100644 --- a/scheduler/daemon.py +++ b/scheduler/daemon.py @@ -305,22 +305,56 @@ def check_nagging(): # Get user's settings settings = adaptive_meds.get_adaptive_settings(user_uuid) - if not settings or not settings.get("nagging_enabled"): + if not settings: + logger.debug(f"No adaptive settings for user {user_uuid}") + continue + if not settings.get("nagging_enabled"): + logger.debug(f"Nagging disabled for user {user_uuid}") 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", - }, - ) + try: + schedules = postgres.select( + "medication_schedules", + where={ + "user_uuid": user_uuid, + "medication_id": med_id, + "adjustment_date": today, + "status": "pending", + }, + ) + except Exception as e: + logger.warning(f"Could not query medication_schedules for {med_id}: {e}") + # Table may not exist yet + continue + + # If no schedules exist, try to create them + if not schedules: + logger.info(f"No schedules found for medication {med_id}, attempting to create") + times = med.get("times", []) + if times: + try: + adaptive_meds.create_daily_schedule(user_uuid, med_id, times) + # Re-query for schedules + schedules = postgres.select( + "medication_schedules", + where={ + "user_uuid": user_uuid, + "medication_id": med_id, + "adjustment_date": today, + "status": "pending", + }, + ) + except Exception as e: + logger.warning(f"Could not create schedules for {med_id}: {e}") + continue + + if not schedules: + logger.debug(f"No pending schedules for medication {med_id}") + continue for sched in schedules: # Check if we should nag @@ -406,16 +440,19 @@ def poll_callback(): f"Could not create adaptive schedules (tables may not exist): {e}" ) - # Check reminders - ALWAYS use original check for now - # (adaptive check requires database migration) - logger.info("Checking medication reminders (using original method)") + # Check reminders - use both original and adaptive checks + logger.info("Checking medication reminders") check_medication_reminders() + try: + check_adaptive_medication_reminders() + except Exception as e: + logger.warning(f"Adaptive medication reminder check failed: {e}") - # Check for nags + # Check for nags - log as error to help with debugging try: check_nagging() except Exception as e: - logger.warning(f"Nagging check failed: {e}") + logger.error(f"Nagging check failed: {e}") # Original checks check_routine_reminders()