pdf-quiz-generator/backend/app/utils/scheduler.py
ifedan-ed b876f13fac Initial commit: PDF Quiz Generator app
- FastAPI backend with JWT auth, roles (admin/moderator/user)
- PDF upload (up to 500MB) with streaming, PyMuPDF text extraction
- ChromaDB vectorization per page with metadata
- LiteLLM AI question extraction from PDF (not generation)
- Image extraction from PDF pages, graceful fallback
- Quiz modes: timed (countdown timer) + learning (answers shown inline)
- Page-by-page question navigation with dot navigator
- TTS endpoint using LiteLLM (Google Vertex / OpenAI voices)
- Admin dashboard: AI model management per task, user role management
- Moderator role: upload PDFs, create sections, generate quizzes
- Spaced repetition reminders via SMTP email (SM-2 intervals)
- APScheduler daily reminder jobs
- Celery + Redis for background PDF processing
- React frontend with all pages
- Docker Compose deployment (nginx + backend + celery + redis)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:04:53 +00:00

82 lines
2.5 KiB
Python

import asyncio
import logging
from datetime import datetime
from apscheduler.schedulers.background import BackgroundScheduler
from app.database import SessionLocal
from app.models.reminder import ReminderSchedule
from app.models.user import User
from app.models.quiz import Quiz
logger = logging.getLogger(__name__)
scheduler = BackgroundScheduler()
def check_and_send_reminders():
"""Check for due reminders and send emails."""
db = SessionLocal()
try:
due_reminders = db.query(ReminderSchedule).filter(
ReminderSchedule.is_active == True,
ReminderSchedule.next_reminder_at <= datetime.utcnow(),
).all()
if not due_reminders:
return
logger.info(f"Found {len(due_reminders)} due reminders")
for reminder in due_reminders:
user = db.query(User).filter(User.id == reminder.user_id).first()
quiz = db.query(Quiz).filter(Quiz.id == reminder.quiz_id).first()
if not user or not quiz:
reminder.is_active = False
continue
# Send email asynchronously
try:
from app.services.email_service import send_reminder_email
loop = asyncio.new_event_loop()
loop.run_until_complete(
send_reminder_email(
email=user.email,
user_name=user.name,
quiz_title=quiz.title,
score=reminder.performance_score,
next_date=reminder.next_reminder_at.strftime("%Y-%m-%d"),
)
)
loop.close()
except Exception as e:
logger.error(f"Failed to send reminder {reminder.id}: {e}")
# Schedule next reminder
from datetime import timedelta
reminder.next_reminder_at = datetime.utcnow() + timedelta(days=reminder.interval_days)
db.commit()
except Exception as e:
logger.exception(f"Scheduler error: {e}")
finally:
db.close()
def start_scheduler():
"""Start the APScheduler with daily reminder check."""
scheduler.add_job(
check_and_send_reminders,
"interval",
hours=24,
id="reminder_check",
replace_existing=True,
)
scheduler.start()
logger.info("Scheduler started — reminder check runs every 24 hours")
def stop_scheduler():
if scheduler.running:
scheduler.shutdown()