import asyncio from datetime import datetime, timedelta from db import db from utils.sms_utils import send_medicine_reminder_sms from utils.email_utils import send_medicine_reminder_email REMINDER_INTERVAL_SECONDS = 60 # Check every minute REMINDER_WINDOW_MINUTES = 5 # Remind if medicine time is within next 5 minutes # Global set to track sent reminders for today, reset daily sent_reminders_today = set() async def run_reminder_check(): print("run_reminder_check started") try: from zoneinfo import ZoneInfo tz = ZoneInfo("Asia/Karachi") except ImportError: import pytz tz = pytz.timezone("Asia/Karachi") now = datetime.now(tz) today_str = now.strftime("%Y-%m-%d") print(f"[reminder_worker] Running at {now.isoformat()} (today: {today_str})") # Reset sent reminders if it's a new day if not hasattr(run_reminder_check, 'last_reset_day') or run_reminder_check.last_reset_day != today_str: print("[reminder_worker] New day detected, resetting sent_reminders_today.") sent_reminders_today.clear() run_reminder_check.last_reset_day = today_str users = await db.users.find({}).to_list(length=1000) print(f"[reminder_worker] Found {len(users)} users.") for user in users: medicines = await db.medicines.find({"user_id": str(user["_id"]), "status": "active"}).to_list(length=6) print(f"[reminder_worker] User {user.get('email', user.get('_id'))} has {len(medicines)} active medicines.") for med in medicines: history = med.get("history", []) taken_times = set() sent_times = set() for h in history: if h.get("status") == "taken": taken_times.add(h.get("time")) elif h.get("status") == "reminder_sent": sent_times.add(h.get("time")) for t in med.get("times", []): med_time_str = f"{today_str} {t}" med_dt = None parse_attempts = [ ("%Y-%m-%d %I:%M %p", "12-hour"), ("%Y-%m-%d %H:%M", "24-hour"), ] for fmt, label in parse_attempts: try: med_dt = datetime.strptime(med_time_str, fmt) med_dt = med_dt.replace(tzinfo=tz) break except Exception: continue if not med_dt: print(f"[reminder_worker] Failed to parse med_time_str '{med_time_str}' with both 12-hour and 24-hour formats.") continue delta_seconds = (med_dt - now).total_seconds() print(f"[reminder_worker] Checking medicine '{med.get('name', '')}' for user {user.get('email', user.get('_id'))}: scheduled at {med_dt.isoformat()}, now {now.isoformat()}, delta_seconds={delta_seconds}") # Send reminder 5 minutes before to 2 minutes after scheduled time if -300 <= delta_seconds <= 120: iso_time = med_dt.isoformat() # Only send reminder if not already taken and not already sent for this specific time if iso_time not in taken_times and iso_time not in sent_times: print(f"[reminder_worker] Attempting to send reminder for {med.get('name', '')} at {t} to {user.get('phone_number', '')} and {user.get('email', '')}") sms_sent = False email_sent = False try: send_medicine_reminder_sms( user["phone_number"], med["name"], med.get("dosage", ""), t ) print("[reminder_worker] SMS reminder sent successfully.") sms_sent = True except Exception as e: print(f"[reminder_worker] SMS reminder failed: {e}") try: await send_medicine_reminder_email( user["email"], user["full_name"], med["name"], med.get("dosage", ""), t ) print("[reminder_worker] Email reminder sent successfully.") email_sent = True except Exception as e: print(f"[reminder_worker] Email reminder failed: {e}") if sms_sent or email_sent: # Update history to mark as reminder_sent history.append({"time": iso_time, "status": "reminder_sent", "sent_at": now.isoformat()}) await db.medicines.update_one({"_id": med["_id"]}, {"$set": {"history": history}}) else: print(f"[reminder_worker] Reminder already sent or dose already taken for {med.get('name', '')} at {t}.") else: print(f"[reminder_worker] Not in reminder window for {med.get('name', '')} at {t}.") async def reminder_worker(): print("reminder_worker started") while True: await run_reminder_check() await asyncio.sleep(REMINDER_INTERVAL_SECONDS) async def run_missed_doses_check(): from bson import ObjectId print("run_missed_doses_check started") try: from zoneinfo import ZoneInfo tz = ZoneInfo("Asia/Karachi") except ImportError: import pytz tz = pytz.timezone("Asia/Karachi") now = datetime.now(tz) users = await db.users.find({}).to_list(length=1000) for user in users: medicines = await db.medicines.find({"user_id": str(user["_id"]), "status": "active"}).to_list(length=100) for med in medicines: history = med.get("history", []) taken_times = set() absent_times = set() for h in history: if h.get("status") == "taken": taken_times.add(h.get("time")) elif h.get("status") == "absent": absent_times.add(h.get("time")) for t in med.get("times", []): today_str = now.strftime("%Y-%m-%d") med_time_str = f"{today_str} {t}" try: med_time = datetime.strptime(med_time_str, "%Y-%m-%d %I:%M %p") med_time = med_time.replace(tzinfo=tz) except Exception: continue # If more than 15 min have passed and not marked as taken or absent if now > med_time + timedelta(minutes=15): iso_time = med_time.isoformat() if iso_time not in taken_times and iso_time not in absent_times: history.append({"time": iso_time, "status": "absent"}) await db.medicines.update_one({"_id": med["_id"]}, {"$set": {"history": history}}) # Send missed notification only once try: from utils.sms_utils import send_medicine_missed_sms print(f"Sending SMS missed-dose notification to {user.get('phone_number', '')} for {med.get('name', '')} at {t}") send_medicine_missed_sms( user.get("phone_number", ""), user.get("full_name", ""), med.get("name", ""), med.get("dosage", ""), t ) print("SMS missed-dose notification sent successfully.") except Exception as e: print(f"SMS missed-dose notification failed: {e}") try: from utils.email_utils import send_medicine_missed_email print(f"Sending EMAIL missed-dose notification to {user.get('email', '')} for {med.get('name', '')} at {t}") await send_medicine_missed_email( user.get("email", ""), user.get("full_name", ""), med.get("name", ""), med.get("dosage", ""), t ) print("Email missed-dose notification sent successfully.") except Exception as e: print(f"Email missed-dose notification failed: {e}") async def mark_missed_doses_worker(): print("mark_missed_doses_worker started") while True: await run_missed_doses_check() await asyncio.sleep(60)