"""广告 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, ) -> AdEcpmRecord: """落一条 eCPM 上报记录。report_date 用北京时间当天,供按天聚合。""" 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, 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()