260 lines
8.3 KiB
Python
260 lines
8.3 KiB
Python
"""监测优化 Analytics API"""
|
||
import logging
|
||
from typing import Optional
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||
from sqlalchemy import select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.api.deps import get_current_user
|
||
from app.database import get_db
|
||
from app.models.analytics import OptimizationInsight, PublishRecord
|
||
from app.models.user import User
|
||
from app.schemas.analytics import (
|
||
ContentPerformanceResponse,
|
||
InsightResponse,
|
||
MetricsResponse,
|
||
MetricsUpdateRequest,
|
||
OverviewStatsResponse,
|
||
PublishRecordCreate,
|
||
PublishRecordResponse,
|
||
TopContentResponse,
|
||
)
|
||
from app.services.analytics import AnalyticsTracker, InsightGenerator
|
||
|
||
logger = logging.getLogger(__name__)
|
||
router = APIRouter()
|
||
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# 辅助:获取当前用户所属组织ID
|
||
# ------------------------------------------------------------------ #
|
||
|
||
async def _get_org_id(current_user: User = Depends(get_current_user)) -> str | None:
|
||
org_id = getattr(current_user, "organization_id", None)
|
||
if not org_id:
|
||
return None
|
||
return str(org_id)
|
||
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# POST /api/v1/analytics/publish - 记录发布
|
||
# ------------------------------------------------------------------ #
|
||
|
||
@router.post(
|
||
"/publish",
|
||
response_model=PublishRecordResponse,
|
||
status_code=status.HTTP_201_CREATED,
|
||
summary="记录内容发布",
|
||
)
|
||
async def record_publish(
|
||
body: PublishRecordCreate,
|
||
org_id: str | None = Depends(_get_org_id),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
if not org_id:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_403_FORBIDDEN,
|
||
detail="用户未关联组织,无法记录发布",
|
||
)
|
||
tracker = AnalyticsTracker(db)
|
||
record = await tracker.record_publish(org_id, body.model_dump())
|
||
return record
|
||
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# PUT /api/v1/analytics/metrics/{publish_id} - 更新指标
|
||
# ------------------------------------------------------------------ #
|
||
|
||
@router.put(
|
||
"/metrics/{publish_id}",
|
||
response_model=MetricsResponse,
|
||
summary="更新内容效果指标(追加快照)",
|
||
)
|
||
async def update_metrics(
|
||
publish_id: str,
|
||
body: MetricsUpdateRequest,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
# 检查发布记录是否属于当前组织
|
||
org_id = str(getattr(current_user, "organization_id", None) or "")
|
||
pr_stmt = select(PublishRecord).where(
|
||
PublishRecord.id == publish_id,
|
||
PublishRecord.organization_id == org_id,
|
||
)
|
||
pr_result = await db.execute(pr_stmt)
|
||
record = pr_result.scalar_one_or_none()
|
||
if not record:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail="发布记录不存在或无权限",
|
||
)
|
||
|
||
tracker = AnalyticsTracker(db)
|
||
metrics = await tracker.update_metrics(publish_id, body.model_dump())
|
||
return metrics
|
||
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# GET /api/v1/analytics/overview - 全局概览
|
||
# ------------------------------------------------------------------ #
|
||
|
||
@router.get(
|
||
"/overview",
|
||
response_model=OverviewStatsResponse,
|
||
summary="获取全局效果概览",
|
||
)
|
||
async def get_overview(
|
||
org_id: str | None = Depends(_get_org_id),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
if not org_id:
|
||
return OverviewStatsResponse(
|
||
total_published=0,
|
||
total_views=0,
|
||
total_interactions=0,
|
||
total_ai_citations=0,
|
||
avg_engagement_rate=0.0,
|
||
platform_distribution={},
|
||
)
|
||
tracker = AnalyticsTracker(db)
|
||
overview = await tracker.get_overview(org_id)
|
||
return overview
|
||
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# GET /api/v1/analytics/content/{publish_id} - 单内容详情
|
||
# ------------------------------------------------------------------ #
|
||
|
||
@router.get(
|
||
"/content/{publish_id}",
|
||
response_model=ContentPerformanceResponse,
|
||
summary="获取单条内容详细表现",
|
||
)
|
||
async def get_content_performance(
|
||
publish_id: str,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
org_id = str(getattr(current_user, "organization_id", None) or "")
|
||
pr_stmt = select(PublishRecord).where(
|
||
PublishRecord.id == publish_id,
|
||
PublishRecord.organization_id == org_id,
|
||
)
|
||
pr_result = await db.execute(pr_stmt)
|
||
if not pr_result.scalar_one_or_none():
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail="发布记录不存在或无权限",
|
||
)
|
||
|
||
tracker = AnalyticsTracker(db)
|
||
performance = await tracker.get_content_performance(publish_id)
|
||
if not performance:
|
||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="未找到数据")
|
||
return performance
|
||
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# GET /api/v1/analytics/top - 排行榜
|
||
# ------------------------------------------------------------------ #
|
||
|
||
@router.get(
|
||
"/top",
|
||
response_model=TopContentResponse,
|
||
summary="获取表现最好内容排行",
|
||
)
|
||
async def get_top_performing(
|
||
sort_by: str = Query(default="views", description="排序字段: views/likes/comments/shares/ai_citation_count/read_completion_rate"),
|
||
limit: int = Query(default=10, ge=1, le=50),
|
||
org_id: str | None = Depends(_get_org_id),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
if not org_id:
|
||
return TopContentResponse(items=[], sort_by=sort_by, total=0)
|
||
tracker = AnalyticsTracker(db)
|
||
items = await tracker.get_top_performing(org_id, limit=limit, sort_by=sort_by)
|
||
return TopContentResponse(items=items, sort_by=sort_by, total=len(items))
|
||
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# GET /api/v1/analytics/insights - 洞察列表
|
||
# ------------------------------------------------------------------ #
|
||
|
||
@router.get(
|
||
"/insights",
|
||
response_model=list[InsightResponse],
|
||
summary="获取洞察列表",
|
||
)
|
||
async def list_insights(
|
||
limit: int = Query(default=20, ge=1, le=100),
|
||
insight_type: Optional[str] = Query(default=None),
|
||
org_id: str | None = Depends(_get_org_id),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
if not org_id:
|
||
return []
|
||
stmt = (
|
||
select(OptimizationInsight)
|
||
.where(OptimizationInsight.organization_id == org_id)
|
||
.order_by(OptimizationInsight.created_at.desc())
|
||
.limit(limit)
|
||
)
|
||
if insight_type:
|
||
stmt = stmt.where(OptimizationInsight.insight_type == insight_type)
|
||
|
||
result = await db.execute(stmt)
|
||
insights = result.scalars().all()
|
||
return insights
|
||
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# POST /api/v1/analytics/insights/generate - 触发生成洞察
|
||
# ------------------------------------------------------------------ #
|
||
|
||
@router.post(
|
||
"/insights/generate",
|
||
response_model=list[dict],
|
||
summary="触发AI生成洞察建议",
|
||
)
|
||
async def generate_insights(
|
||
org_id: str | None = Depends(_get_org_id),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
if not org_id:
|
||
return []
|
||
generator = InsightGenerator()
|
||
insights = await generator.generate_insights(org_id, db)
|
||
return insights
|
||
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# POST /api/v1/analytics/insights/{insight_id}/apply - 标记洞察已应用
|
||
# ------------------------------------------------------------------ #
|
||
|
||
@router.post(
|
||
"/insights/{insight_id}/apply",
|
||
response_model=InsightResponse,
|
||
summary="标记洞察已应用",
|
||
)
|
||
async def apply_insight(
|
||
insight_id: str,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
org_id = str(getattr(current_user, "organization_id", None) or "")
|
||
stmt = select(OptimizationInsight).where(
|
||
OptimizationInsight.id == insight_id,
|
||
OptimizationInsight.organization_id == org_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="洞察不存在或无权限")
|
||
|
||
insight.applied = True
|
||
await db.commit()
|
||
await db.refresh(insight)
|
||
return insight
|