e1bd0e3ef7
改了什么:新增新版大盘日期窗口聚合、广告收益拆分、比价耗时字段、美团 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。
85 lines
3.2 KiB
Python
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"]],
|
|
)
|