olivas/backend/app/api/endpoints/reports.py
Vadym Samoilenko f217a5aea6 Add Azure AD SSO authentication for backend and frontend
Replace X-User-Id header auth with Azure AD JWT token validation.
Backend validates tokens via JWKS, frontend uses MSAL for login/token
acquisition. Adds logout button, 401 handling, and configurable
AZURE_AUTH_ENABLED toggle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 18:41:06 +00:00

88 lines
3.2 KiB
Python

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"'
},
)