""" daemon.py - Background polling loop for scheduled tasks Override poll_callback() with your domain-specific logic. """ import os import time import logging from datetime import datetime import core.postgres as postgres import core.notifications as notifications logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL", 60)) def check_medication_reminders(): """Check for medications due now and send notifications.""" try: from datetime import date as date_type meds = postgres.select("medications", where={"active": True}) now = datetime.now() current_time = now.strftime("%H:%M") current_day = now.strftime("%a").lower() # "mon","tue", etc. today = now.date() today_str = today.isoformat() for med in meds: freq = med.get("frequency", "daily") # Skip as_needed -- no scheduled reminders for PRN if freq == "as_needed": continue # Day-of-week check for specific_days if freq == "specific_days": days = med.get("days_of_week", []) if current_day not in days: continue # Interval check for every_n_days 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 # Time check times = med.get("times", []) if current_time not in times: continue # Already taken today? Check by created_at date 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 for log in logs ) if already_taken: continue user_settings = notifications.getNotificationSettings(med["user_uuid"]) if user_settings: msg = f"Time to take {med['name']} ({med['dosage']} {med['unit']})" notifications._sendToEnabledChannels(user_settings, msg) except Exception as e: logger.error(f"Error checking medication reminders: {e}") def check_routine_reminders(): """Check for scheduled routines due now and send notifications.""" try: now = datetime.now() current_time = now.strftime("%H:%M") current_day = now.strftime("%a").lower() schedules = postgres.select("routine_schedules", where={"remind": True}) for schedule in schedules: if current_time != schedule.get("time"): continue days = schedule.get("days", []) if current_day not in days: continue routine = postgres.select_one("routines", {"id": schedule["routine_id"]}) if not routine: continue user_settings = notifications.getNotificationSettings(routine["user_uuid"]) if user_settings: msg = f"Time to start your routine: {routine['name']}" notifications._sendToEnabledChannels(user_settings, msg) except Exception as e: logger.error(f"Error checking routine reminders: {e}") def check_refills(): """Check for medications running low on refills.""" try: meds = postgres.select("medications") for med in meds: qty = med.get("quantity_remaining") if qty is not None and qty <= 7: user_settings = notifications.getNotificationSettings(med["user_uuid"]) if user_settings: msg = f"Low on {med['name']}: only {qty} doses remaining. Time to refill!" notifications._sendToEnabledChannels(user_settings, msg) except Exception as e: logger.error(f"Error checking refills: {e}") def poll_callback(): """Called every POLL_INTERVAL seconds.""" check_medication_reminders() check_routine_reminders() check_refills() def daemon_loop(): logger.info("Scheduler daemon starting") while True: try: poll_callback() except Exception as e: logger.error(f"Poll callback error: {e}") time.sleep(POLL_INTERVAL) if __name__ == "__main__": daemon_loop()