155 lines
4.6 KiB
Python
155 lines
4.6 KiB
Python
import logging
|
|
import uuid
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
from fastapi.responses import StreamingResponse
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from starlette.responses import Response
|
|
|
|
from app.api.deps import get_current_user
|
|
from app.database import get_db
|
|
from app.models.brand import Brand
|
|
from app.models.user import User
|
|
from app.schemas.scoring import CitationResult
|
|
from app.services.citation.citation import export_citations_csv, export_citations_pdf
|
|
from app.services.scoring.scoring_service import ScoringService, ScoringResultV2
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
async def _compute_v2_scores(
|
|
db: AsyncSession,
|
|
user_id: uuid.UUID,
|
|
brand_id: uuid.UUID,
|
|
) -> ScoringResultV2 | None:
|
|
try:
|
|
from app.api.scoring import (
|
|
_get_citations_for_brand,
|
|
_analyze_sentiments_for_citations,
|
|
)
|
|
|
|
total_queries, brand_citations, _, competitor_mentions = (
|
|
await _get_citations_for_brand(db, user_id, brand_id)
|
|
)
|
|
|
|
if total_queries == 0:
|
|
return None
|
|
|
|
brand_stmt = select(Brand).where(
|
|
Brand.id == brand_id, Brand.user_id == user_id
|
|
)
|
|
brand_result = await db.execute(brand_stmt)
|
|
brand = brand_result.scalar_one_or_none()
|
|
if not brand:
|
|
return None
|
|
|
|
sentiment_counts = await _analyze_sentiments_for_citations(
|
|
brand_name=brand.name,
|
|
brand_citations=brand_citations,
|
|
)
|
|
|
|
citation_results = [
|
|
CitationResult(
|
|
cited=c.cited,
|
|
position=c.citation_position,
|
|
citation_text=c.citation_text,
|
|
sentiment=c.sentiment or "neutral",
|
|
confidence=c.confidence or 0.0,
|
|
)
|
|
for c in brand_citations
|
|
]
|
|
|
|
positions = [c.citation_position for c in brand_citations if c.cited]
|
|
|
|
scoring_service = ScoringService()
|
|
return scoring_service.calculate_v2(
|
|
mentioned_count=len(brand_citations),
|
|
total_queries=total_queries,
|
|
positions=positions,
|
|
sentiment_counts=sentiment_counts,
|
|
citations=citation_results,
|
|
brand_mentions=len(brand_citations),
|
|
competitor_mentions=competitor_mentions,
|
|
)
|
|
except Exception:
|
|
logger.warning("V2 scoring failed for brand %s", brand_id, exc_info=True)
|
|
return None
|
|
|
|
|
|
@router.get("/export/csv")
|
|
async def export_report(
|
|
query_id: uuid.UUID = Query(...),
|
|
brand_id: Optional[uuid.UUID] = Query(None),
|
|
format: str = Query("csv"),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
if format != "csv":
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Only CSV format is supported",
|
|
)
|
|
|
|
try:
|
|
v2_result = None
|
|
if brand_id is not None:
|
|
v2_result = await _compute_v2_scores(db, current_user.id, brand_id)
|
|
|
|
csv_content = await export_citations_csv(
|
|
db, current_user.id, query_id, v2_result=v2_result
|
|
)
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=str(e),
|
|
)
|
|
|
|
date_str = datetime.now().strftime("%Y%m%d")
|
|
filename = f"geo-report-{date_str}.csv"
|
|
|
|
return StreamingResponse(
|
|
iter([csv_content]),
|
|
media_type="text/csv",
|
|
headers={
|
|
"Content-Disposition": f'attachment; filename="{filename}"',
|
|
},
|
|
)
|
|
|
|
|
|
@router.get("/export/pdf")
|
|
async def export_pdf(
|
|
query_id: Optional[uuid.UUID] = None,
|
|
brand_id: Optional[uuid.UUID] = Query(None),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
try:
|
|
v2_result = None
|
|
if brand_id is not None:
|
|
v2_result = await _compute_v2_scores(db, current_user.id, brand_id)
|
|
|
|
pdf_bytes = await export_citations_pdf(
|
|
db, current_user.id, query_id, v2_result=v2_result
|
|
)
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=str(e),
|
|
)
|
|
|
|
date_str = datetime.now().strftime("%Y%m%d")
|
|
filename = f"geo-report-{date_str}.pdf"
|
|
|
|
return Response(
|
|
content=pdf_bytes,
|
|
media_type="application/pdf",
|
|
headers={
|
|
"Content-Disposition": f'attachment; filename="{filename}"',
|
|
},
|
|
)
|