""" Report routes for the sim-search API. This module defines the routes for report generation and management. """ import os from typing import Any, List, Optional from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks from fastapi.responses import FileResponse from sqlalchemy.orm import Session from app.api.dependencies import get_current_active_user from app.db.models import User, Report, Search from app.db.session import get_db from app.schemas.report import ( ReportCreate, ReportUpdate, Report as ReportSchema, ReportList, ReportProgress, ReportDownload ) from app.services.report_service import ReportService router = APIRouter() report_service = ReportService() # Dictionary to store report generation progress report_progress = {} @router.post("/generate", response_model=ReportSchema) async def generate_report( report_in: ReportCreate, background_tasks: BackgroundTasks, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db), ) -> Any: """ Generate a report from search results. Args: report_in: Report creation parameters background_tasks: FastAPI background tasks current_user: Current authenticated user db: Database session Returns: Generated report """ try: # Check if search_id is provided and exists search = None if report_in.search_id: search = db.query(Search).filter( Search.id == report_in.search_id, Search.user_id == current_user.id ).first() if not search: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Search not found", ) # Create report record title = report_in.title or f"Report: {report_in.query}" report = Report( user_id=current_user.id, search_id=report_in.search_id, title=title, content="Report generation in progress...", detail_level=report_in.detail_level or "standard", query_type=report_in.query_type, model_used=report_in.model, ) db.add(report) db.commit() db.refresh(report) # Initialize progress tracking report_progress[report.id] = { "progress": 0.0, "status": "Initializing report generation...", "current_chunk": 0, "total_chunks": 0, "current_report": "Report generation in progress...", } # Generate report in background background_tasks.add_task( report_service.generate_report_background, report_id=report.id, report_in=report_in, search=search, db=db, progress_dict=report_progress, ) return report except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error generating report: {str(e)}", ) @router.get("/list", response_model=ReportList) async def list_reports( skip: int = 0, limit: int = 100, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db), ) -> Any: """ Get a list of user's reports. Args: skip: Number of records to skip limit: Maximum number of records to return current_user: Current authenticated user db: Database session Returns: List of reports """ reports = db.query(Report).filter(Report.user_id == current_user.id).order_by( Report.created_at.desc() ).offset(skip).limit(limit).all() total = db.query(Report).filter(Report.user_id == current_user.id).count() return {"reports": reports, "total": total} @router.get("/{report_id}", response_model=ReportSchema) async def get_report( report_id: str, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db), ) -> Any: """ Get a specific report. Args: report_id: ID of the report current_user: Current authenticated user db: Database session Returns: Report Raises: HTTPException: If the report is not found or doesn't belong to the user """ report = db.query(Report).filter( Report.id == report_id, Report.user_id == current_user.id ).first() if not report: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Report not found", ) return report @router.delete("/{report_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_report( report_id: str, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db), ) -> None: """ Delete a report. Args: report_id: ID of the report to delete current_user: Current authenticated user db: Database session Raises: HTTPException: If the report is not found or doesn't belong to the user """ report = db.query(Report).filter( Report.id == report_id, Report.user_id == current_user.id ).first() if not report: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Report not found", ) db.delete(report) db.commit() @router.get("/{report_id}/progress", response_model=ReportProgress) async def get_report_progress( report_id: str, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db), ) -> Any: """ Get the progress of a report generation. Args: report_id: ID of the report current_user: Current authenticated user db: Database session Returns: Report generation progress Raises: HTTPException: If the report is not found or doesn't belong to the user """ # Check if report exists and belongs to user report = db.query(Report).filter( Report.id == report_id, Report.user_id == current_user.id ).first() if not report: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Report not found", ) # Get progress from progress dictionary progress_data = report_progress.get(report_id, { "progress": 1.0, "status": "Report generation complete", "current_chunk": 0, "total_chunks": 0, "current_report": None, }) return { "report_id": report_id, "progress": progress_data.get("progress", 1.0), "status": progress_data.get("status", "Report generation complete"), "current_chunk": progress_data.get("current_chunk", 0), "total_chunks": progress_data.get("total_chunks", 0), "current_report": progress_data.get("current_report", None), } @router.get("/{report_id}/download") async def download_report( report_id: str, format: str = "markdown", current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db), ) -> Any: """ Download a report in the specified format. Args: report_id: ID of the report format: Format of the report (markdown, html, pdf) current_user: Current authenticated user db: Database session Returns: Report file Raises: HTTPException: If the report is not found or doesn't belong to the user """ report = db.query(Report).filter( Report.id == report_id, Report.user_id == current_user.id ).first() if not report: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Report not found", ) # Generate file in the requested format try: file_path = await report_service.generate_report_file(report, format) # Return file return FileResponse( path=file_path, filename=f"report_{report_id}.{format}", media_type="application/octet-stream", ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error generating report file: {str(e)}", )