mirror of
https://github.com/stardrophere/InsightRadar.git
synced 2026-06-06 01:57:51 +08:00
232 lines
7.8 KiB
Python
232 lines
7.8 KiB
Python
# app/api/endpoints/events.py
|
|
from datetime import timedelta
|
|
from typing import List
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.dependencies import get_db
|
|
from app.models.models import (
|
|
ExtractedTopic,
|
|
InfoSource,
|
|
RankingLog,
|
|
TargetType,
|
|
TrendingEvent,
|
|
UnifiedEvent,
|
|
utcnow,
|
|
)
|
|
from app.schemas.event_schema import (
|
|
PaginatedUnifiedEventResponse,
|
|
PlatformTrendResponse,
|
|
UnifiedEventResponse,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
# 排名轨迹最多返回多少个点,避免长时间跨度下数据过大
|
|
MAX_RANKING_POINTS = 30
|
|
|
|
|
|
@router.get("/unified", response_model=PaginatedUnifiedEventResponse)
|
|
def list_unified_events(
|
|
min_hot: int = Query(5, ge=0, description="热度阈值,仅返回 hot_score >= 此值的事件"),
|
|
hours: int = Query(48, ge=1, le=720, description="查询最近多少小时的数据"),
|
|
sort_by: str = Query("hot_score", description="排序字段: hot_score | created_at"),
|
|
skip: int = Query(0, ge=0, description="分页偏移量"),
|
|
limit: int = Query(10, ge=1, le=50, description="每页返回条数"),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""分页返回统一事件,附带各平台热搜、排名轨迹和标签。"""
|
|
time_limit = utcnow() - timedelta(hours=hours)
|
|
|
|
# 先查总数,用于前端判断是否还有更多
|
|
base_query = db.query(UnifiedEvent).filter(
|
|
UnifiedEvent.hot_score >= min_hot,
|
|
UnifiedEvent.created_at >= time_limit,
|
|
)
|
|
total = base_query.count()
|
|
|
|
# 分页查询
|
|
if sort_by == "created_at":
|
|
base_query = base_query.order_by(UnifiedEvent.created_at.desc())
|
|
else:
|
|
base_query = base_query.order_by(UnifiedEvent.hot_score.desc(), UnifiedEvent.created_at.desc())
|
|
|
|
events = base_query.offset(skip).limit(limit).all()
|
|
|
|
if not events:
|
|
return PaginatedUnifiedEventResponse(total=total, has_more=False, data=[])
|
|
|
|
event_ids = [ev.id for ev in events]
|
|
|
|
# 批量查询所有相关的热搜条目(避免 N+1)
|
|
trend_rows = (
|
|
db.query(TrendingEvent, InfoSource.source_name)
|
|
.join(InfoSource, TrendingEvent.source_id == InfoSource.id)
|
|
.filter(TrendingEvent.unified_event_id.in_(event_ids))
|
|
.all()
|
|
)
|
|
|
|
# 按 unified_event_id 分组
|
|
trend_map: dict[int, list[tuple]] = {}
|
|
trend_ids: list[int] = []
|
|
for trend, source_name in trend_rows:
|
|
trend_map.setdefault(trend.unified_event_id, []).append((trend, source_name))
|
|
trend_ids.append(trend.id)
|
|
|
|
# 批量查询排名日志(避免逐条查询)
|
|
ranking_map: dict[int, list[int]] = {}
|
|
if trend_ids:
|
|
ranking_rows = (
|
|
db.query(
|
|
RankingLog.event_id,
|
|
RankingLog.ranking_position,
|
|
)
|
|
.filter(
|
|
RankingLog.event_id.in_(trend_ids),
|
|
RankingLog.observed_at >= time_limit,
|
|
)
|
|
.order_by(RankingLog.event_id, RankingLog.observed_at.asc())
|
|
.all()
|
|
)
|
|
for event_id, position in ranking_rows:
|
|
ranking_map.setdefault(event_id, []).append(position)
|
|
|
|
# 批量查询标签
|
|
tag_map: dict[int, list[str]] = {}
|
|
tag_rows = (
|
|
db.query(ExtractedTopic.target_id, ExtractedTopic.topic_keyword)
|
|
.filter(
|
|
ExtractedTopic.target_type == TargetType.EVENT,
|
|
ExtractedTopic.target_id.in_(event_ids),
|
|
)
|
|
.order_by(ExtractedTopic.relevance_score.desc(), ExtractedTopic.created_at.desc())
|
|
.all()
|
|
)
|
|
for target_id, keyword in tag_rows:
|
|
tag_map.setdefault(target_id, []).append(keyword)
|
|
|
|
# 组装响应
|
|
results: list[UnifiedEventResponse] = []
|
|
for ev in events:
|
|
platform_list: list[PlatformTrendResponse] = []
|
|
trends_for_ev = trend_map.get(ev.id, [])
|
|
for trend, source_name in trends_for_ev:
|
|
history = ranking_map.get(trend.id, [])
|
|
# 截取尾部,只保留最近的点
|
|
if len(history) > MAX_RANKING_POINTS:
|
|
history = history[-MAX_RANKING_POINTS:]
|
|
|
|
platform_list.append(
|
|
PlatformTrendResponse(
|
|
source_id=trend.source_id,
|
|
platform_name=source_name,
|
|
headline=trend.current_headline,
|
|
url=trend.event_url,
|
|
current_ranking=trend.current_ranking,
|
|
ranking_history=history,
|
|
)
|
|
)
|
|
|
|
# 取所有关联热搜条目中最新的 updated_at,代表"最后一次在平台热搜榜看到"的时间
|
|
last_active_at = (
|
|
max(t.updated_at for t, _ in trends_for_ev)
|
|
if trends_for_ev
|
|
else ev.updated_at
|
|
)
|
|
|
|
results.append(
|
|
UnifiedEventResponse(
|
|
event_id=ev.id,
|
|
unified_title=ev.unified_title if ev.unified_title else "暂无标题",
|
|
summary=ev.ai_comprehensive_summary,
|
|
hot_score=ev.hot_score,
|
|
created_at=ev.created_at,
|
|
last_active_at=last_active_at,
|
|
platforms=platform_list,
|
|
tags=tag_map.get(ev.id, []),
|
|
)
|
|
)
|
|
|
|
has_more = (skip + limit) < total
|
|
return PaginatedUnifiedEventResponse(total=total, has_more=has_more, data=results)
|
|
|
|
|
|
@router.get("/unified/{event_id}", response_model=UnifiedEventResponse)
|
|
def get_unified_event(
|
|
event_id: int,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""按 ID 查询单个统一事件,用于推荐跳转时的聚光灯展示。"""
|
|
ev = db.query(UnifiedEvent).filter(UnifiedEvent.id == event_id).first()
|
|
if not ev:
|
|
raise HTTPException(status_code=404, detail="Event not found")
|
|
|
|
time_limit = utcnow() - timedelta(hours=720)
|
|
|
|
trend_rows = (
|
|
db.query(TrendingEvent, InfoSource.source_name)
|
|
.join(InfoSource, TrendingEvent.source_id == InfoSource.id)
|
|
.filter(TrendingEvent.unified_event_id == event_id)
|
|
.all()
|
|
)
|
|
|
|
trend_ids = [t.id for t, _ in trend_rows]
|
|
ranking_map: dict[int, list[int]] = {}
|
|
if trend_ids:
|
|
ranking_rows = (
|
|
db.query(RankingLog.event_id, RankingLog.ranking_position)
|
|
.filter(
|
|
RankingLog.event_id.in_(trend_ids),
|
|
RankingLog.observed_at >= time_limit,
|
|
)
|
|
.order_by(RankingLog.event_id, RankingLog.observed_at.asc())
|
|
.all()
|
|
)
|
|
for eid, pos in ranking_rows:
|
|
ranking_map.setdefault(eid, []).append(pos)
|
|
|
|
tag_rows = (
|
|
db.query(ExtractedTopic.topic_keyword)
|
|
.filter(
|
|
ExtractedTopic.target_type == TargetType.EVENT,
|
|
ExtractedTopic.target_id == event_id,
|
|
)
|
|
.order_by(ExtractedTopic.relevance_score.desc())
|
|
.all()
|
|
)
|
|
tags = [row[0] for row in tag_rows]
|
|
|
|
platform_list: list[PlatformTrendResponse] = []
|
|
for trend, source_name in trend_rows:
|
|
history = ranking_map.get(trend.id, [])
|
|
if len(history) > MAX_RANKING_POINTS:
|
|
history = history[-MAX_RANKING_POINTS:]
|
|
platform_list.append(
|
|
PlatformTrendResponse(
|
|
source_id=trend.source_id,
|
|
platform_name=source_name,
|
|
headline=trend.current_headline,
|
|
url=trend.event_url,
|
|
current_ranking=trend.current_ranking,
|
|
ranking_history=history,
|
|
)
|
|
)
|
|
|
|
last_active_at = (
|
|
max(t.updated_at for t, _ in trend_rows)
|
|
if trend_rows
|
|
else ev.updated_at
|
|
)
|
|
|
|
return UnifiedEventResponse(
|
|
event_id=ev.id,
|
|
unified_title=ev.unified_title if ev.unified_title else "暂无标题",
|
|
summary=ev.ai_comprehensive_summary,
|
|
hot_score=ev.hot_score,
|
|
created_at=ev.created_at,
|
|
last_active_at=last_active_at,
|
|
platforms=platform_list,
|
|
tags=tags,
|
|
)
|