f7d86011c1
- 新增 GET /admin/api/ad-revenue-report:展示条数/收益 + 复用金币审计逐条复算做发奖对账 - ad_ecpm/ad_reward/ad_feed_reward 各加 app_env + our_code_id 两列(alembic 迁移) - ecpm-report / feed-reward 接收并落库 app_env/our_code_id;激励发奖按 ad_session_id 回填 - ad_audit 抽出 audit_rows,报表与逐条审计复用同一复算口径 - 组级 matched 改「组内逐条全一致」,避免应发和==实发和的互相抵消掩盖错误 - list_feedbacks 改 offset 分页并返回 total(配合 admin 页码分页) - 反馈正文上限 _CONTENT_MAX 2000→200 - 文档:新增 admin-ad-revenue-report,更新 ecpm/feed-reward/feedback 及对应 db docs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: OuYingJun1024 <1034284404@qq.com> Reviewed-on: #54 Co-authored-by: ouzhou <ouzhou@wonderable.ai> Co-committed-by: ouzhou <ouzhou@wonderable.ai>
76 lines
3.2 KiB
Python
76 lines
3.2 KiB
Python
"""admin 广告收益报表:按 用户/日期/广告类型/应用/代码位 聚合 展示条数 / 收益 / 金币。
|
|
|
|
任意已登录 admin 可看(只读,不涉及资金操作)。聚合逻辑在 app/admin/repositories/ad_revenue.py。
|
|
"""
|
|
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
|
|
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,闭区间,默认=date_from")] = 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=按小时(北京时间);区间>1 天建议用 day")
|
|
] = "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} 天")
|
|
|
|
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"],
|
|
items=[AdRevenueRow(**r) for r in result["items"]],
|
|
)
|