import os import json from datetime import datetime from typing import Optional import firebase_admin from firebase_admin import credentials, firestore from fastapi import FastAPI, HTTPException, BackgroundTasks from pydantic import BaseModel, Field from openai import OpenAI from dotenv import load_dotenv load_dotenv() # ── Firebase Admin Init ────────────────────────────────────────────────────── if not firebase_admin._apps: # Support JSON string via env var (for Docker/HF Spaces) or file path (local dev) firebase_json = os.getenv("FIREBASE_SERVICE_ACCOUNT_KEY_JSON") if firebase_json: cred = credentials.Certificate(json.loads(firebase_json)) else: cred = credentials.Certificate(os.getenv("FIREBASE_SERVICE_ACCOUNT_KEY_PATH", "serviceAccountKey.json")) firebase_admin.initialize_app(cred) db = firestore.client() # ── OpenRouter LLM Init ────────────────────────────────────────────────────── client = OpenAI( base_url="https://openrouter.ai/api/v1", api_key=os.getenv("OPENROUTER_API_KEY"), ) LLM_MODEL = os.getenv("OPENROUTER_MODEL", "google/gemini-2.0-flash-001") app = FastAPI(title="lonelytrack", version="0.1.0") # ── Pydantic Models ───────────────────────────────────────────────────────── class UserRequest(BaseModel): user_id: str topic: str daily_minutes: int = Field(gt=0, le=480) total_days: int = Field(gt=0, le=365, default=14) skill_level: str = Field(pattern="^(beginner|intermediate|advanced|pro|Beginner|Intermediate|Advanced|Pro)$") class DailyTask(BaseModel): day: int topic: str duration_mins: int status: str = "pending" # pending | completed | missed class LearningPlan(BaseModel): goal: str total_days: int schedule: list[DailyTask] class StatusUpdate(BaseModel): user_id: str plan_id: str day: int status: str = Field(pattern="^(completed|missed)$") class TutorialRequest(BaseModel): topic: str skill_level: str = "beginner" class QuizRequest(BaseModel): topic: str skill_level: str = "beginner" num_questions: int = Field(default=5, ge=3, le=10) class QuizQuestion(BaseModel): question: str options: list[str] correct_answer: int # index into options explanation: str class QuizSubmission(BaseModel): user_id: str plan_id: str day: int answers: list[int] # user's selected indices class ReminderSettings(BaseModel): user_id: str enabled: bool = True hour: int = Field(default=9, ge=0, le=23) minute: int = Field(default=0, ge=0, le=59) # ── Helpers ────────────────────────────────────────────────────────────────── PLAN_PROMPT_TEMPLATE = """You are a study-plan generator. Create a structured learning plan. Topic: {topic} Daily available time: {daily_minutes} minutes Total duration: {total_days} days Current skill level: {skill_level} Return ONLY valid JSON matching this exact schema (no markdown, no explanation): {{ "goal": "", "total_days": {total_days}, "schedule": [ {{"day": 1, "topic": "", "duration_mins": , "status": "pending"}}, ... ] }} Rules: - Each day's duration_mins must be <= {daily_minutes}. - Provide exactly {total_days} days of content. - Tailor complexity to the {skill_level} level. """ SIMPLIFY_PROMPT_TEMPLATE = """The learner has missed 3 consecutive days. Simplify the remaining schedule. Original goal: {goal} Remaining schedule (days not yet completed): {remaining_json} Return ONLY valid JSON as a list of daily tasks matching this schema (no markdown): [ {{"day": , "topic": "", "duration_mins": , "status": "pending"}} ] Rules: - Reduce complexity and session length by ~25%. - Merge or drop low-priority topics. - Keep the list between 3 and 20 entries. """ TUTORIAL_PROMPT_TEMPLATE = """You are an expert tutor. Write a comprehensive tutorial on the following topic. Topic: {topic} Skill level: {skill_level} Write a well-structured tutorial with: - A brief introduction explaining what this topic is and why it matters - Clear step-by-step explanations with examples - Code examples if applicable (use proper formatting) - Key takeaways or summary at the end - Practice exercises or questions to reinforce learning Write in plain text with clear section headers (use ALL CAPS for headers). Keep the tutorial focused and around 800-1200 words. Make it engaging and easy to follow for a {skill_level} learner. """ QUIZ_PROMPT_TEMPLATE = """You are a quiz generator for educational content. Generate exactly {num_questions} multiple-choice questions about: Topic: {topic} Difficulty: {skill_level} Return ONLY valid JSON matching this exact schema (no markdown, no explanation): {{ "questions": [ {{ "question": "", "options": ["