204 lines
7.1 KiB
Python
204 lines
7.1 KiB
Python
"""Dashboard API endpoints."""
|
|
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from sqlalchemy import select, func, Integer
|
|
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 import Competitor
|
|
from app.models.query import Query as QueryModel
|
|
from app.models.citation_record import CitationRecord
|
|
from app.schemas.dashboard import (
|
|
DashboardStatsResponse,
|
|
DimensionScoreItem,
|
|
PlatformScoreItem,
|
|
RecentQueryItem,
|
|
)
|
|
from app.services.scoring.scoring_service import get_health_level
|
|
from app.services.scoring.brand_scoring_data_service import (
|
|
get_brand_scoring_data_service,
|
|
REQUIRED_PLATFORMS,
|
|
)
|
|
from app.services.cache import get_cache_service, TTL_DASHBOARD
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/stats", response_model=DashboardStatsResponse)
|
|
async def get_dashboard_stats(
|
|
brand_id: uuid.UUID | None = Query(None),
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""
|
|
获取看板统计数据
|
|
|
|
包括:
|
|
- 综合评分(V2)和较昨日变化
|
|
- 健康等级
|
|
- 五维度评分详情
|
|
- 各平台评分列表(含竞品对比)
|
|
- 竞品地位(领先/落后数量)
|
|
- 最近查询记录
|
|
"""
|
|
cache = get_cache_service()
|
|
if brand_id is None:
|
|
brand_stmt = select(Brand).where(Brand.user_id == current_user.id).limit(1)
|
|
brand_result = await db.execute(brand_stmt)
|
|
brand = brand_result.scalar_one_or_none()
|
|
|
|
if brand:
|
|
brand_id = brand.id
|
|
else:
|
|
return DashboardStatsResponse(
|
|
overall_score=0.0,
|
|
health_level="danger",
|
|
score_change=0.0,
|
|
platform_scores=[],
|
|
recent_queries=[],
|
|
dimensions=[],
|
|
competitors_ahead=0,
|
|
competitors_behind=0,
|
|
monitored_platforms=0,
|
|
total_platforms=7,
|
|
)
|
|
|
|
cache_key = f"dashboard:stats:{current_user.id}:{brand_id}"
|
|
cached = await cache.get_json(cache_key)
|
|
if cached is not None:
|
|
return cached
|
|
|
|
brand_stmt = select(Brand).where(Brand.id == brand_id)
|
|
brand_result = await db.execute(brand_stmt)
|
|
brand = brand_result.scalar_one_or_none()
|
|
brand_name = brand.name if brand else None
|
|
|
|
scoring_data_service = get_brand_scoring_data_service()
|
|
|
|
scoring_data = await scoring_data_service.get_brand_scoring_data(
|
|
db, current_user.id, brand
|
|
)
|
|
|
|
overall_score = scoring_data.v2_result.overall_score
|
|
score_change = scoring_data.change_from_yesterday
|
|
|
|
dimensions = [
|
|
DimensionScoreItem(
|
|
name=scoring_data.v2_result.mention_rate.name,
|
|
score=round(scoring_data.v2_result.mention_rate.score, 2),
|
|
max_score=scoring_data.v2_result.mention_rate.max_score,
|
|
percentage=round(scoring_data.v2_result.mention_rate.percentage, 2),
|
|
),
|
|
DimensionScoreItem(
|
|
name=scoring_data.v2_result.recommendation_rank.name,
|
|
score=round(scoring_data.v2_result.recommendation_rank.score, 2),
|
|
max_score=scoring_data.v2_result.recommendation_rank.max_score,
|
|
percentage=round(scoring_data.v2_result.recommendation_rank.percentage, 2),
|
|
),
|
|
DimensionScoreItem(
|
|
name=scoring_data.v2_result.sentiment_score.name,
|
|
score=round(scoring_data.v2_result.sentiment_score.score, 2),
|
|
max_score=scoring_data.v2_result.sentiment_score.max_score,
|
|
percentage=round(scoring_data.v2_result.sentiment_score.percentage, 2),
|
|
),
|
|
DimensionScoreItem(
|
|
name=scoring_data.v2_result.citation_quality.name,
|
|
score=round(scoring_data.v2_result.citation_quality.score, 2),
|
|
max_score=scoring_data.v2_result.citation_quality.max_score,
|
|
percentage=round(scoring_data.v2_result.citation_quality.percentage, 2),
|
|
),
|
|
DimensionScoreItem(
|
|
name=scoring_data.v2_result.competitive_position.name,
|
|
score=round(scoring_data.v2_result.competitive_position.score, 2),
|
|
max_score=scoring_data.v2_result.competitive_position.max_score,
|
|
percentage=round(scoring_data.v2_result.competitive_position.percentage, 2),
|
|
),
|
|
]
|
|
|
|
ahead_count = scoring_data.competitor_data.get("ahead_count", 0)
|
|
behind_count = scoring_data.competitor_data.get("behind_count", 0)
|
|
|
|
platform_scores_dict = scoring_data.platform_scores
|
|
|
|
competitor_scores_dict = await scoring_data_service.get_competitor_platform_scores(
|
|
db, current_user.id, brand_id
|
|
)
|
|
|
|
competitor_stmt = select(Competitor).where(
|
|
Competitor.brand_id == brand_id
|
|
).limit(1)
|
|
competitor_result = await db.execute(competitor_stmt)
|
|
first_competitor = competitor_result.scalar_one_or_none()
|
|
competitor_name = first_competitor.name if first_competitor else None
|
|
|
|
platform_scores = [
|
|
PlatformScoreItem(
|
|
platform=platform,
|
|
score=score,
|
|
competitor_score=competitor_scores_dict.get(platform),
|
|
competitor_name=competitor_name if competitor_scores_dict.get(platform, 0) > 0 else None,
|
|
)
|
|
for platform, score in platform_scores_dict.items()
|
|
]
|
|
|
|
monitored = sum(1 for s in platform_scores_dict.values() if s > 0)
|
|
|
|
recent_queries_stmt = (
|
|
select(QueryModel)
|
|
.where(QueryModel.user_id == current_user.id)
|
|
.order_by(QueryModel.created_at.desc())
|
|
.limit(10)
|
|
)
|
|
recent_queries_result = await db.execute(recent_queries_stmt)
|
|
recent_queries_list = list(recent_queries_result.scalars().all())
|
|
|
|
recent_queries = []
|
|
for query in recent_queries_list:
|
|
citation_count_stmt = select(
|
|
func.count().label("total"),
|
|
func.sum(
|
|
func.cast(
|
|
func.case((CitationRecord.cited == True, 1), else_=0),
|
|
Integer
|
|
)
|
|
).label("cited")
|
|
).where(CitationRecord.query_id == query.id)
|
|
count_result = await db.execute(citation_count_stmt)
|
|
count_row = count_result.one()
|
|
|
|
recent_queries.append(RecentQueryItem(
|
|
id=str(query.id),
|
|
keyword=query.keyword,
|
|
target_brand=query.target_brand,
|
|
citation_count=count_row.cited or 0,
|
|
queried_at=query.last_queried_at or query.created_at,
|
|
))
|
|
|
|
health_level = get_health_level(overall_score)
|
|
|
|
response = DashboardStatsResponse(
|
|
overall_score=round(overall_score, 2),
|
|
health_level=health_level,
|
|
score_change=score_change,
|
|
platform_scores=platform_scores,
|
|
recent_queries=recent_queries,
|
|
dimensions=dimensions,
|
|
competitors_ahead=ahead_count,
|
|
competitors_behind=behind_count,
|
|
monitored_platforms=monitored,
|
|
total_platforms=7,
|
|
brand_name=brand_name,
|
|
)
|
|
|
|
await cache.set_json(
|
|
cache_key,
|
|
response.model_dump(mode="json"),
|
|
expire=TTL_DASHBOARD,
|
|
)
|
|
|
|
return response
|