136 lines
4.0 KiB
Python
136 lines
4.0 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()
|
|
|
|
|
|
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 == 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
|