Files
shaguabijia-app-server/app/admin/routers/ad_revenue.py
T
lowmaster-chen e1bd0e3ef7 feat: 接入数据大盘聚合与美团 CPS 对账
改了什么:新增新版大盘日期窗口聚合、广告收益拆分、比价耗时字段、美团 CPS 拉单入库与大盘展示,并同步反馈审核和邀请奖励下线口径。
验证:python -m pytest tests/test_admin_read.py tests/test_admin_write.py tests/test_compare_record.py tests/test_invite.py tests/test_feedback.py -q。
2026-06-28 09:56:13 +08:00

85 lines
3.2 KiB
Python

"""admin 广告收益报表。"""
from __future__ import annotations
from datetime import date as _date
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Query
from app.admin.deps import AdminDb, get_current_admin
from app.admin.repositories import ad_revenue
from app.admin.schemas.ad_revenue import (
AdRevenueDaily,
AdRevenueReportOut,
AdRevenueRow,
AdRevenueTypeSummary,
)
from app.core.rewards import cn_today
router = APIRouter(
prefix="/admin/api/ad-revenue-report",
tags=["admin-ad-revenue-report"],
dependencies=[Depends(get_current_admin)],
)
_MAX_RANGE_DAYS = 92
def _parse_day(value: str | None, *, field: str, default: _date) -> _date:
if value is None:
return default
try:
return _date.fromisoformat(value)
except ValueError as e:
raise HTTPException(status_code=422, detail=f"{field} 需为 YYYY-MM-DD") from e
@router.get("", response_model=AdRevenueReportOut, summary="广告收益报表")
def get_ad_revenue_report(
db: AdminDb,
date_from: Annotated[str | None, Query(description="起始日 北京时间 YYYY-MM-DD")] = None,
date_to: Annotated[str | None, Query(description="结束日 北京时间 YYYY-MM-DD")] = None,
user_id: Annotated[int | None, Query(description="只看某用户")] = None,
ad_type: Annotated[str | None, Query(description="reward_video / feed / draw")] = None,
granularity: Annotated[str, Query(description="day / hour")] = "day",
limit: Annotated[int, Query(ge=1, le=1000)] = 500,
) -> AdRevenueReportOut:
today = cn_today()
d_from = _parse_day(date_from, field="date_from", default=today)
d_to = _parse_day(date_to, field="date_to", default=d_from)
if d_to < d_from:
raise HTTPException(status_code=422, detail="date_to 不能早于 date_from")
if (d_to - d_from).days + 1 > _MAX_RANGE_DAYS:
raise HTTPException(status_code=422, detail=f"区间最长 {_MAX_RANGE_DAYS}")
if ad_type is not None and ad_type not in {"reward_video", "feed", "draw"}:
raise HTTPException(status_code=422, detail="ad_type 需为 reward_video/feed/draw")
if granularity not in {"day", "hour"}:
raise HTTPException(status_code=422, detail="granularity 需为 day/hour")
result = ad_revenue.ad_revenue_report(
db,
date_from=d_from.isoformat(),
date_to=d_to.isoformat(),
user_id=user_id,
ad_type=ad_type,
granularity=granularity,
limit=limit,
)
return AdRevenueReportOut(
date_from=d_from.isoformat(),
date_to=d_to.isoformat(),
daily=[AdRevenueDaily(**d) for d in result["daily"]],
total=result["total"],
truncated=result["truncated"],
total_impressions=result["total_impressions"],
total_revenue_yuan=result["total_revenue_yuan"],
total_expected_coin=result["total_expected_coin"],
total_actual_coin=result["total_actual_coin"],
mismatch_count=result["mismatch_count"],
by_ad_type={
key: AdRevenueTypeSummary(**value)
for key, value in result["by_ad_type"].items()
},
items=[AdRevenueRow(**r) for r in result["items"]],
)