This commit is contained in:
Neo
2026-02-04 21:39:01 +08:00
parent ee773a9b52
commit a3f4d04335
148 changed files with 31455 additions and 182 deletions

View File

@@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
"""
充值统计任务
功能说明:
"日期"为粒度,统计充值数据
数据来源:
- dwd_recharge_order: 充值订单
- dim_member_card_account: 会员卡账户(余额快照)
目标表:
billiards_dws.dws_finance_recharge_summary
更新策略:
- 更新频率:每日更新
- 幂等方式delete-before-insert按日期
业务规则:
- 首充/续充:通过 is_first 字段区分
- 现金/赠送:通过 pay_money/gift_money 区分
- 卡余额:区分储值卡和赠送卡
作者ETL团队
创建日期2026-02-01
"""
from __future__ import annotations
from datetime import date, datetime, timedelta
from decimal import Decimal
from typing import Any, Dict, List, Optional, Tuple
from .base_dws_task import BaseDwsTask, TaskContext
class FinanceRechargeTask(BaseDwsTask):
"""
充值统计任务
"""
def get_task_code(self) -> str:
return "DWS_FINANCE_RECHARGE"
def get_target_table(self) -> str:
return "dws_finance_recharge_summary"
def get_primary_keys(self) -> List[str]:
return ["site_id", "stat_date"]
def extract(self, context: TaskContext) -> Dict[str, Any]:
start_date = context.window_start.date() if hasattr(context.window_start, 'date') else context.window_start
end_date = context.window_end.date() if hasattr(context.window_end, 'date') else context.window_end
site_id = context.store_id
recharge_summary = self._extract_recharge_summary(site_id, start_date, end_date)
card_balances = self._extract_card_balances(site_id, end_date)
return {
'recharge_summary': recharge_summary,
'card_balances': card_balances,
'start_date': start_date,
'end_date': end_date,
'site_id': site_id
}
def transform(self, extracted: Dict[str, Any], context: TaskContext) -> List[Dict[str, Any]]:
recharge_summary = extracted['recharge_summary']
card_balances = extracted['card_balances']
site_id = extracted['site_id']
results = []
for recharge in recharge_summary:
stat_date = recharge.get('stat_date')
# 获取当日卡余额快照
balance = card_balances if stat_date == extracted['end_date'] else {}
record = {
'site_id': site_id,
'tenant_id': self.config.get("app.tenant_id", site_id),
'stat_date': stat_date,
'recharge_count': self.safe_int(recharge.get('recharge_count', 0)),
'recharge_total': self.safe_decimal(recharge.get('recharge_total', 0)),
'recharge_cash': self.safe_decimal(recharge.get('recharge_cash', 0)),
'recharge_gift': self.safe_decimal(recharge.get('recharge_gift', 0)),
'first_recharge_count': self.safe_int(recharge.get('first_recharge_count', 0)),
'first_recharge_cash': self.safe_decimal(recharge.get('first_recharge_cash', 0)),
'first_recharge_gift': self.safe_decimal(recharge.get('first_recharge_gift', 0)),
'first_recharge_total': self.safe_decimal(recharge.get('first_recharge_total', 0)),
'renewal_count': self.safe_int(recharge.get('renewal_count', 0)),
'renewal_cash': self.safe_decimal(recharge.get('renewal_cash', 0)),
'renewal_gift': self.safe_decimal(recharge.get('renewal_gift', 0)),
'renewal_total': self.safe_decimal(recharge.get('renewal_total', 0)),
'recharge_member_count': self.safe_int(recharge.get('recharge_member_count', 0)),
'new_member_count': self.safe_int(recharge.get('new_member_count', 0)),
'total_card_balance': self.safe_decimal(balance.get('total_balance', 0)),
'cash_card_balance': self.safe_decimal(balance.get('cash_balance', 0)),
'gift_card_balance': self.safe_decimal(balance.get('gift_balance', 0)),
}
results.append(record)
return results
def load(self, transformed: List[Dict[str, Any]], context: TaskContext) -> Dict:
if not transformed:
return {"counts": {"fetched": 0, "inserted": 0, "updated": 0, "skipped": 0, "errors": 0}}
deleted = self.delete_existing_data(context, date_col="stat_date")
inserted = self.bulk_insert(transformed)
return {
"counts": {"fetched": len(transformed), "inserted": inserted, "updated": 0, "skipped": 0, "errors": 0},
"extra": {"deleted": deleted}
}
def _extract_recharge_summary(self, site_id: int, start_date: date, end_date: date) -> List[Dict[str, Any]]:
sql = """
SELECT
DATE(create_time) AS stat_date,
COUNT(*) AS recharge_count,
SUM(pay_money + gift_money) AS recharge_total,
SUM(pay_money) AS recharge_cash,
SUM(gift_money) AS recharge_gift,
COUNT(CASE WHEN is_first = 1 THEN 1 END) AS first_recharge_count,
SUM(CASE WHEN is_first = 1 THEN pay_money ELSE 0 END) AS first_recharge_cash,
SUM(CASE WHEN is_first = 1 THEN gift_money ELSE 0 END) AS first_recharge_gift,
SUM(CASE WHEN is_first = 1 THEN pay_money + gift_money ELSE 0 END) AS first_recharge_total,
COUNT(CASE WHEN is_first != 1 OR is_first IS NULL THEN 1 END) AS renewal_count,
SUM(CASE WHEN is_first != 1 OR is_first IS NULL THEN pay_money ELSE 0 END) AS renewal_cash,
SUM(CASE WHEN is_first != 1 OR is_first IS NULL THEN gift_money ELSE 0 END) AS renewal_gift,
SUM(CASE WHEN is_first != 1 OR is_first IS NULL THEN pay_money + gift_money ELSE 0 END) AS renewal_total,
COUNT(DISTINCT member_id) AS recharge_member_count,
COUNT(DISTINCT CASE WHEN is_first = 1 THEN member_id END) AS new_member_count
FROM billiards_dwd.dwd_recharge_order
WHERE site_id = %s AND DATE(create_time) >= %s AND DATE(create_time) <= %s
GROUP BY DATE(create_time)
"""
rows = self.db.query(sql, (site_id, start_date, end_date))
return [dict(row) for row in rows] if rows else []
def _extract_card_balances(self, site_id: int, stat_date: date) -> Dict[str, Decimal]:
CASH_CARD_TYPE_ID = 2793249295533893
GIFT_CARD_TYPE_IDS = [2791990152417157, 2793266846533445, 2794699703437125]
sql = """
SELECT card_type_id, SUM(balance) AS total_balance
FROM billiards_dwd.dim_member_card_account
WHERE site_id = %s AND valid_to IS NULL
GROUP BY card_type_id
"""
rows = self.db.query(sql, (site_id,))
cash_balance = Decimal('0')
gift_balance = Decimal('0')
for row in (rows or []):
card_type_id = row['card_type_id']
balance = self.safe_decimal(row['total_balance'])
if card_type_id == CASH_CARD_TYPE_ID:
cash_balance += balance
elif card_type_id in GIFT_CARD_TYPE_IDS:
gift_balance += balance
return {
'cash_balance': cash_balance,
'gift_balance': gift_balance,
'total_balance': cash_balance + gift_balance
}
__all__ = ['FinanceRechargeTask']