"""监测优化 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