import io import logging from fastapi import APIRouter, Depends, Header, 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), x_user_id: str | None = Header(None), ): user_id = get_user_id(x_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"' }, )