# 实现计划:ETL DWS 层扩展 — 小程序数据支撑 ## 概述 基于设计文档,将实现拆分为:DDL 建表/扩展 → 助教订单流水统计任务 → 会员消费汇总扩展 → 定档折算惩罚 → RLS 视图 + FDW 映射 → 影子跑数验证 六个阶段。所有数据库操作在测试库(`test_etl_feiqiu` / `test_zqyy_app`)中进行。 ## 任务 - [x] 1. DDL 建表与字段扩展 - [x] 1.1 编写迁移脚本创建 `dws.dws_assistant_order_contribution` 表 - 新建 `db/etl_feiqiu/migrations/<日期>__create_dws_assistant_order_contribution.sql` - 包含表定义、唯一索引 `idx_aoc_site_assistant_date`、查询索引 `idx_aoc_stat_date` - 字段参照设计文档数据模型章节 - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_ - [x] 1.2 编写迁移脚本扩展 `dws_member_consumption_summary` 字段 - 新建 `db/etl_feiqiu/migrations/<日期>__alter_member_consumption_add_recharge_fields.sql` - ALTER TABLE 添加 `recharge_count_30d/60d/90d`、`recharge_amount_30d/60d/90d`、`avg_ticket_amount` - _Requirements: 3.1, 3.2_ - [x] 1.3 编写迁移脚本扩展 `dws_assistant_daily_detail` 字段 - 新建 `db/etl_feiqiu/migrations/<日期>__alter_assistant_daily_add_penalty_fields.sql` - ALTER TABLE 添加 `penalty_minutes`、`penalty_reason`、`is_exempt`、`per_hour_contribution` - _Requirements: 5.1, 5.2_ - [x] 1.4 在测试库 `test_etl_feiqiu` 执行全部迁移脚本 - 通过 `PG_DSN`(指向测试库)连接执行 SQL - _Requirements: 1.5, 3.2, 5.2_ - [x] 1.5 运行 `gen_consolidated_ddl.py` 导出最新 DDL - 执行 `python scripts/ops/gen_consolidated_ddl.py` - 验证 `docs/database/ddl/etl_feiqiu__dws.sql` 已包含新表和扩展字段 - _Requirements: 1.6_ - [x] 2. 实现助教订单流水统计任务 - [x] 2.1 创建数据结构和 `AssistantOrderContributionTask` 骨架 - 新建 `apps/etl/connectors/feiqiu/tasks/dws/assistant_order_contribution_task.py` - 定义 `TableUsage`、`AssistantService`、`OrderData` dataclass - 定义 `AssistantOrderContributionTask` 类继承 `BaseDwsTask` - 实现 `get_task_code`、`get_target_table`、`get_primary_keys` - _Requirements: 1.1, 2.7_ - [x] 2.2 实现四项统计核心计算(纯函数) - 实现 `compute_order_gross_revenue` 静态方法 - 实现 `compute_order_net_revenue` 静态方法 - 实现 `compute_time_weighted_revenue` 静态方法(含台费分摊、酒水均分逻辑) - 实现 `compute_time_weighted_net_revenue` 静态方法 - 处理超休/打赏课特殊情况 - _Requirements: 2.2, 2.3, 2.4, 2.5, 2.6_ - [x] 2.3 编写属性测试:订单级统计不变量 - **Property 1: 订单级统计不变量 — gross/net 各助教相等** - **Validates: Requirements 2.2, 2.3, 10.1, 10.2** - [x] 2.4 编写属性测试:时效贡献流水之和约束 - **Property 2: 时效贡献流水之和 ≤ order_gross_revenue** - **Validates: Requirements 2.4, 10.3** - [x] 2.5 编写属性测试:时效净贡献减法关系 - **Property 3: time_weighted_net_revenue = time_weighted_revenue - commission** - **Validates: Requirements 2.5, 10.4** - [x] 2.6 实现 `extract` 方法 - 从 `dwd_settlement_head`、`dwd_table_fee_log`、`dwd_assistant_service_log` 提取数据 - 按 `order_settle_id` 聚合为 `OrderData` 结构 - _Requirements: 2.1_ - [x] 2.7 实现 `transform` 方法 - 遍历订单,调用四项统计计算函数 - 按 `(assistant_id, stat_date)` 聚合日度统计 - _Requirements: 2.2, 2.3, 2.4, 2.5, 2.6_ - [x] 2.8 注册任务到 task_registry 并导出模块 - 在 `tasks/dws/__init__.py` 中导出 `AssistantOrderContributionTask` - 在 `orchestration/task_registry.py` 中注册 `DWS_ASSISTANT_ORDER_CONTRIBUTION`,`layer="DWS"`,`depends_on=["DWD_LOAD_FROM_ODS"]` - _Requirements: 2.7, 2.8_ - [x] 3. 检查点 — 确保助教订单流水统计测试通过 - 运行属性测试:`cd C:\NeoZQYY && pytest tests/test_dws_contribution_properties.py -v` - 确保所有属性测试通过,如有问题请询问用户。 - [x] 4. 扩展会员消费汇总任务 - [x] 4.1 在 `MemberConsumptionTask` 中新增充值统计提取 - 新增 `_extract_recharge_stats` 方法,从 `dwd.dwd_recharge_order` 按 30/60/90 天窗口聚合 - 在 `extract` 方法中调用并返回充值统计数据 - _Requirements: 4.1, 3.3_ - [x] 4.2 在 `MemberConsumptionTask.transform` 中填充新字段 - 合并充值统计到输出记录 - 计算 `avg_ticket_amount = total_consume_amount / MAX(total_visit_count, 1)` - 处理无充值/无消费的边界情况 - _Requirements: 4.2, 4.3, 4.4, 3.4_ - [x] 4.3 编写属性测试:次均消费公式 - **Property 5: avg_ticket_amount = total_consume_amount / MAX(total_visit_count, 1)** - **Validates: Requirements 3.4, 10.7** - [x] 5. 实现定档折算惩罚检测与计算 - [x] 5.1 实现时间重叠检测逻辑 - 在 `AssistantDailyTask` 中新增 `detect_overlap_violations` 静态方法 - 定义惩罚区域集合(大厅 A/B/C/S/TV + 麻将房 M1–M7) - 按 `(table_id, service_date)` 分组,检测时间段重叠且助教数 > 2 - _Requirements: 6.1_ - [x] 5.2 实现惩罚分钟数计算 - 在 `AssistantDailyTask` 中新增 `compute_penalty_minutes` 静态方法 - 计算 `per_hour_contribution = 台费每小时单价 / 助教人数` - 按分段公式计算 `penalty_minutes` - 处理 `is_exempt = TRUE` 豁免逻辑 - _Requirements: 6.2, 6.3, 6.4, 6.5_ - [x] 5.3 集成惩罚逻辑到 `AssistantDailyTask.transform` - 在现有聚合逻辑后调用重叠检测和惩罚计算 - 将 `penalty_minutes`、`penalty_reason`、`is_exempt`、`per_hour_contribution` 填充到输出记录 - _Requirements: 6.6, 6.7_ - [x] 5.4 编写属性测试:惩罚分钟数分段公式 - **Property 4: 惩罚分钟数符合分段公式且在 [0, actual_minutes] 范围内** - **Validates: Requirements 6.3, 6.4, 10.5, 10.6** - [x] 5.5 编写属性测试:重叠检测正确性 - **Property 6: 3+ 助教时间重叠时应检测到违规** - **Validates: Requirements 6.1** - [x] 6. 检查点 — 确保惩罚计算和消费汇总测试通过 - 运行属性测试:`cd C:\NeoZQYY && pytest tests/test_dws_contribution_properties.py -v` - 运行单元测试:`cd apps/etl/connectors/feiqiu && pytest tests/unit/ -k "contribution or penalty or consumption" -v` - 确保所有测试通过,如有问题请询问用户。 - [x] 7. RLS 视图与 FDW 映射 - [x] 7.1 创建 `dws_assistant_order_contribution` 的 RLS 视图 - 在测试库 `test_etl_feiqiu` 的 `app` schema 中创建 `v_dws_assistant_order_contribution` 视图 - 使用 `current_setting('app.current_site_id')::bigint` 过滤 - 授予 `app_reader` 角色 SELECT 权限 - _Requirements: 7.1, 7.3_ - [x] 7.2 验证已有 RLS 视图自动包含新增字段 - 确认 `app.v_dws_member_consumption_summary` 和 `app.v_dws_assistant_daily_detail` 使用 `SELECT *`,新增字段自动包含 - _Requirements: 7.2_ - [x] 7.3 创建/更新 FDW 外部表映射 - 在测试库 `test_zqyy_app` 的 `fdw_etl` schema 中创建 `dws_assistant_order_contribution` 外部表 - 重建 `dws_member_consumption_summary` 和 `dws_assistant_daily_detail` 的 FDW 外部表以包含新字段 - FDW 映射通过 `app` schema RLS 视图访问 - _Requirements: 8.1, 8.2, 8.3_ - [x] 8. 影子跑数验证 - [x] 8.1 编写验证脚本 - 新建 `apps/etl/connectors/feiqiu/scripts/verify_dws_extensions.py` - 验证四项统计:对照 PRD 示例数据验算,检查 gross/net 各助教相等 - 验证充值窗口:检查新增字段有值,充值金额与源数据一致 - 验证惩罚字段:检查符合条件的记录正确填充 - _Requirements: 9.1, 9.2, 9.3, 9.4_ - [x] 8.2 编写数据库手册文档 - 新建 `docs/database/BD_Manual_dws_assistant_order_contribution.md` - 包含表结构、字段说明、索引、验证 SQL(至少 3 条)、兼容性说明、回滚策略 - 更新 `docs/database/` 中 `dws_member_consumption_summary` 和 `dws_assistant_daily_detail` 的文档 - _Requirements: 1.1_ - [x] 9. 最终检查点 — 确保所有测试通过 - 运行属性测试:`cd C:\NeoZQYY && pytest tests/test_dws_contribution_properties.py -v` - 运行单元测试:`cd apps/etl/connectors/feiqiu && pytest tests/unit/ -k "contribution or penalty or consumption" -v` - 确保所有测试通过,如有问题请询问用户。 ## 备注 - 标记 `*` 的子任务为可选(属性测试),可跳过以加速 MVP - 每个任务引用具体需求编号以确保可追溯 - 所有数据库操作在测试库(`test_etl_feiqiu` / `test_zqyy_app`)中进行 - 检查点确保增量验证 - 属性测试验证全称正确性属性,单元测试验证具体示例和边界情况