73 KiB
DWS 层任务详解
本文档说明飞球 ETL 系统中 DWS(数据服务层)的所有任务。 DWS 层负责从 DWD 层读取明细数据,按业务维度聚合计算后写入汇总表, 服务于助教业绩、会员分析、财务统计、指数算法等业务场景。
概述
DWS 层共有 15 个已注册任务,按业务域分为四组:
助教业绩域(5 个)
| 任务代码 | Python 类 | 目标表 | 粒度 | 更新策略 |
|---|---|---|---|---|
DWS_ASSISTANT_DAILY |
AssistantDailyTask |
dws_assistant_daily_detail |
日期+助教 | delete-before-insert |
DWS_ASSISTANT_MONTHLY |
AssistantMonthlyTask |
dws_assistant_monthly_summary |
月份+助教 | delete-before-insert |
DWS_ASSISTANT_CUSTOMER |
AssistantCustomerTask |
dws_assistant_customer_stats |
日期+助教+会员 | delete-before-insert |
DWS_ASSISTANT_SALARY |
AssistantSalaryTask |
dws_assistant_salary_calc |
月份+助教 | delete-before-insert |
DWS_ASSISTANT_FINANCE |
AssistantFinanceTask |
dws_assistant_finance_analysis |
日期+助教 | delete-before-insert |
会员分析域(2 个)
| 任务代码 | Python 类 | 目标表 | 粒度 | 更新策略 |
|---|---|---|---|---|
DWS_MEMBER_CONSUMPTION |
MemberConsumptionTask |
dws_member_consumption_summary |
日期+会员 | delete-before-insert |
DWS_MEMBER_VISIT |
MemberVisitTask |
dws_member_visit_detail |
日期+会员+结账单 | delete-before-insert |
财务统计域(4 个)
| 任务代码 | Python 类 | 目标表 | 粒度 | 更新策略 |
|---|---|---|---|---|
DWS_FINANCE_DAILY |
FinanceDailyTask |
dws_finance_daily_summary |
日期 | delete-before-insert |
DWS_FINANCE_RECHARGE |
FinanceRechargeTask |
dws_finance_recharge_summary |
日期 | delete-before-insert |
DWS_FINANCE_INCOME_STRUCTURE |
FinanceIncomeStructureTask |
dws_finance_income_structure |
日期+收入类型 | delete-before-insert |
DWS_FINANCE_DISCOUNT_DETAIL |
FinanceDiscountDetailTask |
dws_finance_discount_detail |
日期+折扣类型 | delete-before-insert |
运维任务(4 个)
| 任务代码 | Python 类 | 继承 | 说明 | 更新策略 |
|---|---|---|---|---|
DWS_BUILD_ORDER_SUMMARY |
DwsBuildOrderSummaryTask |
BaseTask |
构建订单汇总中间表 | delete-before-insert |
DWS_RETENTION_CLEANUP |
DwsRetentionCleanupTask |
BaseDwsTask |
按时间分层清理历史数据 | DELETE |
DWS_MV_REFRESH_FINANCE_DAILY |
DwsMvRefreshFinanceDailyTask |
BaseMvRefreshTask |
刷新财务日报物化视图 | REFRESH |
DWS_MV_REFRESH_ASSISTANT_DAILY |
DwsMvRefreshAssistantDailyTask |
BaseMvRefreshTask |
刷新助教日报物化视图 | REFRESH |
注册位置:
orchestration/task_registry.py除DWS_BUILD_ORDER_SUMMARY继承BaseTask外,其余 14 个任务均继承BaseDwsTask(或其子类BaseMvRefreshTask)。
BaseDwsTask 公共机制
BaseDwsTask(位于 tasks/dws/base_dws_task.py)继承自 BaseTask,为所有 DWS 层业务任务提供统一的基础设施。核心能力包括:
- 时间分层与窗口计算 — 按业务需要选取不同跨度的数据范围
- 配置缓存 — 从 DWS 配置表加载业绩档位、等级定价、奖金规则等,带 TTL 缓存
- 幂等更新(delete-before-insert) — 先删后插,保证重跑结果一致
- 批量写入(bulk_insert / upsert) — 两种落库方式,适配不同场景
- DWD 数据读取 — 分批迭代或直接查询 DWD 层数据
- SCD2 维度 as-of 取值 — 按历史生效期获取维度快照
- 滚动窗口统计与排名计算 — 多窗口聚合和考虑并列的排名
子类必须实现的抽象方法
def get_target_table(self) -> str:
"""返回目标表名(不含 schema),如 'dws_assistant_daily_detail'"""
def get_primary_keys(self) -> List[str]:
"""返回主键字段列表,如 ['site_id', 'assistant_id', 'stat_date']"""
这两个方法被 delete_existing_data()、bulk_insert()、upsert() 内部调用,用于定位目标表和构建冲突检测条件。
1. 时间分层(TimeLayer)
TimeLayer 是一个枚举类,定义了 5 个数据筛选层级。DWS 任务根据业务需要选择合适的层级来确定查询范围。
class TimeLayer(Enum):
LAST_2_DAYS = "LAST_2_DAYS" # 近 2 天
LAST_1_MONTH = "LAST_1_MONTH" # 近 1 月(30 天)
LAST_3_MONTHS = "LAST_3_MONTHS" # 近 3 月(90 天)
LAST_6_MONTHS = "LAST_6_MONTHS" # 近 6 月(不含本月)
ALL = "ALL" # 全量(从 2000-01-01 起)
各层级的日期范围计算规则
| 层级 | 起始日期 | 结束日期 | 说明 |
|---|---|---|---|
LAST_2_DAYS |
base_date - 1天 |
base_date |
昨天 + 今天 |
LAST_1_MONTH |
base_date - 30天 |
base_date |
固定 30 天窗口 |
LAST_3_MONTHS |
base_date - 90天 |
base_date |
固定 90 天窗口 |
LAST_6_MONTHS |
6 个月前月初 | 上月末 | 不含本月,按自然月偏移 |
ALL |
2000-01-01 |
base_date |
全量回溯 |
base_date默认为date.today(),可由调用方指定。
TimeWindow(财务报表专用)
除 TimeLayer 外,还提供 TimeWindow 枚举用于财务报表场景,支持更精细的时间口径:
| 窗口类型 | 说明 | 口径 |
|---|---|---|
THIS_WEEK |
本周 | 周一起始 |
LAST_WEEK |
上周 | 上周一 ~ 上周日 |
THIS_MONTH |
本月 | 月初 ~ 今天 |
LAST_MONTH |
上月 | 上月初 ~ 上月末 |
LAST_3_MONTHS_EXCL_CURRENT |
前 3 个月(不含本月) | 3 个月前月初 ~ 上月末 |
LAST_3_MONTHS_INCL_CURRENT |
前 3 个月(含本月) | 2 个月前月初 ~ 今天 |
THIS_QUARTER |
本季度 | 季度首月 1 日 ~ 今天 |
LAST_QUARTER |
上季度 | 上季度首月 1 日 ~ 上季度末 |
LAST_6_MONTHS |
最近半年 | 不含本月,同 TimeLayer |
环比计算
get_comparison_range(time_range) 方法自动计算上一个等长区间,用于环比分析:
当前区间: [start, end] → 天数 = end - start + 1
环比区间: [start - 天数, start - 1]
2. 配置缓存(ConfigCache)
DWS 层的业务计算依赖多张配置表(绩效档位、等级定价、奖金规则等)。ConfigCache 数据类将这些配置统一加载并缓存,避免每次计算都查库。
ConfigCache 数据结构
@dataclass
class ConfigCache:
performance_tiers: List[Dict] # 绩效档位配置
level_prices: List[Dict] # 等级定价配置
bonus_rules: List[Dict] # 奖金规则配置
area_categories: Dict[str, Dict] # 区域分类映射
skill_types: Dict[int, Dict] # 技能类型映射
loaded_at: datetime # 加载时间
缓存机制
- 类级别共享:
_config_cache为类变量,同一进程内所有 DWS 任务实例共享同一份缓存 - TTL 过期:
_config_cache_ttl = 300(5 分钟),超时后下次访问自动重新加载 - 强制刷新:调用
load_config_cache(force_reload=True)可跳过 TTL 检查 - 加载入口:
load_config_cache()方法,内部依次调用 5 个私有加载方法
配置表来源
| 配置项 | 来源表 | 用途 |
|---|---|---|
performance_tiers |
billiards_dws.cfg_performance_tier |
绩效档位(小时阈值 → 抽成/休假) |
level_prices |
billiards_dws.cfg_assistant_level_price |
助教等级单价(基础课/附加课) |
bonus_rules |
billiards_dws.cfg_bonus_rules |
奖金规则(冲刺奖金/Top 排名奖金) |
area_categories |
billiards_dws.cfg_area_category |
区域分类映射(精确/模糊/兜底) |
skill_types |
billiards_dws.cfg_skill_type |
技能 → 课程类型映射(BASE/BONUS/ROOM) |
生效期过滤
所有配置项均支持 effective_from / effective_to 生效期字段。_filter_by_effective_date(items, effective_date) 方法按指定日期过滤,确保历史月份使用当时生效的配置版本。
配置应用方法
| 方法 | 功能 | 匹配逻辑 |
|---|---|---|
get_performance_tier(hours, is_new_hire, date) |
按有效小时数匹配绩效档位 | 遍历档位,找 min_hours ≤ hours < max_hours 的首个匹配 |
get_performance_tier_by_id(tier_id, date) |
按档位 ID 直接获取 | 精确匹配 tier_id |
get_level_price(level_code, date) |
获取助教等级单价 | 按 level_code 匹配 |
get_course_type(skill_id) |
技能 → 课程类型 | 查 skill_types 映射,默认 BASE |
get_area_category(area_name) |
区域名 → 分类 | 精确匹配 → 模糊匹配 → 兜底 OTHER |
calculate_sprint_bonus(hours, date) |
冲刺奖金 | 不累计,取满足阈值的最高档 |
calculate_top_rank_bonus(rank, date) |
Top 排名奖金 | 第 1/2/3 名分别对应配置金额,>3 返回 0 |
3. 幂等更新策略(delete-before-insert)
DWS 层的主要更新策略是 delete-before-insert:在写入新数据前,先按日期范围和门店 ID 删除已有数据,再批量插入。这保证了任务重跑(幂等)时不会产生重复数据。
delete_existing_data()
def delete_existing_data(
self,
context: TaskContext,
date_col: str = "stat_date",
extra_conditions: Optional[Dict[str, Any]] = None
) -> int:
执行逻辑:
- 从
get_target_table()获取目标表名,拼接billiards_dws.schema 前缀 - 构建 WHERE 条件:
site_id = {context.store_id}(门店隔离){date_col} >= {window_start}AND{date_col} <= {window_end}(日期范围)- 可选的
extra_conditions(如按助教 ID 过滤)
- 执行
DELETE FROM ... WHERE ... - 返回删除行数
典型调用模式(子类 load 方法中):
def load(self, transformed, context):
# 1. 先删除当前窗口内的旧数据
deleted = self.delete_existing_data(context, date_col="stat_date")
# 2. 再批量插入新数据
inserted = self.bulk_insert(transformed)
return {"deleted": deleted, "inserted": inserted}
4. 批量写入方法
BaseDwsTask 提供两种写入方法,子类根据场景选择:
bulk_insert() — 纯插入
def bulk_insert(
self,
rows: List[Dict[str, Any]],
columns: Optional[List[str]] = None
) -> int:
- 目标表由
get_target_table()确定,自动拼接billiards_dws.前缀 - 若
columns为None,从第一行的 keys 自动推断 - 逐行执行
INSERT INTO ... VALUES (...) - 返回插入行数
- 适用场景:配合
delete_existing_data()使用,先删后插
upsert() — 插入或更新
def upsert(
self,
rows: List[Dict[str, Any]],
columns: Optional[List[str]] = None,
update_columns: Optional[List[str]] = None
) -> Tuple[int, int]:
- 利用 PostgreSQL 的
INSERT ... ON CONFLICT (...) DO UPDATE SET ...语法 - 冲突检测键由
get_primary_keys()提供 - 若
update_columns为None,自动排除主键列和created_at后取剩余列 - 更新时自动追加
updated_at = NOW() - 返回
(inserted, updated)元组 - 适用场景:不适合先删后插的场景(如需保留
created_at等元数据)
两种策略对比
| 特性 | delete-before-insert + bulk_insert | upsert |
|---|---|---|
| 幂等性 | ✅ 先删后插,天然幂等 | ✅ ON CONFLICT 保证幂等 |
| 性能 | 批量删除 + 批量插入,适合大范围重算 | 逐行判断冲突,适合小批量增量 |
| 元数据保留 | ❌ 删除后 created_at 会重置 |
✅ 仅更新指定列 |
| 主要使用者 | 大多数 DWS 汇总任务 | 少数需要保留历史元数据的场景 |
5. DWD 数据读取
iter_dwd_rows() — 分批迭代
def iter_dwd_rows(
self, table_name, columns, start_date, end_date,
date_col="created_at", where_clause="", order_by="", batch_size=1000
) -> Iterator[List[Dict[str, Any]]]:
- 按
LIMIT/OFFSET分批读取billiards_dwd.{table_name} - 默认按
date_col ASC排序,每批 1000 行 - 自动构建日期范围 WHERE 条件,支持追加自定义
where_clause - 以生成器方式 yield 每批数据,适合处理大数据量
query_dwd() — 直接查询
def query_dwd(self, sql, params=None) -> List[Dict[str, Any]]:
- 直接执行任意 SQL,返回字典列表
- 适合复杂聚合查询或多表 JOIN 场景
6. SCD2 维度 as-of 取值
DWS 汇总计算涉及历史月份时,不能直接使用维度表的"当前版本",需要按生效期取历史快照。
get_assistant_level_asof(assistant_id, asof_date)
查询 billiards_dwd.dim_assistant 表,按 scd2_start_time ≤ asof_date 且 scd2_end_time IS NULL 或 > asof_date 条件取最近一条记录,返回助教在指定日期的等级信息(level_code、level_name)。
get_member_card_balance_asof(member_id, asof_date)
查询 billiards_dwd.dim_member_card_account 表,按 SCD2 生效期取会员在指定日期的卡余额,区分现金卡(card_type_id = 2793249295533893)和赠送卡(台费卡/活动抵用券/酒水卡),返回 cash_balance、gift_balance、total_balance。
7. 辅助计算方法
滚动窗口统计
calculate_rolling_stats(base_date, entity_id, entity_type, stat_sql, windows) 按预定义的窗口天数列表(默认 [7, 10, 15, 30, 60, 90])执行统计 SQL,返回各窗口的聚合结果,键名格式为 {指标}_{天数}d。
排名计算(考虑并列)
calculate_rank_with_ties(values) 接收 (entity_id, score) 列表,按分数降序排名。并列时共享同一排名,下一名跳过(如 2 个第 1 名,下一个是第 3 名)。返回 (entity_id, rank, dense_rank) 元组列表。
其他工具方法
| 方法 | 功能 |
|---|---|
is_new_hire_in_month(hire_date, stat_month) |
判断是否为当月新入职(月 1 日后入职) |
is_guest(member_id) |
判断是否为散客(member_id 为 0 或 None) |
safe_decimal(value, default) |
安全转换为 Decimal,异常返回默认值 |
safe_int(value, default) |
安全转换为 int,异常返回默认值 |
seconds_to_hours(seconds) |
秒 → 小时(Decimal 精度) |
hours_to_seconds(hours) |
小时 → 秒 |
get_month_first_day(dt) |
获取月第一天 |
get_month_last_day(dt) |
获取月最后一天 |
get_comparison_range(time_range) |
计算环比区间 |
助教业绩域
助教业绩域包含 5 个任务,围绕助教的日度服务明细、月度汇总与排名、客户关系、工资计算、收支分析展开。数据流向为:
dwd_assistant_service_log ──┬──► DWS_ASSISTANT_DAILY(日度明细)
dwd_assistant_trash_event ──┘ │
▼
DWS_ASSISTANT_MONTHLY(月度汇总+档位+排名)
│
▼
DWS_ASSISTANT_SALARY(工资计算)
│
dwd_assistant_service_log ────► DWS_ASSISTANT_FINANCE(收支分析)◄── dws_assistant_salary_calc
dwd_assistant_service_log ────► DWS_ASSISTANT_CUSTOMER(客户关系统计)
DWS_ASSISTANT_DAILY — 助教日度业绩明细
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_ASSISTANT_DAILY |
| Python 类 | AssistantDailyTask(tasks/dws/assistant_daily_task.py) |
| 目标表 | billiards_dws.dws_assistant_daily_detail |
| 主键 | site_id, assistant_id, stat_date |
| 粒度 | 日期 + 助教 |
| 更新策略 | delete-before-insert(按日期窗口) |
| 更新频率 | 每小时增量更新 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dwd_assistant_service_log |
billiards_dwd |
助教服务流水(主数据源) |
dwd_assistant_trash_event |
billiards_dwd |
废除记录(排除无效业绩) |
dim_assistant |
billiards_dwd |
助教维度(SCD2,获取当日等级) |
cfg_skill_type |
billiards_dws |
技能 → 课程类型映射 |
聚合维度与输出字段
按 (assistant_id, service_date) 聚合,输出以下字段:
| 字段分组 | 字段 | 说明 |
|---|---|---|
| 标识 | site_id, tenant_id, assistant_id, assistant_nickname, stat_date |
门店、助教、日期 |
| 等级 | assistant_level_code, assistant_level_name |
SCD2 as-of 取值,取统计日当日生效的等级 |
| 服务次数 | total_service_count, base_service_count, bonus_service_count, room_service_count |
总/基础课/附加课/包厢课 |
| 计费秒数 | total_seconds, base_seconds, bonus_seconds, room_seconds |
原始秒数 |
| 计费小时 | total_hours, base_hours, bonus_hours, room_hours |
秒数 ÷ 3600,Decimal 精度 |
| 计费金额 | total_ledger_amount, base_ledger_amount, bonus_ledger_amount, room_ledger_amount |
台账金额 |
| 去重统计 | unique_customers, unique_tables |
去重客户数(排除散客)、去重台桌数 |
| 废除统计 | trashed_seconds, trashed_count |
被废除的秒数和次数 |
核心业务逻辑
- 课程类型分类:通过
skill_id查询cfg_skill_type映射,分为BASE(基础课)、BONUS(附加课)、ROOM(包厢课),未匹配默认BASE - 废除记录排除:以
assistant_service_id为键构建废除索引,被废除的服务记录不计入有效业绩(服务次数、时长、金额),但单独统计trashed_seconds和trashed_count - 助教等级 SCD2 取值:调用
get_assistant_level_asof(assistant_id, service_date)获取统计日当日生效的等级版本,而非当前最新版本 - 散客过滤:
unique_customers统计时排除member_id为 0 或 None 的散客 - 客户/台桌去重:无论服务记录是否被废除,客户和台桌均参与去重统计
DWS_ASSISTANT_MONTHLY — 助教月度业绩汇总
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_ASSISTANT_MONTHLY |
| Python 类 | AssistantMonthlyTask(tasks/dws/assistant_monthly_task.py) |
| 目标表 | billiards_dws.dws_assistant_monthly_summary |
| 主键 | site_id, assistant_id, stat_month |
| 粒度 | 月份 + 助教 |
| 更新策略 | delete-before-insert(按月份) |
| 更新频率 | 每日更新当月数据 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dws_assistant_daily_detail |
billiards_dws |
日度明细(按月聚合) |
dwd_assistant_service_log |
billiards_dwd |
月度去重客户/台桌(直接从 DWD 去重,避免日度求和失真) |
dim_assistant |
billiards_dwd |
助教维度(入职日期、当前等级) |
cfg_performance_tier |
billiards_dws |
绩效档位配置 |
聚合维度与输出字段
按 (assistant_id, stat_month) 聚合,输出以下字段:
| 字段分组 | 字段 | 说明 |
|---|---|---|
| 标识 | site_id, tenant_id, assistant_id, assistant_nickname, stat_month |
门店、助教、月份 |
| 等级 | assistant_level_code, assistant_level_name |
月末 SCD2 as-of 取值 |
| 入职 | hire_date, is_new_hire |
入职日期、是否当月新入职 |
| 工作天数 | work_days |
COUNT(DISTINCT stat_date) |
| 服务次数 | total_service_count, base_service_count, bonus_service_count, room_service_count |
月度累计 |
| 时长 | total_hours, base_hours, bonus_hours, room_hours, effective_hours, trashed_hours |
月度累计小时数 |
| 金额 | total_ledger_amount, base_ledger_amount, bonus_ledger_amount, room_ledger_amount |
月度累计金额 |
| 去重统计 | unique_customers, unique_tables |
月度去重(从 DWD 直接去重) |
| 平均时长 | avg_service_seconds |
总秒数 ÷ 总服务次数 |
| 档位 | tier_id, tier_code, tier_name |
匹配的绩效档位 |
| 排名 | rank_by_hours, rank_with_ties |
按有效业绩小时数排名(考虑并列) |
核心业务逻辑
1. 有效业绩计算
effective_hours = total_hours - trashed_hours
其中 trashed_hours 由日度明细的 trashed_seconds 累加后转换为小时。
2. 新入职判断
调用 is_new_hire_in_month(hire_date, stat_month):入职日期在当月 1 日 0 点之后即视为新入职。
3. 档位匹配
- 正常助教:以
effective_hours匹配cfg_performance_tier,找min_hours ≤ hours < max_hours的首个档位 - 新入职助教:先按日均折算 30 天(
effective_hours / work_days × 30),再匹配档位 - 新人封顶规则:当同时满足以下条件时,档位不超过配置的最大等级(默认 2 档):
- 统计月份 ≥ 封顶规则生效月(配置项
dws.monthly.new_hire_cap_effective_from,默认2026-03-01) - 入职日期晚于封顶日(配置项
dws.monthly.new_hire_cap_day,默认当月 25 日)
- 统计月份 ≥ 封顶规则生效月(配置项
4. 排名逻辑
按 effective_hours 降序排名,使用 calculate_rank_with_ties() 方法:
- 并列时共享同一排名,下一名跳过(如 2 个第 1 名,下一个是第 3 名)
rank_by_hours和rank_with_ties均使用此排名结果- 排名结果用于后续
DWS_ASSISTANT_SALARY的 Top3 奖金计算
5. 月度去重客户/台桌
unique_customers 和 unique_tables 优先使用从 dwd_assistant_service_log 直接按月去重的结果(_extract_monthly_uniques),而非日度明细的简单求和,避免跨日重复计数导致失真。
6. 月份过滤调度
- 默认仅处理当月;月初前 N 天(配置项
dws.monthly.prev_month_grace_days,默认 5)可同时处理上月 - 配置
dws.monthly.allow_history = True可处理全部历史月份 - 配置
dws.monthly.history_months可指定回溯月数
DWS_ASSISTANT_CUSTOMER — 助教-客户关系统计
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_ASSISTANT_CUSTOMER |
| Python 类 | AssistantCustomerTask(tasks/dws/assistant_customer_task.py) |
| 目标表 | billiards_dws.dws_assistant_customer_stats |
| 主键 | site_id, assistant_id, member_id, stat_date |
| 粒度 | 统计日期 + 助教 + 会员 |
| 更新策略 | delete-before-insert(按统计日期) |
| 更新频率 | 每日更新 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dwd_assistant_service_log |
billiards_dwd |
助教服务流水(全量,用于累计和滚动窗口统计) |
dim_member |
billiards_dwd |
会员维度(昵称、手机号) |
dim_assistant |
billiards_dwd |
助教维度(当前有效记录) |
聚合维度与输出字段
按 (assistant_id, member_id) 聚合,以 stat_date(窗口结束日期)为统计基准日,输出以下字段:
| 字段分组 | 字段 | 说明 |
|---|---|---|
| 标识 | site_id, tenant_id, assistant_id, assistant_nickname, member_id, member_nickname, member_mobile, stat_date |
助教、会员、统计日期 |
| 全量累计 | first_service_date, last_service_date, total_service_count, total_service_hours, total_service_amount |
首次/最近服务日期、累计次数/时长/金额 |
| 滚动窗口 | service_count_{N}d, service_hours_{N}d, service_amount_{N}d(N = 7/10/15/30/60/90) |
各窗口的服务次数、时长、金额 |
| 活跃度 | days_since_last, is_active_7d, is_active_30d |
距最近服务天数、近 7/30 天是否活跃 |
核心业务逻辑
- 散客排除:
member_id为 0 或 None 的散客不进入此表统计 - 滚动窗口:在单条 SQL 中通过
CASE WHEN service_date >= stat_date - INTERVAL '{N-1} days'实现 6 个窗口(7/10/15/30/60/90 天)的并行计算 - 活跃度判定:
is_active_7d= 近 7 天服务次数 > 0;is_active_30d= 近 30 天服务次数 > 0 - HAVING 过滤:仅保留最近 90 天内有服务记录的助教-客户对,避免输出过多历史冷数据
- 手机号脱敏:
member_mobile输出时中间 4 位替换为****(如138****1234)
DWS_ASSISTANT_SALARY — 助教工资计算
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_ASSISTANT_SALARY |
| Python 类 | AssistantSalaryTask(tasks/dws/assistant_salary_task.py) |
| 目标表 | billiards_dws.dws_assistant_salary_calc |
| 主键 | site_id, assistant_id, salary_month |
| 粒度 | 月份 + 助教 |
| 更新策略 | delete-before-insert(按月份) |
| 更新频率 | 月初计算上月工资 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dws_assistant_monthly_summary |
billiards_dws |
月度业绩汇总(有效小时数、档位、排名) |
dws_assistant_recharge_commission |
billiards_dws |
充值提成(Excel 导入) |
cfg_performance_tier |
billiards_dws |
绩效档位(抽成比例、假期天数) |
cfg_assistant_level_price |
billiards_dws |
等级定价(客户支付价格) |
cfg_bonus_rules |
billiards_dws |
奖金规则(冲刺奖金、Top 排名奖金) |
输出字段
| 字段分组 | 字段 | 说明 |
|---|---|---|
| 标识 | site_id, tenant_id, assistant_id, assistant_nickname, salary_month |
门店、助教、工资月份 |
| 等级与档位 | assistant_level_code, assistant_level_name, hire_date, is_new_hire, tier_id, tier_code, tier_name, rank_with_ties |
等级、档位、排名 |
| 时长 | effective_hours, base_hours, bonus_hours, room_hours |
有效小时数(来自月度汇总) |
| 定价信息 | base_course_price, bonus_course_price, base_deduction, bonus_deduction_ratio |
客户支付价格、球房抽成 |
| 收入明细 | base_income, bonus_income, room_income, total_course_income |
各课程类型收入 |
| 奖金明细 | sprint_bonus, top_rank_bonus, recharge_commission, other_bonus, total_bonus |
各类奖金 |
| 应发工资 | gross_salary |
课时收入 + 奖金合计 |
| 假期 | vacation_days, vacation_unlimited |
档位对应的假期天数 |
| 备注 | calc_notes |
计算备注(新入职、档位、奖金等) |
工资计算公式
应发工资 = 课时收入 + 奖金合计
课时收入 = 基础课收入 + 附加课收入 + 包厢课收入
奖金合计 = 冲刺奖金 + Top3排名奖金 + 充值提成 + 其他奖金
基础课收入
基础课收入 = base_hours × (base_course_price - base_deduction)
base_course_price:客户支付价格,按助教等级区分(初级 98 / 中级 108 / 高级 118 / 星级 138 元/小时)base_deduction:专业课抽成(元/小时),由档位配置决定,球房从每小时扣除- 示例:中级助教 170 小时,3 档(抽成 13 元)→ 170 × (108 - 13) = 16,150 元
附加课收入
附加课收入 = bonus_hours × bonus_course_price × (1 - bonus_deduction_ratio)
bonus_course_price:附加课客户支付价格(固定 190 元/小时)bonus_deduction_ratio:打赏课抽成比例,由档位配置决定- 示例:15 小时,3 档(抽成比例 0.35)→ 15 × 190 × (1 - 0.35) = 1,852.5 元
包厢课收入
包厢课收入 = room_hours × (room_course_price - base_deduction)
room_course_price:包厢课统一价格(配置项dws.salary.room_course_price,默认 138 元/小时)
冲刺奖金
调用 calculate_sprint_bonus(effective_hours, salary_month),按 cfg_bonus_rules 配置表匹配:
- 不累计,取满足有效小时数阈值的最高档奖金
Top3 排名奖金
调用 calculate_top_rank_bonus(rank, salary_month),仅排名前 3 的助教获得:
- 第 1 名:1,000 元
- 第 2 名:600 元
- 第 3 名:400 元
- 并列排名均可获得对应奖金
充值提成
从 dws_assistant_recharge_commission 表读取,按 assistant_id 汇总当月提成金额。
SCD2 口径
等级定价使用 get_level_price(level_code, salary_month) 按月份取历史生效值,确保历史月份使用当时的定价版本。
运行调度
- 默认仅在月初前 N 天运行(配置项
dws.salary.run_days,默认 5),超过则跳过 - 配置
dws.salary.allow_out_of_cycle = True可强制运行 - 工资月份判定:月初(day ≤ 5)计算上月工资,否则计算当月(调整场景)
DWS_ASSISTANT_FINANCE — 助教收支分析
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_ASSISTANT_FINANCE |
| Python 类 | AssistantFinanceTask(tasks/dws/assistant_finance_task.py) |
| 目标表 | billiards_dws.dws_assistant_finance_analysis |
| 主键 | site_id, stat_date, assistant_id |
| 粒度 | 日期 + 助教 |
| 更新策略 | delete-before-insert(按日期) |
| 更新频率 | 每日更新 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dwd_assistant_service_log |
billiards_dwd |
助教服务流水(日度收入) |
cfg_skill_type |
billiards_dws |
技能 → 课程类型映射(收入分类) |
dws_assistant_salary_calc |
billiards_dws |
工资计算结果(月度成本) |
dws_assistant_daily_detail |
billiards_dws |
日度明细(计算月度工作天数) |
输出字段
| 字段分组 | 字段 | 说明 |
|---|---|---|
| 标识 | site_id, tenant_id, stat_date, assistant_id, assistant_nickname |
门店、日期、助教 |
| 收入 | revenue_total, revenue_base, revenue_bonus, revenue_room |
日度总收入及按课程类型拆分 |
| 成本 | cost_daily |
日均成本 |
| 利润 | gross_profit, gross_margin |
毛利润、毛利率 |
| 服务量 | service_count, service_hours, room_service_count, room_service_hours, unique_customers |
服务次数、时长、包厢服务、去重客户数 |
核心业务逻辑
1. 日度收入计算
从 dwd_assistant_service_log 按 (DATE(start_use_time), site_assistant_id) 聚合:
revenue_total:SUM(ledger_amount)revenue_base/revenue_bonus/revenue_room:按cfg_skill_type.course_type_code分类汇总service_hours:SUM(income_seconds) / 3600.0unique_customers:COUNT(DISTINCT tenant_member_id)(排除 ≤ 0)
2. 日均成本计算
cost_daily = gross_salary / work_days
gross_salary:从dws_assistant_salary_calc取对应月份的应发工资work_days:从dws_assistant_daily_detail按月统计COUNT(DISTINCT stat_date),默认 20 天
3. 毛利润与毛利率
gross_profit = revenue_total - cost_daily
gross_margin = gross_profit / revenue_total (revenue_total > 0 时)
= 0 (revenue_total = 0 时)
4. 依赖关系
此任务依赖 DWS_ASSISTANT_SALARY 和 DWS_ASSISTANT_DAILY 的输出数据,应在这两个任务完成后运行。
会员分析域
会员分析域包含 2 个任务,围绕会员的消费行为汇总和到店明细展开。数据流向为:
dwd_settlement_head ──────────┬──► DWS_MEMBER_CONSUMPTION(消费汇总+分层)
dim_member ───────────────────┤
dim_member_card_account ──────┘
dwd_settlement_head ──────────┬──► DWS_MEMBER_VISIT(到店明细)
dwd_assistant_service_log ────┤
dwd_table_fee_log ────────────┤
dim_member ───────────────────┤
dim_table ────────────────────┘
DWS_MEMBER_CONSUMPTION — 会员消费汇总
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_MEMBER_CONSUMPTION |
| Python 类 | MemberConsumptionTask(tasks/dws/member_consumption_task.py) |
| 目标表 | billiards_dws.dws_member_consumption_summary |
| 主键 | site_id, member_id, stat_date |
| 粒度 | 统计日期 + 会员 |
| 更新策略 | delete-before-insert(按统计日期) |
| 更新频率 | 每日更新 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dwd_settlement_head |
billiards_dwd |
结账单头表(消费金额、台费、商品、助教费用) |
dim_member |
billiards_dwd |
会员维度(SCD2 当前版本,昵称、手机号、卡等级、注册日期、累计充值) |
dim_member_card_account |
billiards_dwd |
会员卡账户(SCD2 当前版本,卡余额) |
聚合维度与输出字段
按 (member_id) 聚合全量消费记录,以 stat_date(窗口结束日期)为统计基准日,输出以下字段:
| 字段分组 | 字段 | 说明 |
|---|---|---|
| 标识 | site_id, tenant_id, member_id, stat_date |
门店、会员、统计日期 |
| 会员信息 | member_nickname, member_mobile, card_grade_name, register_date |
昵称、脱敏手机号、卡等级、注册日期 |
| 全量累计 | first_consume_date, last_consume_date, total_visit_count, total_consume_amount, total_recharge_amount, total_table_fee, total_goods_amount, total_assistant_amount |
首次/最近消费日期、累计到店次数、累计消费金额、累计充值金额、累计台费、累计商品金额、累计助教费用 |
| 滚动窗口(次数) | visit_count_7d, visit_count_10d, visit_count_15d, visit_count_30d, visit_count_60d, visit_count_90d |
各窗口到店次数 |
| 滚动窗口(金额) | consume_amount_7d, consume_amount_10d, consume_amount_15d, consume_amount_30d, consume_amount_60d, consume_amount_90d |
各窗口消费金额 |
| 卡余额 | cash_card_balance, gift_card_balance, total_card_balance |
储值卡(现金卡)余额、赠送卡余额、总余额 |
| 活跃度 | days_since_last, is_active_7d, is_active_30d, is_active_90d |
距最近消费天数、近 7/30/90 天是否活跃 |
| 客户分层 | customer_tier |
分层标签(高价值/中等/低活跃/流失) |
核心业务逻辑
1. 散客排除
member_id 为 0 或 None 的散客不进入此表统计。SQL 层面和 transform 阶段均做过滤。
2. 消费统计来源
从 dwd_settlement_head 按 member_id 聚合,消费金额拆分为:
consume_money:总消费金额table_charge_money:台费goods_money:商品金额assistant_pd_money + assistant_cx_money:助教费用(专业课 + 陪练课合计)
3. 滚动窗口
在单条 SQL 中通过 CASE WHEN consume_date >= stat_date - INTERVAL '{N-1} days' 实现 6 个窗口(7/10/15/30/60/90 天)的并行计算,同时统计到店次数和消费金额。
4. 卡余额区分
从 dim_member_card_account 按 card_type_id 区分卡类型:
| 卡类型 | card_type_id | 归入字段 |
|---|---|---|
| 储值卡(现金卡) | 2793249295533893 |
cash_card_balance |
| 台费卡 | 2791990152417157 |
gift_card_balance |
| 活动抵用券 | 2793266846533445 |
gift_card_balance |
| 酒水卡 | 2794699703437125 |
gift_card_balance |
total_card_balance = cash_card_balance + gift_card_balance
仅取 SCD2 当前版本(scd2_is_current = 1)且未删除(is_delete = 0)的记录。同一会员可能有多张卡,余额按类型累加。
5. 活跃度判定
days_since_last:stat_date - last_consume_date的天数差,无消费记录时为NULLis_active_7d:近 7 天到店次数 > 0is_active_30d:近 30 天到店次数 > 0is_active_90d:近 90 天到店次数 > 0
6. 客户分层规则
按以下优先级判定 customer_tier:
| 分层 | 条件 | 说明 |
|---|---|---|
高价值 |
90 天内消费 ≥ 3 次 且 消费金额 ≥ 1000 元 | 高频高额客户 |
中等 |
30 天内有消费 | 近期活跃客户 |
低活跃 |
90 天内有消费但 30 天内无消费 | 有消费但频率下降 |
流失 |
90 天内无消费 | 长期未到店 |
判定顺序为从上到下,命中即返回。
7. 手机号脱敏
member_mobile 输出时中间 4 位替换为 ****(如 138****1234),长度不足 7 位时原样输出。
DWS_MEMBER_VISIT — 会员到店明细
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_MEMBER_VISIT |
| Python 类 | MemberVisitTask(tasks/dws/member_visit_task.py) |
| 目标表 | billiards_dws.dws_member_visit_detail |
| 主键 | site_id, member_id, order_settle_id |
| 粒度 | 会员 + 结账单(每次到店一条记录) |
| 更新策略 | delete-before-insert(按 visit_date 日期窗口) |
| 更新频率 | 每日增量更新 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dwd_settlement_head |
billiards_dwd |
结账单头表(消费金额、支付方式) |
dwd_assistant_service_log |
billiards_dwd |
助教服务流水(服务时长、金额) |
dwd_table_fee_log |
billiards_dwd |
台费流水(真实台桌使用秒数) |
dim_member |
billiards_dwd |
会员维度(SCD2 当前版本,昵称、手机号、生日) |
dim_table |
billiards_dwd |
台桌维度(SCD2 当前版本,台桌名称、区域名称) |
cfg_area_category |
billiards_dws |
区域分类映射(通过 ConfigCache 加载) |
聚合维度与输出字段
以每笔结账单(order_settle_id)为粒度,输出以下字段:
| 字段分组 | 字段 | 说明 |
|---|---|---|
| 标识 | site_id, tenant_id, member_id, order_settle_id, visit_date, visit_time |
门店、会员、结账单号、到店日期、到店时间 |
| 会员信息 | member_nickname, member_mobile, member_birthday |
昵称、脱敏手机号、生日 |
| 台桌信息 | table_id, table_name, area_name, area_category |
台桌 ID、台桌名称、区域名称、区域分类 |
| 消费金额 | table_fee, goods_amount, assistant_amount, total_consume, total_discount, actual_pay |
台费、商品金额、助教费用、总消费、总优惠、实付金额 |
| 支付方式 | cash_pay, cash_card_pay, gift_card_pay, groupbuy_pay |
现金/在线支付、储值卡支付、赠送卡支付、团购券支付 |
| 时长 | table_duration_min, assistant_duration_min |
台桌使用时长(分钟)、助教服务时长(分钟) |
| 助教服务 | assistant_services |
JSON 格式的助教服务明细 |
核心业务逻辑
1. 散客排除
SQL 层面通过 member_id IS NOT NULL AND member_id != 0 过滤,transform 阶段通过 is_guest() 二次过滤。
2. 消费金额拆分
从 dwd_settlement_head 直接读取各金额字段:
table_fee:table_charge_money(台费)goods_amount:goods_money(商品金额)assistant_amount:assistant_pd_money + assistant_cx_money(专业课 + 陪练课助教费用合计)total_consume:consume_money(总消费金额)actual_pay:pay_amount(实付金额)
3. 总优惠计算
total_discount = adjust_amount + member_discount_amount + rounding_amount
adjust_amount:手动调整金额member_discount_amount:会员折扣金额rounding_amount:抹零金额
4. 支付方式拆分
| 字段 | 来源字段 | 说明 |
|---|---|---|
cash_pay |
pay_amount |
现金/在线支付 |
cash_card_pay |
balance_amount |
储值卡(现金卡)支付 |
gift_card_pay |
gift_card_amount |
赠送卡支付 |
groupbuy_pay |
coupon_amount |
团购券支付 |
5. 台桌使用时长
从 dwd_table_fee_log 按 order_settle_id 聚合 SUM(real_table_use_seconds) 获取真实台费秒数,转换为分钟(整除 60)。仅取未删除记录(is_delete = 0)。
6. 助教服务时长
从 dwd_assistant_service_log 按 order_settle_id 关联,汇总所有助教的 income_seconds 后转换为分钟(整除 60)。仅取未删除记录(is_delete = 0)。
7. 助教服务明细(JSON)
每笔结账单关联的助教服务以 JSON 数组格式存储在 assistant_services 字段中,每个元素包含:
[
{
"assistant_id": 12345,
"nickname": "张教练",
"duration_min": 60,
"amount": 108.00
}
]
| JSON 字段 | 来源 | 说明 |
|---|---|---|
assistant_id |
site_assistant_id |
助教 ID |
nickname |
nickname |
助教昵称 |
duration_min |
income_seconds // 60 |
服务时长(分钟) |
amount |
ledger_amount |
台账金额 |
无助教服务时 assistant_services 为 NULL。
8. 区域分类
通过 ConfigCache 加载 cfg_area_category 配置,调用 get_area_category(area_name) 将台桌区域名称映射为分类标签。匹配逻辑:精确匹配 → 模糊匹配 → 兜底 OTHER。
9. 手机号脱敏
与 DWS_MEMBER_CONSUMPTION 相同,中间 4 位替换为 ****。
财务统计域
财务统计域包含 4 个任务,围绕门店的日度财务汇总、充值统计、收入结构分析和优惠明细展开。数据流向为:
dwd_settlement_head ──────────┬──► DWS_FINANCE_DAILY(财务日报)
dwd_groupbuy_redemption ──────┤
dwd_recharge_order ───────────┤
dwd_member_balance_change ────┤
dws_finance_expense_summary ──┤
dws_platform_settlement ──────┘
dwd_recharge_order ───────────┬──► DWS_FINANCE_RECHARGE(充值统计)
dim_member_card_account ──────┘
dwd_settlement_head ──────────┬──► DWS_FINANCE_INCOME_STRUCTURE(收入结构)
dwd_table_fee_log ────────────┤
dwd_assistant_service_log ────┤
dim_table ────────────────────┤
cfg_area_category ────────────┘
dwd_settlement_head ──────────┬──► DWS_FINANCE_DISCOUNT_DETAIL(折扣明细)
dwd_groupbuy_redemption ──────┤
dwd_member_balance_change ────┘
DWS_FINANCE_DAILY — 财务日报
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_FINANCE_DAILY |
| Python 类 | FinanceDailyTask(tasks/dws/finance_daily_task.py) |
| 目标表 | billiards_dws.dws_finance_daily_summary |
| 主键 | site_id, stat_date |
| 粒度 | 日期 |
| 更新策略 | delete-before-insert(按日期窗口) |
| 更新频率 | 每小时更新当日数据 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dwd_settlement_head |
billiards_dwd |
结账单头表(发生额、支付、优惠) |
dwd_groupbuy_redemption |
billiards_dwd |
团购核销(团购实付金额) |
dwd_recharge_order |
billiards_dwd |
充值订单(首充/续充、现金/赠送) |
dwd_member_balance_change |
billiards_dwd |
余额变动(赠送卡消费) |
dws_finance_expense_summary |
billiards_dws |
支出汇总(Excel 导入,按月分摊到日) |
dws_platform_settlement |
billiards_dws |
平台回款/服务费(Excel 导入) |
输出字段
| 字段分组 | 字段 | 说明 |
|---|---|---|
| 标识 | site_id, tenant_id, stat_date |
门店、统计日期 |
| 发生额 | gross_amount, table_fee_amount, goods_amount, assistant_pd_amount, assistant_cx_amount |
正价总额及按类型拆分(台费/商品/专业课/陪练课) |
| 优惠 | discount_total, discount_groupbuy, discount_vip, discount_gift_card, discount_manual, discount_rounding, discount_other |
优惠合计及按类型拆分 |
| 确认收入 | confirmed_income |
发生额 - 优惠合计 |
| 现金流入 | cash_inflow_total, cash_pay_amount, groupbuy_pay_amount, platform_settlement_amount, recharge_cash_inflow |
现金流入合计及来源拆分 |
| 现金流出 | cash_outflow_total, platform_fee_amount |
现金流出合计(支出 + 平台费用) |
| 现金净变动 | cash_balance_change |
流入 - 流出 |
| 卡消费 | card_consume_total, cash_card_consume, gift_card_consume |
储值卡消费 + 赠送卡消费 |
| 充值统计 | recharge_count, recharge_total, recharge_cash, recharge_gift, first_recharge_count, first_recharge_amount, renewal_count, renewal_amount |
充值笔数/金额、首充/续充拆分 |
| 订单统计 | order_count, member_order_count, guest_order_count, avg_order_amount |
总订单数、会员/散客订单数、客单价 |
核心业务逻辑
1. 发生额(正价)
gross_amount = table_charge_money + goods_money + assistant_pd_money + assistant_cx_money
从 dwd_settlement_head 按 DATE(pay_time) 聚合,分别统计台费、商品、专业课(PD)、陪练课(CX)四类收入。
2. 团购优惠计算
团购实付金额 = pl_coupon_sale_amount > 0 ? pl_coupon_sale_amount : groupbuy_redemption.ledger_unit_price
团购优惠 = coupon_amount - 团购实付金额
coupon_amount:团购抵消台费金额(结账单字段)- 团购实付金额优先取
pl_coupon_sale_amount(平台券销售金额),否则取dwd_groupbuy_redemption.ledger_unit_price - 团购优惠为负时置 0
3. 大客户优惠拆分
手动调整金额(adjust_amount)中,通过配置项 dws.discount.big_customer_member_ids 和 dws.discount.big_customer_order_ids 标记的订单归入大客户优惠,其余归入其他优惠:
discount_other = adjust_amount - big_customer_amount (负值置 0)
4. 赠送卡消费
从 dwd_member_balance_change 提取赠送卡消费(from_type = 1 且 change_amount < 0),按卡类型过滤:
- 台费卡(
card_type_id = 2791990152417157) - 酒水卡(
card_type_id = 2794699703437125) - 活动抵用券(
card_type_id = 2793266846533445)
取 ABS(change_amount) 汇总为当日赠送卡消费总额。
5. 优惠合计与确认收入
discount_total = discount_groupbuy + discount_vip + discount_gift_card + discount_manual + discount_rounding
confirmed_income = gross_amount - discount_total
6. 现金流计算
cash_inflow_total = cash_pay_amount + platform_inflow + recharge_cash_inflow
cash_outflow_total = expense_amount + platform_fee_amount
cash_balance_change = cash_inflow_total - cash_outflow_total
platform_inflow:优先取platform_settlement_amount(平台回款),为 0 时取groupbuy_pay_amountplatform_fee_amount:commission_amount + service_fee(平台佣金 + 服务费)expense_amount:月度支出按日均分摊(月总额 ÷ 当月天数)
7. 支出分摊逻辑
支出数据来自 dws_finance_expense_summary(Excel 导入),以月为粒度。任务按当月天数均分到每日:
daily_expense = expense_amount / days_in_month
8. 卡消费统计
cash_card_consume = recharge_card_amount + balance_amount (储值卡支付)
gift_card_consume = 赠送卡消费总额 (来自余额变动)
card_consume_total = cash_card_consume + gift_card_consume
DWS_FINANCE_RECHARGE — 充值统计
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_FINANCE_RECHARGE |
| Python 类 | FinanceRechargeTask(tasks/dws/finance_recharge_task.py) |
| 目标表 | billiards_dws.dws_finance_recharge_summary |
| 主键 | site_id, stat_date |
| 粒度 | 日期 |
| 更新策略 | delete-before-insert(按日期窗口) |
| 更新频率 | 每日更新 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dwd_recharge_order |
billiards_dwd |
充值订单(首充/续充、现金/赠送) |
dim_member_card_account |
billiards_dwd |
会员卡账户(SCD2 当前版本,卡余额快照) |
输出字段
| 字段分组 | 字段 | 说明 |
|---|---|---|
| 标识 | site_id, tenant_id, stat_date |
门店、统计日期 |
| 充值汇总 | recharge_count, recharge_total, recharge_cash, recharge_gift |
充值笔数、总额(现金+赠送)、现金部分、赠送部分 |
| 首充 | first_recharge_count, first_recharge_cash, first_recharge_gift, first_recharge_total |
首充笔数、现金、赠送、总额 |
| 续充 | renewal_count, renewal_cash, renewal_gift, renewal_total |
续充笔数、现金、赠送、总额 |
| 会员统计 | recharge_member_count, new_member_count |
当日充值去重会员数、首充新会员数 |
| 卡余额快照 | total_card_balance, cash_card_balance, gift_card_balance |
全店卡余额总计、储值卡余额、赠送卡余额 |
核心业务逻辑
1. 首充/续充区分
通过 dwd_recharge_order.is_first 字段区分:
is_first = 1:首充(会员首次充值)is_first = 0或NULL:续充
每笔充值金额拆分为:
充值总额 = pay_money(现金部分)+ gift_money(赠送部分)
2. 会员去重统计
recharge_member_count:COUNT(DISTINCT member_id),当日充值的去重会员数new_member_count:COUNT(DISTINCT CASE WHEN is_first = 1 THEN member_id END),当日首充的去重新会员数
3. 卡余额快照
从 dim_member_card_account 取 SCD2 当前版本(scd2_is_current = 1)且未删除(is_delete = 0)的记录,按 card_type_id 分类汇总:
| 卡类型 | card_type_id | 归入字段 |
|---|---|---|
| 储值卡(现金卡) | 2793249295533893 |
cash_card_balance |
| 台费卡 | 2791990152417157 |
gift_card_balance |
| 酒水卡 | 2794699703437125 |
gift_card_balance |
| 活动抵用券 | 2793266846533445 |
gift_card_balance |
total_card_balance = cash_card_balance + gift_card_balance
注意:卡余额为窗口结束日的全量快照,而非按日变化值。窗口内所有日期共享同一份余额数据。
DWS_FINANCE_INCOME_STRUCTURE — 收入结构分析
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_FINANCE_INCOME_STRUCTURE |
| Python 类 | FinanceIncomeStructureTask(tasks/dws/finance_income_task.py) |
| 目标表 | billiards_dws.dws_finance_income_structure |
| 主键 | site_id, stat_date, structure_type, category_code |
| 粒度 | 日期 + 结构类型 + 分类代码 |
| 更新策略 | delete-before-insert(按日期窗口) |
| 更新频率 | 每日更新 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dwd_settlement_head |
billiards_dwd |
结账单头表(按收入类型汇总) |
dwd_table_fee_log |
billiards_dwd |
台费流水(按区域汇总台费收入) |
dwd_assistant_service_log |
billiards_dwd |
助教服务流水(按区域汇总助教收入) |
dim_table |
billiards_dwd |
台桌维度(SCD2,获取区域名称) |
cfg_area_category |
billiards_dws |
区域分类映射(通过 ConfigCache 加载) |
输出字段
| 字段 | 说明 |
|---|---|
site_id, tenant_id, stat_date |
门店、统计日期 |
structure_type |
结构类型:INCOME_TYPE(按收入类型)或 AREA(按区域) |
category_code |
分类代码(见下方分类定义) |
category_name |
分类名称(中文) |
income_amount |
收入金额 |
income_ratio |
收入占比(保留 4 位小数) |
order_count |
关联订单数 |
duration_minutes |
使用时长(分钟),仅 AREA 类型有值 |
两种分析维度
维度 1:按收入类型(structure_type = 'INCOME_TYPE')
从 dwd_settlement_head 按 pay_time::DATE 聚合,仅统计已结账订单(settle_status = 1),每日展开为 4 条记录:
| category_code | category_name | 来源字段 | 说明 |
|---|---|---|---|
TABLE_FEE |
台费收入 | table_charge_money |
台桌使用费 |
GOODS |
商品收入 | goods_money |
商品销售 |
ASSISTANT_BASE |
助教基础课 | assistant_pd_money |
专业课(PD=陪打) |
ASSISTANT_BONUS |
助教附加课 | assistant_cx_money |
附加课(CX=超休/促销) |
占比计算:income_ratio = 该类型金额 / 当日四类收入总和
维度 2:按区域(structure_type = 'AREA')
通过 CTE 合并台费流水和助教服务流水,关联 dim_table 获取 site_table_area_name,再通过 get_area_category(area_name) 映射到分类代码。
区域映射逻辑(与 DWS_MEMBER_VISIT 相同):精确匹配 → 模糊匹配 → 兜底 OTHER。
相同 category_code 的不同区域名称会被合并聚合。每条记录额外输出 duration_minutes(台费秒数 + 助教服务秒数,转换为分钟)。
占比计算:income_ratio = 该区域金额 / 当日所有区域收入总和
核心业务逻辑
1. 自定义 load 方法
此任务未使用 BaseDwsTask.delete_existing_data() + bulk_insert(),而是自行实现 DELETE + INSERT SQL,直接操作 billiards_dws.dws_finance_income_structure 表,逐行插入并自动设置 created_at 和 updated_at。
2. 区域收入来源
区域维度的收入同时包含台费收入(dwd_table_fee_log.ledger_amount)和助教收入(dwd_assistant_service_log.ledger_amount),通过 UNION ALL 合并后按 (stat_date, area_name) 聚合。
DWS_FINANCE_DISCOUNT_DETAIL — 折扣明细统计
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_FINANCE_DISCOUNT_DETAIL |
| Python 类 | FinanceDiscountDetailTask(tasks/dws/finance_discount_task.py) |
| 目标表 | billiards_dws.dws_finance_discount_detail |
| 主键 | site_id, stat_date, discount_type_code |
| 粒度 | 日期 + 折扣类型 |
| 更新策略 | delete-before-insert(按日期窗口) |
| 更新频率 | 每日更新 |
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dwd_settlement_head |
billiards_dwd |
结账单头表(团购、手动调整、会员折扣、抹零) |
dwd_groupbuy_redemption |
billiards_dwd |
团购核销(团购实付金额) |
dwd_member_balance_change |
billiards_dwd |
余额变动(赠送卡消费,按卡类型拆分) |
输出字段
| 字段 | 说明 |
|---|---|
site_id, tenant_id, stat_date |
门店、统计日期 |
discount_type_code |
折扣类型代码(见下方分类定义) |
discount_type_name |
折扣类型名称(中文) |
discount_amount |
折扣金额 |
discount_ratio |
折扣占比(保留 4 位小数) |
usage_count |
使用次数 |
affected_orders |
影响订单数(当前等于 usage_count) |
折扣类型分类
每日展开为最多 8 条记录,覆盖以下折扣类型:
| discount_type_code | discount_type_name | 数据来源 | 计算逻辑 |
|---|---|---|---|
GROUPBUY |
团购优惠 | dwd_settlement_head + dwd_groupbuy_redemption |
coupon_amount - 团购实付金额(负值置 0) |
VIP |
会员折扣 | dwd_settlement_head.member_discount_amount |
直接取绝对值 |
ROUNDING |
抹零 | dwd_settlement_head.rounding_amount |
直接取绝对值 |
GIFT_CARD_TABLE |
台费卡抵扣 | dwd_member_balance_change(card_type_id = 2791990152417157) |
消费金额绝对值 |
GIFT_CARD_DRINK |
酒水卡抵扣 | dwd_member_balance_change(card_type_id = 2794699703437125) |
消费金额绝对值 |
GIFT_CARD_COUPON |
活动抵用券抵扣 | dwd_member_balance_change(card_type_id = 2793266846533445) |
消费金额绝对值 |
BIG_CUSTOMER |
大客户优惠 | dwd_settlement_head.adjust_amount(配置标记) |
按配置的会员/订单 ID 匹配 |
OTHER |
其他优惠 | dwd_settlement_head.adjust_amount(剩余部分) |
adjust_amount - big_customer_amount(负值置 0) |
核心业务逻辑
1. 团购优惠计算
与 DWS_FINANCE_DAILY 相同的逻辑:
团购实付 = pl_coupon_sale_amount > 0 ? pl_coupon_sale_amount : groupbuy_redemption.ledger_unit_price
团购优惠 = coupon_amount - 团购实付
仅统计 coupon_amount > 0 的已结账订单(settle_status = 1)。
2. 赠送卡消费拆分
与 DWS_FINANCE_DAILY 不同,此任务将赠送卡消费按卡类型拆分为 3 条独立记录(台费卡/酒水卡/活动抵用券),而非合并为一个总额。数据来源相同:dwd_member_balance_change 中 from_type = 1 且 change_amount < 0 的记录。
3. 大客户优惠拆分
手动调整金额(adjust_amount)通过配置项拆分:
dws.discount.big_customer_member_ids:大客户会员 ID 列表dws.discount.big_customer_order_ids:大客户订单 ID 列表
匹配到的订单调整金额归入 BIG_CUSTOMER,其余归入 OTHER。若两个配置项均为空,则所有手动调整归入 OTHER。
4. 占比计算
discount_ratio = 该类型折扣金额 / 当日所有类型折扣金额总和
5. 自定义 load 方法
与 DWS_FINANCE_INCOME_STRUCTURE 类似,此任务自行实现 DELETE + INSERT SQL,逐行插入并自动设置 created_at 和 updated_at。
运维任务
运维任务包含 4 个任务,负责订单汇总中间表构建、历史数据清理和物化视图刷新。这些任务不直接产出业务报表,而是为其他 DWS 任务和下游查询提供基础设施支撑。
dwd_settlement_head ──────────┐
dwd_table_fee_log ────────────┤
dwd_assistant_service_log ────┼──► DWS_BUILD_ORDER_SUMMARY(订单汇总中间表)
dwd_store_goods_sale ─────────┤
dwd_groupbuy_redemption ──────┤
dwd_refund / dwd_refund_ex ───┘
dws_*(所有 DWS 汇总表)──────► DWS_RETENTION_CLEANUP(历史数据清理)
dws_finance_daily_summary ────► DWS_MV_REFRESH_FINANCE_DAILY(财务日报物化视图刷新)
dws_assistant_daily_detail ───► DWS_MV_REFRESH_ASSISTANT_DAILY(助教日报物化视图刷新)
DWS_BUILD_ORDER_SUMMARY — 订单汇总中间表构建
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_BUILD_ORDER_SUMMARY |
| Python 类 | DwsBuildOrderSummaryTask(tasks/utility/dws_build_order_summary_task.py) |
| 继承 | BaseTask(非 BaseDwsTask) |
| 目标表 | billiards_dws.dws_order_summary |
| 主键 | site_id, order_settle_id |
| 粒度 | 订单(每笔结账单一条记录) |
| 更新策略 | delete-before-insert + ON CONFLICT DO UPDATE(upsert) |
用途
构建订单级别的汇总中间表 dws_order_summary,将分散在多张 DWD 事实表中的订单信息(台费、助教费、商品、团购、退款等)合并为一张宽表。该中间表可供下游报表查询和指数计算直接使用,避免每次都执行多表 JOIN。
数据来源
| 来源表 | Schema | 用途 |
|---|---|---|
dwd_settlement_head |
billiards_dwd |
结账单头表(基础订单信息、支付金额、优惠金额) |
dwd_table_fee_log |
billiards_dwd |
台费流水(真实台费金额) |
dwd_assistant_service_log |
billiards_dwd |
助教服务流水(助教服务金额) |
dwd_store_goods_sale |
billiards_dwd |
商品销售明细(商品数量、金额) |
dwd_groupbuy_redemption |
billiards_dwd |
团购核销(团购金额) |
dwd_refund / dwd_refund_ex |
billiards_dwd |
退款记录(退款金额) |
输出字段
| 字段分组 | 字段 | 说明 |
|---|---|---|
| 标识 | site_id, order_settle_id, order_trade_no, order_date, tenant_id |
门店、结账单号、交易号、订单日期、租户 |
| 会员 | member_id, member_flag, recharge_order_flag |
会员 ID、是否绑定会员、是否充值订单 |
| 商品 | item_count, total_item_quantity |
商品种类数、商品总数量 |
| 费用明细 | table_fee_amount, assistant_service_amount, goods_amount, group_amount |
台费、助教费、商品金额、团购金额 |
| 优惠 | total_coupon_deduction, member_discount_amount, manual_discount_amount |
团购抵扣、会员折扣、手动调整 |
| 金额汇总 | order_original_amount, order_final_amount |
订单原价、实付金额 |
| 支付方式 | stored_card_deduct, external_paid_amount, total_paid_amount |
储值卡抵扣、外部支付、总支付 |
| 台账流水 | book_table_flow, book_assistant_flow, book_goods_flow, book_group_flow, book_order_flow |
台费/助教/商品/团购/订单台账流水 |
| 有效消费 | order_effective_consume_cash, order_effective_recharge_cash, order_effective_flow |
有效消费现金、有效充值现金、有效流水 |
| 退款 | refund_amount, net_income |
退款金额、净收入 |
核心业务逻辑
1. SQL CTE 多表合并
任务通过一条大型 SQL(SQL_BUILD_SUMMARY)完成所有计算,使用 6 个 CTE 分别从不同事实表聚合数据,最终通过 LEFT JOIN 合并到订单粒度:
base:从dwd_settlement_head提取订单基础信息table_fee:从dwd_table_fee_log按order_settle_id聚合台费assistant_fee:从dwd_assistant_service_log按order_settle_id聚合助教费goods_fee:从dwd_store_goods_sale按order_settle_id聚合商品数量和金额group_fee:从dwd_groupbuy_redemption按order_settle_id聚合团购金额refunds:从dwd_refund+dwd_refund_ex按relate_id(关联订单)聚合退款金额
2. 金额优先级
台费、助教费、商品金额优先取明细表(dwd_table_fee_log / dwd_assistant_service_log / dwd_store_goods_sale)的聚合值,若明细表无数据则回退到结账单头表(dwd_settlement_head)的汇总字段:
COALESCE(tf.table_fee_amount, b.settle_table_fee_amount)
COALESCE(af.assistant_service_amount, b.settle_assistant_service_amount)
COALESCE(gf.goods_amount, b.settle_goods_amount)
3. 订单原价计算
order_original_amount = total_paid_amount + total_coupon_deduction + member_discount_amount + manual_discount_amount
即实付金额加上所有优惠金额,还原订单原始价格。
4. 外部支付金额
external_paid_amount = MAX(total_paid_amount - stored_card_deduct, 0)
总支付减去储值卡抵扣部分,即通过现金/在线支付的金额。
5. 净收入
net_income = total_paid_amount - refund_amount
6. 充值订单标记
recharge_order_flag = (consume_money = 0 AND pay_amount > 0)
消费金额为 0 但有支付金额的订单标记为充值订单。
配置参数
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
dws.order_summary.full_refresh |
bool |
False |
全量刷新模式(忽略日期窗口,处理全部数据) |
dws.order_summary.site_id |
int/None |
app.store_id |
指定门店 ID,设为 null 时处理所有门店 |
dws.order_summary.start_date |
date/None |
窗口起始日期 | 手动指定起始日期(覆盖窗口计算) |
dws.order_summary.end_date |
date/None |
窗口结束日期 | 手动指定结束日期(覆盖窗口计算) |
dws.order_summary.delete_before_insert |
bool |
True |
是否在插入前先删除旧数据 |
执行模式
- 增量模式(默认):按时间窗口处理,先 DELETE 窗口内旧数据,再 INSERT ... ON CONFLICT DO UPDATE
- 全量刷新(
full_refresh=True):- 若
site_id为null:执行TRUNCATE TABLE清空全表后重建 - 若
site_id有值:DELETE 该门店全部数据后重建
- 若
- 分段执行:支持
build_window_segments窗口分段,大时间范围自动拆分为多段依次执行
前置依赖
目标表 billiards_dws.dws_order_summary 必须已存在,否则抛出 RuntimeError。需先运行 INIT_DWS_SCHEMA 任务创建表结构。
DWS_RETENTION_CLEANUP — 时间分层清理
| 属性 | 值 |
|---|---|
| 任务代码 | DWS_RETENTION_CLEANUP |
| Python 类 | DwsRetentionCleanupTask(tasks/dws/retention_cleanup_task.py) |
| 继承 | BaseDwsTask |
| 目标表 | 多张 DWS 表(按配置) |
| 更新策略 | DELETE(按日期截断删除历史数据) |
用途
按配置的时间分层范围,对 DWS 层的汇总表执行历史数据清理。用于控制 DWS 表的数据量增长,删除超出保留期的历史记录。该任务默认不启用,需通过配置显式开启。
默认清理表列表
任务内置了 14 张 DWS 表的清理定义,每张表指定了对应的日期列:
| 目标表 | 日期列 | 说明 |
|---|---|---|
dws_assistant_daily_detail |
stat_date |
助教日度明细 |
dws_assistant_monthly_summary |
stat_month |
助教月度汇总 |
dws_assistant_customer_stats |
stat_date |
助教-客户关系 |
dws_assistant_salary_calc |
salary_month |
助教工资 |
dws_assistant_recharge_commission |
commission_month |
充值提成 |
dws_assistant_finance_analysis |
stat_date |
助教收支分析 |
dws_member_consumption_summary |
stat_date |
会员消费汇总 |
dws_member_visit_detail |
visit_date |
会员到店明细 |
dws_finance_daily_summary |
stat_date |
财务日报 |
dws_finance_income_structure |
stat_date |
收入结构 |
dws_finance_discount_detail |
stat_date |
折扣明细 |
dws_finance_recharge_summary |
stat_date |
充值统计 |
dws_finance_expense_summary |
expense_month |
支出汇总 |
dws_platform_settlement |
settlement_date |
平台结算 |
配置参数
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
dws.retention.enabled |
bool |
False |
是否启用清理(必须显式设为 true) |
dws.retention.layer |
str |
"ALL" |
默认清理层级(TimeLayer 枚举值) |
dws.retention.tables |
str |
全部 14 张表 | 需要清理的表名列表(逗号分隔),为空时使用全部默认表 |
dws.retention.table_layers |
str/dict |
{} |
表级别的层级覆盖(JSON 格式),可为特定表指定不同的保留期 |
配置示例
# .env 配置
DWS_RETENTION_ENABLED=true
DWS_RETENTION_LAYER=LAST_3_MONTHS
DWS_RETENTION_TABLES=dws_finance_daily_summary,dws_assistant_daily_detail
DWS_RETENTION_TABLE_LAYERS={"dws_finance_expense_summary":"ALL"}
上述配置含义:
- 启用清理功能
- 默认保留近 3 个月数据,删除更早的记录
- 仅清理
dws_finance_daily_summary和dws_assistant_daily_detail两张表 dws_finance_expense_summary使用ALL层级(即不清理,保留全部数据)
核心业务逻辑
1. 启用检查
任务执行时首先检查 dws.retention.enabled 配置,未启用则直接跳过,不执行任何删除操作。
2. 层级解析与截断日期计算
根据配置的 TimeLayer 层级,调用 get_time_layer_range(layer, base_date) 计算时间范围,取范围的起始日期作为截断点(cutoff)。早于截断点的数据将被删除。
| 层级 | 保留范围 | 截断点(以 2026-03-15 为例) |
|---|---|---|
LAST_2_DAYS |
近 2 天 | 2026-03-14 |
LAST_1_MONTH |
近 30 天 | 2026-02-13 |
LAST_3_MONTHS |
近 90 天 | 2025-12-15 |
LAST_6_MONTHS |
近 6 个月 | 2025-09-01(月初) |
ALL |
全量保留 | 不清理(跳过) |
3. 月度列截断对齐
对于日期列为月度粒度的表(stat_month、salary_month、commission_month、expense_month),截断日期自动对齐到月初(day=1),避免删除当月部分数据。
4. 表级层级覆盖
通过 dws.retention.table_layers 配置,可为特定表指定不同于默认层级的保留期。例如支出汇总表(月度 Excel 导入)可设为 ALL 永久保留,而日度明细表设为 LAST_3_MONTHS。
5. 清理 SQL
对每张目标表执行:
DELETE FROM billiards_dws.{table}
WHERE site_id = {store_id}
AND {date_col} < {cutoff}
按门店隔离,仅删除当前门店的历史数据。
6. 执行结果
返回每张表的删除行数明细和总删除行数:
{
"counts": {"cleaned": 1500},
"extra": {
"details": [
{"table": "dws_finance_daily_summary", "deleted": 800, "cutoff": "2025-12-15"},
{"table": "dws_assistant_daily_detail", "deleted": 700, "cutoff": "2025-12-15"}
]
}
}
DWS_MV_REFRESH_FINANCE_DAILY / DWS_MV_REFRESH_ASSISTANT_DAILY — 物化视图分层刷新
这两个任务共享同一个基类 BaseMvRefreshTask,仅在基表名称上有所不同。
任务信息
| 属性 | DWS_MV_REFRESH_FINANCE_DAILY | DWS_MV_REFRESH_ASSISTANT_DAILY |
|---|---|---|
| 任务代码 | DWS_MV_REFRESH_FINANCE_DAILY |
DWS_MV_REFRESH_ASSISTANT_DAILY |
| Python 类 | DwsMvRefreshFinanceDailyTask |
DwsMvRefreshAssistantDailyTask |
| 继承 | BaseMvRefreshTask → BaseDwsTask |
BaseMvRefreshTask → BaseDwsTask |
| 基表 | dws_finance_daily_summary |
dws_assistant_daily_detail |
| 日期列 | stat_date |
stat_date |
| 更新策略 | REFRESH MATERIALIZED VIEW |
REFRESH MATERIALIZED VIEW |
用途
按时间分层刷新 PostgreSQL 物化视图。物化视图预先按不同时间范围(L1-L4)聚合基表数据,供前端查询直接使用,避免每次查询都执行全表扫描。该任务默认不启用,需通过配置显式开启。
分层机制(L1-L4)
每张基表对应最多 4 个物化视图,按时间范围从小到大排列:
| 层级 | 后缀 | 对应 TimeLayer | 时间范围 | 视图命名示例 |
|---|---|---|---|---|
| L1 | _l1 |
LAST_2_DAYS |
近 2 天 | mv_dws_finance_daily_summary_l1 |
| L2 | _l2 |
LAST_1_MONTH |
近 30 天 | mv_dws_finance_daily_summary_l2 |
| L3 | _l3 |
LAST_3_MONTHS |
近 90 天 | mv_dws_finance_daily_summary_l3 |
| L4 | _l4 |
LAST_6_MONTHS |
近 6 个月 | mv_dws_finance_daily_summary_l4 |
视图命名规则:mv_{基表名}_{层级后缀}
配置参数
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
dws.mv.enabled |
bool |
False |
是否启用物化视图刷新(必须显式设为 true) |
dws.mv.tables |
str |
None |
需要刷新的基表列表(逗号分隔),为空时回退到 dws.retention.tables |
dws.mv.layers |
str |
None |
显式指定刷新的层级列表(逗号分隔,如 LAST_2_DAYS,LAST_1_MONTH) |
dws.mv.table_layers |
str/dict |
None |
表级别的层级覆盖(JSON 格式),为空时回退到 dws.retention.table_layers |
dws.mv.refresh_concurrently |
bool |
False |
是否使用 CONCURRENTLY 关键字刷新(不阻塞读查询,但需要唯一索引) |
层级解析优先级
任务按以下优先级确定需要刷新哪些层级:
- 显式配置(
dws.mv.layers):直接指定层级列表,最高优先级 - 表级覆盖(
dws.mv.table_layers→dws.retention.table_layers):按基表名查找对应层级,刷新该层级及其以下所有层级 - 默认层级(
dws.retention.layer):使用保留清理的层级配置,刷新该层级及其以下所有层级 - 全部刷新:以上均未配置时,刷新 L1-L4 全部 4 个层级
"该层级及其以下"的含义:若配置为 LAST_3_MONTHS(L3),则刷新 L1、L2、L3 三个层级。
启用条件
任务启用需同时满足:
dws.mv.enabled = true- 当前基表在允许列表中(
dws.mv.tables或dws.retention.tables包含该基表名),或两个列表均为空(不限制)
核心业务逻辑
1. 视图存在性检查
刷新前通过 SELECT to_regclass(...) 检查物化视图是否存在。不存在的视图会被跳过并记录警告日志,不会导致任务失败。
2. 刷新 SQL
-- 普通刷新(阻塞读查询)
REFRESH MATERIALIZED VIEW billiards_dws.mv_dws_finance_daily_summary_l1;
-- 并发刷新(不阻塞读查询,需要唯一索引)
REFRESH MATERIALIZED VIEW CONCURRENTLY billiards_dws.mv_dws_finance_daily_summary_l1;
是否使用 CONCURRENTLY 由 dws.mv.refresh_concurrently 配置控制。
3. 执行结果
返回刷新的视图数量和明细:
{
"counts": {"refreshed": 3},
"extra": {
"details": [
{"view": "mv_dws_finance_daily_summary_l1", "layer": "LAST_2_DAYS"},
{"view": "mv_dws_finance_daily_summary_l2", "layer": "LAST_1_MONTH"},
{"view": "mv_dws_finance_daily_summary_l3", "layer": "LAST_3_MONTHS"}
]
}
}
配置示例
# .env 配置
DWS_MV_ENABLED=true
DWS_MV_REFRESH_CONCURRENTLY=false
DWS_MV_LAYERS=LAST_2_DAYS,LAST_1_MONTH,LAST_3_MONTHS
# 或通过 retention 配置联动
DWS_RETENTION_ENABLED=true
DWS_RETENTION_LAYER=LAST_3_MONTHS
# 物化视图刷新会自动使用 retention 的层级配置,刷新 L1/L2/L3
与 DWS_RETENTION_CLEANUP 的配置联动
物化视图刷新任务与保留清理任务共享部分配置:
dws.mv.tables为空时,回退到dws.retention.tables确定需要刷新的基表dws.mv.table_layers为空时,回退到dws.retention.table_layers确定表级层级dws.retention.layer作为默认层级的最终回退
这种设计使得两个任务可以通过统一的 retention 配置体系联动控制,也可以通过 dws.mv.* 配置独立覆盖。