Spaces:
Sleeping
Sleeping
| """ | |
| Study Plan API Router. | |
| AI-generated personalized study schedules. | |
| """ | |
| from datetime import datetime | |
| from typing import List, Optional | |
| from fastapi import APIRouter, HTTPException, Query | |
| from loguru import logger | |
| from open_notebook.domain.study_plan import ( | |
| StudyPlan, StudyPlanCreate, StudyPlanUpdate, | |
| StudyPlanFull, StudyPlanWithTopics, StudyPlanStats, | |
| StudyTopic, StudyTopicCreate, StudyTopicUpdate, | |
| StudySession, StudySessionCreate, StudySessionUpdate, | |
| PlanAdjustment, PlanAdjustmentResponse, | |
| WeeklySchedule, DailySchedule, | |
| PlanGenerationRequest, PlanGenerationResult | |
| ) | |
| from open_notebook.services.study_plan_service import study_plan_service | |
| router = APIRouter(prefix="/study-plans", tags=["study-plans"]) | |
| # ============ Study Plan Endpoints ============ | |
| async def create_plan(data: StudyPlanCreate): | |
| """Create a new study plan.""" | |
| return await study_plan_service.create_plan(data) | |
| async def generate_plan(request: PlanGenerationRequest): | |
| """Generate a complete study plan using AI.""" | |
| return await study_plan_service.generate_plan(request) | |
| async def list_plans( | |
| notebook_id: Optional[str] = Query(None, description="Filter by notebook ID"), | |
| active_only: bool = Query(False, description="Only return active plans") | |
| ): | |
| """List study plans.""" | |
| if notebook_id: | |
| plans = await study_plan_service.get_plans_for_notebook(notebook_id) | |
| elif active_only: | |
| plans = await study_plan_service.get_active_plans() | |
| else: | |
| plans = await study_plan_service.get_active_plans() # Default to active | |
| return plans | |
| async def get_today_sessions(plan_id: Optional[str] = Query(None)): | |
| """Get study sessions scheduled for today.""" | |
| return await study_plan_service.get_today_sessions(plan_id) | |
| async def get_plan(plan_id: str): | |
| """Get a study plan with all details.""" | |
| plan = await study_plan_service.get_plan_full(plan_id) | |
| if not plan: | |
| raise HTTPException(status_code=404, detail="Study plan not found") | |
| return plan | |
| async def update_plan(plan_id: str, data: StudyPlanUpdate): | |
| """Update a study plan.""" | |
| plan = await study_plan_service.update_plan(plan_id, data) | |
| if not plan: | |
| raise HTTPException(status_code=404, detail="Study plan not found") | |
| return plan | |
| async def delete_plan(plan_id: str): | |
| """Delete a study plan and all related data.""" | |
| success = await study_plan_service.delete_plan(plan_id) | |
| if not success: | |
| raise HTTPException(status_code=404, detail="Study plan not found") | |
| return {"status": "deleted", "plan_id": plan_id} | |
| async def get_plan_stats(plan_id: str): | |
| """Get statistics for a study plan.""" | |
| return await study_plan_service.get_plan_stats(plan_id) | |
| async def get_weekly_schedule( | |
| plan_id: str, | |
| week_start: Optional[datetime] = Query(None, description="Start of week (defaults to current week)") | |
| ): | |
| """Get weekly schedule for a study plan.""" | |
| return await study_plan_service.get_weekly_schedule(plan_id, week_start) | |
| # ============ Topic Endpoints ============ | |
| async def create_topic(plan_id: str, data: StudyTopicCreate): | |
| """Create a study topic.""" | |
| if data.plan_id != plan_id: | |
| data.plan_id = plan_id | |
| return await study_plan_service.create_topic(data) | |
| async def list_topics(plan_id: str): | |
| """List topics for a study plan.""" | |
| return await study_plan_service.get_topics_for_plan(plan_id) | |
| async def get_topic(topic_id: str): | |
| """Get a study topic.""" | |
| topic = await study_plan_service.get_topic(topic_id) | |
| if not topic: | |
| raise HTTPException(status_code=404, detail="Topic not found") | |
| return topic | |
| async def update_topic(topic_id: str, data: StudyTopicUpdate): | |
| """Update a study topic.""" | |
| topic = await study_plan_service.update_topic(topic_id, data) | |
| if not topic: | |
| raise HTTPException(status_code=404, detail="Topic not found") | |
| return topic | |
| async def delete_topic(topic_id: str): | |
| """Delete a study topic.""" | |
| success = await study_plan_service.delete_topic(topic_id) | |
| if not success: | |
| raise HTTPException(status_code=404, detail="Topic not found") | |
| return {"status": "deleted", "topic_id": topic_id} | |
| # ============ Session Endpoints ============ | |
| async def create_session(plan_id: str, data: StudySessionCreate): | |
| """Create a study session.""" | |
| if data.plan_id != plan_id: | |
| data.plan_id = plan_id | |
| return await study_plan_service.create_session(data) | |
| async def list_sessions(plan_id: str): | |
| """List sessions for a study plan.""" | |
| return await study_plan_service.get_sessions_for_plan(plan_id) | |
| async def get_session(session_id: str): | |
| """Get a study session.""" | |
| session = await study_plan_service.get_session(session_id) | |
| if not session: | |
| raise HTTPException(status_code=404, detail="Session not found") | |
| return session | |
| async def update_session(session_id: str, data: StudySessionUpdate): | |
| """Update a study session.""" | |
| session = await study_plan_service.update_session(session_id, data) | |
| if not session: | |
| raise HTTPException(status_code=404, detail="Session not found") | |
| return session | |
| async def start_session(session_id: str): | |
| """Start a study session.""" | |
| session = await study_plan_service.start_session(session_id) | |
| if not session: | |
| raise HTTPException(status_code=404, detail="Session not found") | |
| return session | |
| async def complete_session( | |
| session_id: str, | |
| rating: Optional[int] = Query(None, ge=1, le=5), | |
| notes: Optional[str] = Query(None) | |
| ): | |
| """Complete a study session.""" | |
| try: | |
| logger.info(f"API: complete_session called with session_id={session_id}, rating={rating}, notes={notes}") | |
| session = await study_plan_service.complete_session(session_id, rating, notes) | |
| if not session: | |
| raise HTTPException(status_code=404, detail="Session not found") | |
| logger.info(f"API: Session completed successfully: {session.id}") | |
| return session | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| logger.error(f"API: Error completing session: {e}", exc_info=True) | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def delete_session(session_id: str): | |
| """Delete a study session.""" | |
| success = await study_plan_service.delete_session(session_id) | |
| if not success: | |
| raise HTTPException(status_code=404, detail="Session not found") | |
| return {"status": "deleted", "session_id": session_id} | |
| async def skip_session( | |
| session_id: str, | |
| reason: Optional[str] = Query(None, description="Reason for skipping") | |
| ): | |
| """Skip a study session.""" | |
| session = await study_plan_service.skip_session(session_id, reason) | |
| if not session: | |
| raise HTTPException(status_code=404, detail="Session not found") | |
| return session | |
| # ============ Adjustment Endpoints ============ | |
| async def list_adjustments(plan_id: str): | |
| """List adjustments for a study plan.""" | |
| return await study_plan_service.get_adjustments_for_plan(plan_id) | |
| async def respond_to_adjustment(adjustment_id: str, response: PlanAdjustmentResponse): | |
| """Accept or reject a plan adjustment.""" | |
| success = await study_plan_service.respond_to_adjustment(adjustment_id, response.accepted) | |
| return {"status": "accepted" if response.accepted else "rejected", "adjustment_id": adjustment_id} | |