import io import logging from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import StreamingResponse from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.db.session import get_db from app.dependencies import get_user_id from app.models.analysis import Analysis from app.services.storage import storage router = APIRouter(tags=["reports"]) logger = logging.getLogger("olivas.reports") @router.get("/analyses/{analysis_id}/report") async def download_report( analysis_id: str, db: AsyncSession = Depends(get_db), user_id: str = Depends(get_user_id), ): stmt = ( select(Analysis) .options(selectinload(Analysis.aois)) .where(Analysis.id == analysis_id, Analysis.user_id == user_id) ) result = await db.execute(stmt) analysis = result.scalar_one_or_none() if not analysis: raise HTTPException(status_code=404, detail="Analysis not found") if analysis.status != "completed": raise HTTPException(status_code=400, detail="Analysis not yet completed") # Check if PDF already cached if storage.exists(analysis_id, "report.pdf"): data = await storage.load_bytes(analysis_id, "report.pdf") else: from app.services.report_generator import generate_report # Load images for the report try: original_data = await storage.load_bytes(analysis_id, "original.png") except FileNotFoundError: raise HTTPException(status_code=404, detail="Original image not found") try: heatmap_data = await storage.load_bytes(analysis_id, "heatmap_overlay.png") except FileNotFoundError: raise HTTPException(status_code=404, detail="Heatmap image not yet generated") try: gaze_data = await storage.load_bytes(analysis_id, "gaze_sequence.png") except FileNotFoundError: raise HTTPException(status_code=404, detail="Gaze sequence image not yet generated") # Generate rule-based insights from app.services.insights import generate_insights rule_insights = generate_insights(analysis) if analysis.status == "completed" else [] try: data = generate_report( analysis=analysis, original_image=original_data, heatmap_image=heatmap_data, gaze_image=gaze_data, aois=list(analysis.aois), rule_insights=rule_insights, ai_insights=analysis.ai_insights, ai_cost_usd=analysis.ai_cost_usd, ) except Exception as e: logger.error(f"Report generation failed for {analysis_id}: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Report generation failed: {str(e)}") await storage.save_bytes(data, analysis_id, "report.pdf") safe_name = analysis.name.replace(" ", "_").replace("/", "_") return StreamingResponse( io.BytesIO(data), media_type="application/pdf", headers={ "Content-Disposition": f'attachment; filename="olivas-report-{safe_name}.pdf"' }, )