diff --git a/app/admin/repositories/cps.py b/app/admin/repositories/cps.py index b678c38..2aad935 100644 --- a/app/admin/repositories/cps.py +++ b/app/admin/repositories/cps.py @@ -51,7 +51,27 @@ def _yuan_to_cents(v: object) -> int | None: def _ts_to_dt(ts: object) -> datetime | None: """秒级时间戳 → tz-aware UTC datetime(绝对时刻,前端按北京展示)。""" - if not ts: + if ts is None: + return None + if isinstance(ts, datetime): + return ts if ts.tzinfo else ts.replace(tzinfo=_BJ_TZ).astimezone(timezone.utc) + s = str(ts).strip() + if not s or s.lower() == "null": + return None + try: + seconds = float(Decimal(s)) + except (InvalidOperation, ValueError): + return None + if seconds == 0: + return None + # 美团文档是秒级时间戳,这里顺手兼容毫秒/微秒,避免上游格式变化导致时间再次落空。 + if abs(seconds) > 10_000_000_000_000: + seconds /= 1_000_000 + elif abs(seconds) > 10_000_000_000: + seconds /= 1_000 + try: + return datetime.fromtimestamp(seconds, tz=timezone.utc) + except (OverflowError, OSError, ValueError): return None diff --git a/app/admin/repositories/stats.py b/app/admin/repositories/stats.py index 202a5ba..49e810a 100644 --- a/app/admin/repositories/stats.py +++ b/app/admin/repositories/stats.py @@ -24,11 +24,13 @@ from app.models.user import User from app.models.wallet import CoinTransaction, WithdrawOrder _BEIJING = timezone(timedelta(hours=8)) -COUPON_REWARD_BIZ_TYPES = ("reward_video", "ad_reward", "coupon", "coupon_reward") +REWARD_VIDEO_BIZ_TYPES = ("reward_video", "ad_reward") +COUPON_REWARD_BIZ_TYPES = ("coupon", "coupon_reward") COMPARISON_REWARD_BIZ_TYPES = ("comparison", "compare_reward", "comparison_reward") EXCLUDED_REWARD_BIZ_TYPES = ("invite_inviter", "invite_invitee", "admin_grant") UNCLASSIFIED_FEED_BIZ_TYPES = ("feed_ad_reward",) REGULAR_TASK_EXCLUDED_BIZ_TYPES = ( + *REWARD_VIDEO_BIZ_TYPES, *COUPON_REWARD_BIZ_TYPES, *COMPARISON_REWARD_BIZ_TYPES, *EXCLUDED_REWARD_BIZ_TYPES, @@ -339,13 +341,28 @@ def dashboard_overview( period_reward_video_coin_total = _sum( CoinTransaction.amount, *period_coin_conds, - CoinTransaction.biz_type.in_(("reward_video", "ad_reward")), + CoinTransaction.biz_type.in_(REWARD_VIDEO_BIZ_TYPES), ) period_feed_ad_coin_total = _sum( CoinTransaction.amount, *period_coin_conds, CoinTransaction.biz_type == "feed_ad_reward", ) + period_feed_ad_conds = ( + AdFeedRewardRecord.created_at >= start_utc, + AdFeedRewardRecord.created_at < end_utc, + AdFeedRewardRecord.status == "granted", + ) + period_coupon_feed_coin_total = _sum( + AdFeedRewardRecord.coin, + *period_feed_ad_conds, + AdFeedRewardRecord.feed_scene == "coupon", + ) + period_comparison_feed_coin_total = _sum( + AdFeedRewardRecord.coin, + *period_feed_ad_conds, + AdFeedRewardRecord.feed_scene == "comparison", + ) period_signin_coin_total = _sum( CoinTransaction.amount, *period_coin_conds, @@ -365,17 +382,23 @@ def dashboard_overview( CoinTransaction.amount, *period_coin_conds, CoinTransaction.biz_type.in_(COUPON_REWARD_BIZ_TYPES), - ) + ) + period_coupon_feed_coin_total period_comparison_reward_coin_total = _sum( CoinTransaction.amount, *period_coin_conds, CoinTransaction.biz_type.in_(COMPARISON_REWARD_BIZ_TYPES), - ) + ) + period_comparison_feed_coin_total period_regular_task_coin_total = _sum( CoinTransaction.amount, *period_coin_conds, CoinTransaction.biz_type.notin_(REGULAR_TASK_EXCLUDED_BIZ_TYPES), ) + period_classified_coin_total = ( + period_coupon_reward_coin_total + + period_comparison_reward_coin_total + + period_reward_video_coin_total + + period_regular_task_coin_total + ) period_cps_orders = list( db.execute( select(CpsOrder).where( @@ -413,6 +436,41 @@ def dashboard_overview( else None ) + total_reward_video_coin_total = _sum( + CoinTransaction.amount, + CoinTransaction.amount > 0, + CoinTransaction.biz_type.in_(REWARD_VIDEO_BIZ_TYPES), + ) + total_coupon_reward_coin_total = _sum( + CoinTransaction.amount, + CoinTransaction.amount > 0, + CoinTransaction.biz_type.in_(COUPON_REWARD_BIZ_TYPES), + ) + _sum( + AdFeedRewardRecord.coin, + AdFeedRewardRecord.status == "granted", + AdFeedRewardRecord.feed_scene == "coupon", + ) + total_comparison_reward_coin_total = _sum( + CoinTransaction.amount, + CoinTransaction.amount > 0, + CoinTransaction.biz_type.in_(COMPARISON_REWARD_BIZ_TYPES), + ) + _sum( + AdFeedRewardRecord.coin, + AdFeedRewardRecord.status == "granted", + AdFeedRewardRecord.feed_scene == "comparison", + ) + total_regular_task_coin_total = _sum( + CoinTransaction.amount, + CoinTransaction.amount > 0, + CoinTransaction.biz_type.notin_(REGULAR_TASK_EXCLUDED_BIZ_TYPES), + ) + total_classified_coin_total = ( + total_coupon_reward_coin_total + + total_comparison_reward_coin_total + + total_reward_video_coin_total + + total_regular_task_coin_total + ) + return { "users": { "total": _count(User), @@ -423,12 +481,12 @@ def dashboard_overview( "dau": today_dau(db), }, "coins": { - # 累计发放金币(coin_transaction 里所有 amount>0 之和;负数是兑换/扣减不计) - "granted_total": _sum(CoinTransaction.amount, CoinTransaction.amount > 0), + # 累计发放金币按大盘 4 个展示分类加总;未分类/运营手动/邀请奖励不进入本指标。 + "granted_total": total_classified_coin_total, "reward_video_coin_total": _sum( CoinTransaction.amount, CoinTransaction.amount > 0, - CoinTransaction.biz_type.in_(("reward_video", "ad_reward")), + CoinTransaction.biz_type.in_(REWARD_VIDEO_BIZ_TYPES), ), "reward_video_watch_count": _count( AdRewardRecord, @@ -456,6 +514,10 @@ def dashboard_overview( CoinTransaction.biz_type == "signin_boost", ), "signin_boost_watch_count": _count(SigninBoostRecord), + "task_coin_total": total_regular_task_coin_total, + "coupon_reward_coin_total": total_coupon_reward_coin_total, + "comparison_reward_coin_total": total_comparison_reward_coin_total, + "regular_task_coin_total": total_regular_task_coin_total, }, "cash": { "withdraw_success_cents": _sum( @@ -492,7 +554,7 @@ def dashboard_overview( "average_saved_cents": period_avg_saved_cents, }, "coins": { - "granted_total": _sum(CoinTransaction.amount, *period_coin_conds), + "granted_total": period_classified_coin_total, "reward_video_coin_total": period_reward_video_coin_total, "feed_ad_coin_total": period_feed_ad_coin_total, "signin_coin_total": period_signin_coin_total,