diff --git a/app/admin/repositories/ad_revenue.py b/app/admin/repositories/ad_revenue.py index 8dfcf2d..c0d8c94 100644 --- a/app/admin/repositories/ad_revenue.py +++ b/app/admin/repositories/ad_revenue.py @@ -385,10 +385,12 @@ def ad_revenue_report( for k, v in type_map.items() } - # DAU:复用大盘「今日活跃」口径(stats.today_dau,last_login_at)。该口径只能算今日, - # 故仅当查询=今日单天时给值;历史 / 多天区间返回 None,前端显示「-」。 - is_today = date_from == date_to == rewards.cn_today().isoformat() - dau = admin_stats.today_dau(db) if is_today else None + # DAU:复用数据大盘活跃用户口径(登录 + 开始比价 + 开始领券,按用户去重),按所选日期区间 + # 统计(含今日),历史 / 多天区间同样有值。ARPU = 区间预估收益 ÷ 区间活跃用户。全局口径, + # 不随 user / ad_type / feed_scene / app_env 筛选变化(活跃用户口径无这些维度)。 + dau = admin_stats.period_active_dau( + db, _date.fromisoformat(date_from), _date.fromisoformat(date_to) + ) # 主表「逐行」= 单次广告行为(2026-07 按「一次比价/领券放一块」聚合):激励视频 = 一次观看一行(展示+发奖 # 按 ad_session_id 合并);一次比价 / 一次领券 = 该次整场多条广告按 ad_session_id 聚成一行(展开看逐条)。 diff --git a/app/admin/repositories/stats.py b/app/admin/repositories/stats.py index 202a5ba..f799826 100644 --- a/app/admin/repositories/stats.py +++ b/app/admin/repositories/stats.py @@ -55,29 +55,10 @@ def _beijing_today_start_utc() -> datetime: def today_dau(db: Session) -> int: """今日活跃用户数(DAU):登录 + 开始比价 + 开始领券,按用户去重。 - 广告收益报表复用这个函数;历史窗口 DAU 由 dashboard_overview 的 period 口径另算。 + = period_active_dau(今天, 今天);历史 / 多天窗口用 period_active_dau 传区间(广告收益报表复用)。 """ today_bj = datetime.now(_BEIJING).date() - today_start = _beijing_today_start_utc() - tomorrow_start = today_start + timedelta(days=1) - login_user_ids = _id_set( - db, - select(User.id).where(User.last_login_at >= today_start, User.last_login_at < tomorrow_start), - ) - compare_start_user_ids = _event_user_ids( - db, (COMPARE_START_EVENT,), today_start, tomorrow_start - ) - coupon_event_user_ids = _event_user_ids( - db, (COUPON_START_EVENT,), today_start, tomorrow_start - ) - coupon_claim_user_ids = _id_set( - db, - select(CouponPromptEngagement.user_id).where( - CouponPromptEngagement.engage_date == today_bj, - CouponPromptEngagement.engage_type == "claim_started", - ), - ) - return len(login_user_ids | compare_start_user_ids | coupon_event_user_ids | coupon_claim_user_ids) + return period_active_dau(db, today_bj, today_bj) def _default_period_end() -> date: @@ -134,6 +115,56 @@ def _event_user_ids( ) +def _period_active_user_ids( + db: Session, + *, + start_utc: datetime, + end_utc: datetime, + period_from: date, + period_to: date, +) -> set[int]: + """区间去重活跃用户 id 集合:登录(last_login_at)+ 开始比价(real_compare_start)+ + 开始领券(real_coupon_start / claim_started)。 + + user / analytics_event 用 UTC aware 边界 [start_utc, end_utc);coupon_prompt_engagement + 的 engage_date 是北京自然日 date 列,用 [period_from, period_to] 闭区间。今日 / 历史 / 多天通用。 + """ + login_user_ids = _id_set( + db, + select(User.id).where(User.last_login_at >= start_utc, User.last_login_at < end_utc), + ) + compare_start_user_ids = _event_user_ids(db, (COMPARE_START_EVENT,), start_utc, end_utc) + coupon_event_user_ids = _event_user_ids(db, (COUPON_START_EVENT,), start_utc, end_utc) + coupon_claim_user_ids = _id_set( + db, + select(CouponPromptEngagement.user_id).where( + CouponPromptEngagement.engage_date >= period_from, + CouponPromptEngagement.engage_date <= period_to, + CouponPromptEngagement.engage_type == "claim_started", + ), + ) + return login_user_ids | compare_start_user_ids | coupon_event_user_ids | coupon_claim_user_ids + + +def period_active_dau(db: Session, date_from: date, date_to: date) -> int: + """任意北京自然日区间 [date_from, date_to] 的去重活跃用户数。 + + 与数据大盘 period.users.active 同口径(登录 + 开始比价 + 开始领券);广告收益报表按所选 + 日期区间(含今日)复用,ARPU = 区间预估收益 ÷ 本数。全局口径,不随用户 / 类型 / 场景筛选变化。 + """ + period_from, period_to = _normalize_period(date_from, date_to) + start_utc, end_utc, _start_local, _end_local = _period_bounds(period_from, period_to) + return len( + _period_active_user_ids( + db, + start_utc=start_utc, + end_utc=end_utc, + period_from=period_from, + period_to=period_to, + ) + ) + + def _commission_rate_percent(raw: str | None) -> Decimal | None: """美团 commissionRate 原值: "300"=3%, "10"=0.1%;也兼容 "3%"。""" if raw is None: @@ -261,24 +292,12 @@ def dashboard_overview( period_new_user_ids = _user_id_set( select(User.id).where(User.created_at >= start_utc, User.created_at < end_utc) ) - login_user_ids = _user_id_set( - select(User.id).where(User.last_login_at >= start_utc, User.last_login_at < end_utc) - ) - compare_start_user_ids = _event_user_ids( - db, (COMPARE_START_EVENT,), start_utc, end_utc - ) - coupon_event_user_ids = _event_user_ids( - db, (COUPON_START_EVENT,), start_utc, end_utc - ) - coupon_claim_user_ids = _user_id_set( - select(CouponPromptEngagement.user_id).where( - CouponPromptEngagement.engage_date >= period_from, - CouponPromptEngagement.engage_date <= period_to, - CouponPromptEngagement.engage_type == "claim_started", - ) - ) - period_active_user_ids = ( - login_user_ids | compare_start_user_ids | coupon_event_user_ids | coupon_claim_user_ids + period_active_user_ids = _period_active_user_ids( + db, + start_utc=start_utc, + end_utc=end_utc, + period_from=period_from, + period_to=period_to, ) period_retained_new_user_ids = period_new_user_ids & period_active_user_ids period_retention_rate = ( @@ -295,32 +314,17 @@ def dashboard_overview( ComparisonRecord.created_at >= day_start_local, ComparisonRecord.created_at < day_end_local, ) - daily_login_user_ids = _user_id_set( - select(User.id).where( - User.last_login_at >= day_start_utc, - User.last_login_at < day_end_utc, - ) - ) - daily_compare_start_user_ids = _event_user_ids( - db, (COMPARE_START_EVENT,), day_start_utc, day_end_utc - ) - daily_coupon_event_user_ids = _event_user_ids( - db, (COUPON_START_EVENT,), day_start_utc, day_end_utc - ) - daily_coupon_claim_user_ids = _user_id_set( - select(CouponPromptEngagement.user_id).where( - CouponPromptEngagement.engage_date == cur_date, - CouponPromptEngagement.engage_type == "claim_started", - ) - ) trend_points.append( { "date": cur_date, "active_users": len( - daily_login_user_ids - | daily_compare_start_user_ids - | daily_coupon_event_user_ids - | daily_coupon_claim_user_ids + _period_active_user_ids( + db, + start_utc=day_start_utc, + end_utc=day_end_utc, + period_from=cur_date, + period_to=cur_date, + ) ), "new_users": _count( User, diff --git a/app/admin/schemas/ad_revenue.py b/app/admin/schemas/ad_revenue.py index cec29b2..cf875c3 100644 --- a/app/admin/schemas/ad_revenue.py +++ b/app/admin/schemas/ad_revenue.py @@ -138,7 +138,9 @@ class AdRevenueReportOut(BaseModel): ) dau: int | None = Field( None, - description="今日活跃用户数(复用大盘口径,last_login_at);**仅查询=今日单天时有值**,历史/多天为 null", + description="所选日期区间的去重活跃用户数(口径同数据大盘 period.users.active:登录 + 开始比价 + " + "开始领券)。按 date_from~date_to 区间统计,含今日、近 7 天、近 30 天等任意区间;全局口径," + "不随 user_id / ad_type / feed_scene / app_env 筛选变化", ) total: int = Field(..., description="广告事件总数(全量,不受分页影响;= 当前筛选下的分页总条数)") truncated: bool = Field(..., description="当前页之后是否还有更多事件(len(events) > offset + limit)")