geo/backend/app/api/competitor_analysis.py

142 lines
4.2 KiB
Python

import logging
import uuid
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_current_user
from app.database import get_db
from app.models.user import User
from app.models.brand import Brand
from app.models.competitor_insight import CompetitorInsight
from app.schemas.competitor_insight import (
CompetitorAnalysisRequest,
CompetitorInsightResponse,
CompetitorInsightList,
CompetitorGapSummary,
)
from app.services.competitor.competitor_analyzer_service import CompetitorAnalyzerService
logger = logging.getLogger(__name__)
router = APIRouter()
def _to_uuid(value: str | uuid.UUID) -> uuid.UUID:
if isinstance(value, uuid.UUID):
return value
return uuid.UUID(str(value))
async def _get_brand_if_owned(
brand_id: uuid.UUID,
current_user: User,
db: AsyncSession,
) -> Brand:
stmt = select(Brand).where(Brand.id == brand_id, Brand.user_id == _to_uuid(current_user.id))
result = await db.execute(stmt)
brand = result.scalar_one_or_none()
if not brand:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="品牌不存在",
)
return brand
@router.post("/analyze", response_model=CompetitorInsightList)
async def analyze_competitor(
request: CompetitorAnalysisRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
await _get_brand_if_owned(request.brand_id, current_user, db)
service = CompetitorAnalyzerService()
try:
result = await service.analyze_competitor(
brand_id=request.brand_id,
analysis_types=request.analysis_types,
period_days=request.period_days or 30,
)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e),
)
stmt = (
select(CompetitorInsight)
.where(CompetitorInsight.brand_id == request.brand_id)
.order_by(CompetitorInsight.created_at.desc())
)
db_result = await db.execute(stmt)
insights = list(db_result.scalars().all())
return {"items": insights, "total": len(insights)}
@router.get("/brand/{brand_id}", response_model=CompetitorInsightList)
async def get_brand_insights(
brand_id: uuid.UUID,
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
await _get_brand_if_owned(brand_id, current_user, db)
count_stmt = select(func.count()).select_from(CompetitorInsight).where(
CompetitorInsight.brand_id == brand_id,
)
count_result = await db.execute(count_stmt)
total = count_result.scalar_one()
stmt = (
select(CompetitorInsight)
.where(CompetitorInsight.brand_id == brand_id)
.order_by(CompetitorInsight.created_at.desc())
.offset(skip)
.limit(limit)
)
result = await db.execute(stmt)
insights = list(result.scalars().all())
return {"items": insights, "total": total}
@router.get("/{insight_id}", response_model=CompetitorInsightResponse)
async def get_insight_detail(
insight_id: uuid.UUID,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
stmt = select(CompetitorInsight).where(CompetitorInsight.id == insight_id)
result = await db.execute(stmt)
insight = result.scalar_one_or_none()
if not insight:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="洞察不存在",
)
await _get_brand_if_owned(insight.brand_id, current_user, db)
return insight
@router.get("/brand/{brand_id}/gap-summary", response_model=list[CompetitorGapSummary])
async def get_gap_summary(
brand_id: uuid.UUID,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
brand = await _get_brand_if_owned(brand_id, current_user, db)
service = CompetitorAnalyzerService()
gap_summaries = await service.calculate_gap_score(db, brand_id, brand.name)
return gap_summaries