Files
shaguabijia-app-server/app/repositories/ad_ecpm.py
T
OuYingJun1024 bebd694fb5 feat(ad-revenue): admin 广告收益报表(按 用户/日期/类型/应用/代码位 聚合)
- 新增 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>
2026-06-15 21:55:34 +08:00

87 lines
2.7 KiB
Python

"""广告 eCPM 上报 CRUD(内部收益统计/对账)。
客户端在广告展示后(onAdShow)读到 eCPM,经鉴权接口上报,这里落库。鉴权接口已确保
user 存在(JWT),故不做 UnknownUser 校验。best-effort 上报:丢一两条不影响业务,
穿山甲后台报表是结算权威兜底。
"""
from __future__ import annotations
from sqlalchemy import func, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from app.core.rewards import cn_today
from app.models.ad_ecpm import AdEcpmRecord
def create_ecpm_record(
db: Session,
user_id: int,
*,
ad_type: str,
ecpm_raw: str,
ad_session_id: str | None = None,
adn: str | None = None,
slot_id: str | None = None,
app_env: str | None = None,
our_code_id: str | None = None,
) -> AdEcpmRecord:
"""落一条 eCPM 上报记录。report_date 用北京时间当天,供按天聚合。
app_env(prod/test)与 our_code_id(我们后台配置的 104xxx 代码位)供广告收益报表按
应用/代码位聚合;与 adn(实际填充子渠道)/slot_id(底层 rit)是两组不同口径。
"""
if ad_session_id:
existing = find_by_session(db, user_id=user_id, ad_session_id=ad_session_id)
if existing is not None:
return existing
rec = AdEcpmRecord(
user_id=user_id,
ad_type=ad_type,
ad_session_id=ad_session_id,
adn=adn,
slot_id=slot_id,
app_env=app_env,
our_code_id=our_code_id,
ecpm_raw=ecpm_raw,
report_date=cn_today().isoformat(),
)
db.add(rec)
try:
db.commit()
except IntegrityError:
db.rollback()
if ad_session_id:
existing = find_by_session(db, user_id=user_id, ad_session_id=ad_session_id)
if existing is not None:
return existing
raise
db.refresh(rec)
return rec
def find_by_session(
db: Session, *, user_id: int, ad_session_id: str | None
) -> AdEcpmRecord | None:
"""按广告会话找 eCPM。旧客户端无 ad_session_id 时返回 None。"""
if not ad_session_id:
return None
return db.execute(
select(AdEcpmRecord).where(
AdEcpmRecord.user_id == user_id,
AdEcpmRecord.ad_session_id == ad_session_id,
)
).scalar_one_or_none()
def count_today(db: Session, user_id: int) -> int:
"""该用户今日(北京时间)上报的 eCPM 条数,排查/对账辅助用。"""
return db.execute(
select(func.count())
.select_from(AdEcpmRecord)
.where(
AdEcpmRecord.user_id == user_id,
AdEcpmRecord.report_date == cn_today().isoformat(),
)
).scalar_one()