1
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
# BD_Manual:dws_assistant_order_contribution(助教订单流水四项统计)
|
||||
|
||||
> DWS 表:`dws.dws_assistant_order_contribution`
|
||||
> DWD 数据源:`dwd.dwd_settlement_head`(结算主表)、`dwd.dwd_table_fee_log`(台费明细)、`dwd.dwd_assistant_service_log`(助教服务记录)
|
||||
> 任务代码:`DWS_ASSISTANT_ORDER_CONTRIBUTION`
|
||||
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dws/assistant_order_contribution_task.py`
|
||||
> DDL 位置:`docs/database/ddl/etl_feiqiu__dws.sql`
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__create_dws_assistant_order_contribution.sql`
|
||||
> RLS 视图:`db/etl_feiqiu/migrations/2025-02-24__create_rls_view_assistant_order_contribution.sql`
|
||||
> FDW 映射:`db/zqyy_app/migrations/2025-02-24__add_fdw_dws_extensions.sql`
|
||||
|
||||
---
|
||||
|
||||
## 1. 表结构
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 | 取值范围/示例 |
|
||||
|------|------|--------|---------|-------------|
|
||||
| `contribution_id` | BIGINT (SERIAL) | nextval 序列 | 自增主键(PK) | 自增 |
|
||||
| `site_id` | BIGINT NOT NULL | — | 门店 ID | 飞球门店 ID |
|
||||
| `tenant_id` | BIGINT NOT NULL | — | 租户 ID | 飞球租户 ID |
|
||||
| `assistant_id` | BIGINT NOT NULL | — | 助教 ID | 飞球助教 ID |
|
||||
| `assistant_nickname` | VARCHAR(100) | NULL | 助教昵称 | 中文昵称 |
|
||||
| `stat_date` | DATE NOT NULL | — | 统计日期 | `2025-01-15` |
|
||||
| `order_gross_revenue` | NUMERIC(14,2) | 0 | 订单总流水 = 台费 + 酒水食品 + 所有助教服务费 | `0.00` ~ 金额值 |
|
||||
| `order_net_revenue` | NUMERIC(14,2) | 0 | 订单净流水 = 订单总流水 - 所有助教服务分成 | `0.00` ~ 金额值 |
|
||||
| `time_weighted_revenue` | NUMERIC(14,2) | 0 | 时效贡献流水 = 台费按时长分摊 + 个人服务费 + 酒水食品按时长比例 | `0.00` ~ 金额值 |
|
||||
| `time_weighted_net_revenue` | NUMERIC(14,2) | 0 | 时效净贡献 = 时效贡献流水 - 个人服务分成 | `0.00` ~ 金额值 |
|
||||
| `order_count` | INTEGER | 0 | 当日参与订单数 | `0` ~ 正整数 |
|
||||
| `total_service_seconds` | INTEGER | 0 | 当日总服务时长(秒) | `0` ~ 正整数 |
|
||||
| `created_at` | TIMESTAMPTZ | NOW() | 记录创建时间 | ISO 时间戳 |
|
||||
| `updated_at` | TIMESTAMPTZ | NOW() | 记录最后更新时间 | ISO 时间戳 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 主键与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|----|------|
|
||||
| `dws_assistant_order_contribution_pkey` | PRIMARY KEY | `contribution_id` | 物理主键(自增序列) |
|
||||
| `idx_aoc_site_assistant_date` | UNIQUE INDEX | `(site_id, assistant_id, stat_date)` | 业务主键:每个门店每个助教每天唯一一条记录 |
|
||||
| `idx_aoc_stat_date` | INDEX | `(site_id, stat_date)` | 按门店+日期查询,支持日度报表 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据写入策略
|
||||
|
||||
- **delete-before-insert**:每次执行按 `site_id` + 日期窗口全量刷新
|
||||
1. `DELETE FROM dws.dws_assistant_order_contribution WHERE site_id = %s AND stat_date BETWEEN %s AND %s`
|
||||
2. 批量 `INSERT` 新计算结果
|
||||
- 继承 `BaseDwsTask` 默认 load 实现,幂等可重跑
|
||||
|
||||
---
|
||||
|
||||
## 4. 算法概要
|
||||
|
||||
### 4.1 数据来源
|
||||
|
||||
| 来源表 | 筛选条件 | 提取内容 |
|
||||
|--------|---------|---------|
|
||||
| `dwd.dwd_settlement_head` | 日期窗口内,`settle_type IN (1, 3)` | 结算单信息、酒水食品金额 |
|
||||
| `dwd.dwd_table_fee_log` | 关联结算单 | 台桌使用时长、台费金额、区域 |
|
||||
| `dwd.dwd_assistant_service_log` | 关联结算单 | 助教服务时长、服务流水、分成、课程类型 |
|
||||
|
||||
### 4.2 四项统计公式
|
||||
|
||||
**订单总流水(order_gross_revenue)**
|
||||
```
|
||||
order_gross_revenue = total_table_fee + total_goods_amount + SUM(所有助教 ledger_amount)
|
||||
```
|
||||
每个参与助教获得相同值。
|
||||
|
||||
**订单净流水(order_net_revenue)**
|
||||
```
|
||||
order_net_revenue = order_gross_revenue - SUM(所有助教 commission)
|
||||
```
|
||||
每个参与助教获得相同值。
|
||||
|
||||
**时效贡献流水(time_weighted_revenue)**
|
||||
```
|
||||
对于台桌 t:
|
||||
billable_seconds = MAX(SUM(助教服务时长), 台桌使用时长)
|
||||
台费分摊_a = table_fee_t × (service_seconds_a / billable_seconds)
|
||||
|
||||
酒水食品分摊_a = total_goods_amount × (助教 a 总服务时长 / 所有助教总服务时长)
|
||||
|
||||
time_weighted_revenue_a = SUM(各台桌台费分摊_a) + ledger_amount_a + 酒水食品分摊_a
|
||||
```
|
||||
|
||||
**时效净贡献(time_weighted_net_revenue)**
|
||||
```
|
||||
time_weighted_net_revenue_a = time_weighted_revenue_a - commission_a
|
||||
```
|
||||
|
||||
### 4.3 超休/打赏课特殊处理
|
||||
|
||||
当 `course_type = BONUS` 时,四项统计均等于个人服务流水和分成,不参与订单级分摊。
|
||||
|
||||
---
|
||||
|
||||
## 5. 前置依赖
|
||||
|
||||
- 任务依赖:`DWD_LOAD_FROM_ODS`(需先完成 DWD 层数据加载)
|
||||
- 数据源表:`dwd.dwd_settlement_head`、`dwd.dwd_table_fee_log`、`dwd.dwd_assistant_service_log` 必须已有数据
|
||||
|
||||
---
|
||||
|
||||
## 6. 验证 SQL
|
||||
|
||||
### 6.1 检查表是否存在且有数据
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) AS total_rows,
|
||||
COUNT(DISTINCT site_id) AS site_count,
|
||||
COUNT(DISTINCT assistant_id) AS assistant_count,
|
||||
MIN(stat_date) AS earliest_date,
|
||||
MAX(stat_date) AS latest_date
|
||||
FROM dws.dws_assistant_order_contribution;
|
||||
```
|
||||
|
||||
### 6.2 检查业务主键唯一性(不应有重复)
|
||||
|
||||
```sql
|
||||
SELECT site_id, assistant_id, stat_date, COUNT(*) AS cnt
|
||||
FROM dws.dws_assistant_order_contribution
|
||||
GROUP BY site_id, assistant_id, stat_date
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 预期:无结果返回
|
||||
```
|
||||
|
||||
### 6.3 检查四项统计数值合理性(非负)
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE order_gross_revenue < 0) AS neg_gross,
|
||||
COUNT(*) FILTER (WHERE order_net_revenue < 0) AS neg_net,
|
||||
COUNT(*) FILTER (WHERE time_weighted_revenue < 0) AS neg_twr,
|
||||
COUNT(*) FILTER (WHERE time_weighted_net_revenue < 0) AS neg_twnr
|
||||
FROM dws.dws_assistant_order_contribution;
|
||||
-- 预期:所有列均为 0
|
||||
```
|
||||
|
||||
### 6.4 按门店查看统计概况
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
site_id,
|
||||
COUNT(*) AS record_count,
|
||||
SUM(order_count) AS total_orders,
|
||||
ROUND(AVG(order_gross_revenue), 2) AS avg_gross,
|
||||
ROUND(AVG(order_net_revenue), 2) AS avg_net,
|
||||
ROUND(AVG(time_weighted_revenue), 2) AS avg_twr,
|
||||
ROUND(AVG(time_weighted_net_revenue), 2) AS avg_twnr
|
||||
FROM dws.dws_assistant_order_contribution
|
||||
GROUP BY site_id
|
||||
ORDER BY site_id;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. RLS 视图与 FDW 映射
|
||||
|
||||
### 7.1 RLS 视图(ETL 库 app schema)
|
||||
|
||||
```sql
|
||||
-- 视图名:app.v_dws_assistant_order_contribution
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_order_contribution AS
|
||||
SELECT * FROM dws.dws_assistant_order_contribution
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
```
|
||||
|
||||
### 7.2 FDW 外部表(业务库 fdw_etl schema)
|
||||
|
||||
```sql
|
||||
-- 外部表名:fdw_etl.v_dws_assistant_order_contribution
|
||||
-- 通过 app schema RLS 视图访问,非直接访问 dws schema
|
||||
CREATE FOREIGN TABLE fdw_etl.v_dws_assistant_order_contribution (...)
|
||||
SERVER etl_server
|
||||
OPTIONS (schema_name 'app', table_name 'v_dws_assistant_order_contribution');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 兼容性说明
|
||||
|
||||
| 影响范围 | 说明 |
|
||||
|---------|------|
|
||||
| ETL 任务 | 新增任务 `DWS_ASSISTANT_ORDER_CONTRIBUTION`,依赖 `DWD_LOAD_FROM_ODS`。不影响现有 DWS 任务 |
|
||||
| 后端 API | 当前无 API 直接读取此表。后续小程序助教看板需新增接口 |
|
||||
| 管理后台 | 当前无前端页面展示。后续可在助教详情页新增流水统计展示 |
|
||||
| 小程序 | 小程序助教端将通过后端 API 读取此表数据展示四项统计 |
|
||||
| 其他 DWS 表 | 独立于现有 `dws_assistant_daily_detail`,不修改任何已有表或任务逻辑 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 回滚策略
|
||||
|
||||
### 9.1 删除数据(保留表结构)
|
||||
|
||||
```sql
|
||||
DELETE FROM dws.dws_assistant_order_contribution;
|
||||
```
|
||||
|
||||
### 9.2 完整回滚(删除表 + 视图 + FDW)
|
||||
|
||||
```sql
|
||||
-- 1. 删除 FDW 外部表(业务库)
|
||||
DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_assistant_order_contribution;
|
||||
|
||||
-- 2. 删除 RLS 视图(ETL 库)
|
||||
DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution;
|
||||
|
||||
-- 3. 删除表和索引(ETL 库)
|
||||
DROP INDEX IF EXISTS dws.idx_aoc_stat_date;
|
||||
DROP INDEX IF EXISTS dws.idx_aoc_site_assistant_date;
|
||||
DROP TABLE IF EXISTS dws.dws_assistant_order_contribution;
|
||||
```
|
||||
|
||||
### 9.3 回滚任务注册
|
||||
|
||||
从 `orchestration/task_registry.py` 中移除 `DWS_ASSISTANT_ORDER_CONTRIBUTION` 注册行,并从 `tasks/dws/__init__.py` 中移除 `AssistantOrderContributionTask` 导出。
|
||||
|
||||
---
|
||||
|
||||
## 10. 代码引用
|
||||
|
||||
- 任务类:`tasks/dws/assistant_order_contribution_task.py` → `AssistantOrderContributionTask`
|
||||
- 数据结构:`TableUsage`、`AssistantService`、`OrderData`(同文件)
|
||||
- 继承:`BaseDwsTask`
|
||||
- 任务注册:`orchestration/task_registry.py` → `DWS_ASSISTANT_ORDER_CONTRIBUTION`
|
||||
- 属性测试:`tests/test_dws_contribution_properties.py`
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__create_dws_assistant_order_contribution.sql`
|
||||
- RLS 视图:`db/etl_feiqiu/migrations/2025-02-24__create_rls_view_assistant_order_contribution.sql`
|
||||
- FDW 映射:`db/zqyy_app/migrations/2025-02-24__add_fdw_dws_extensions.sql`
|
||||
- 验证脚本:`apps/etl/connectors/feiqiu/scripts/verify_dws_extensions.py`
|
||||
|
||||
---
|
||||
|
||||
## 11. 关联扩展字段说明
|
||||
|
||||
本次 Spec(02-etl-dws-miniapp-extensions)同时扩展了两张已有表的字段,简要说明如下:
|
||||
|
||||
### 11.1 dws_member_consumption_summary 新增字段
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 |
|
||||
|------|------|--------|---------|
|
||||
| `recharge_count_30d` | INTEGER | 0 | 近 30 天充值次数 |
|
||||
| `recharge_count_60d` | INTEGER | 0 | 近 60 天充值次数 |
|
||||
| `recharge_count_90d` | INTEGER | 0 | 近 90 天充值次数 |
|
||||
| `recharge_amount_30d` | NUMERIC(14,2) | 0 | 近 30 天充值金额 |
|
||||
| `recharge_amount_60d` | NUMERIC(14,2) | 0 | 近 60 天充值金额 |
|
||||
| `recharge_amount_90d` | NUMERIC(14,2) | 0 | 近 90 天充值金额 |
|
||||
| `avg_ticket_amount` | NUMERIC(14,2) | 0 | 次均消费 = total_consume_amount / MAX(total_visit_count, 1) |
|
||||
|
||||
迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__alter_member_consumption_add_recharge_fields.sql`
|
||||
|
||||
### 11.2 dws_assistant_daily_detail 新增字段
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 |
|
||||
|------|------|--------|---------|
|
||||
| `penalty_minutes` | NUMERIC(10,2) | 0 | 定档折算惩罚分钟数,无惩罚时为 0 |
|
||||
| `penalty_reason` | TEXT | NULL | 惩罚原因描述,无惩罚时为 NULL |
|
||||
| `is_exempt` | BOOLEAN | FALSE | 是否豁免惩罚 |
|
||||
| `per_hour_contribution` | NUMERIC(14,2) | NULL | 单人每小时贡献流水 = 台费每小时单价 / 助教人数 |
|
||||
|
||||
迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__alter_assistant_daily_add_penalty_fields.sql`
|
||||
@@ -0,0 +1,80 @@
|
||||
# BD_Manual:DWS 库存汇总(日/周/月)
|
||||
|
||||
> DWS 表:`dws.dws_goods_stock_daily_summary`、`dws.dws_goods_stock_weekly_summary`、`dws.dws_goods_stock_monthly_summary`
|
||||
> DWD 数据源:`dwd.dwd_goods_stock_summary`
|
||||
> 任务代码:`DWS_GOODS_STOCK_DAILY`、`DWS_GOODS_STOCK_WEEKLY`、`DWS_GOODS_STOCK_MONTHLY`
|
||||
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dws/goods_stock_daily_task.py`、`goods_stock_weekly_task.py`、`goods_stock_monthly_task.py`
|
||||
> DDL 位置:`docs/database/ddl/etl_feiqiu__dws.sql`
|
||||
> 迁移脚本:`db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/migrations/2026-02-20__create_dws_goods_stock_summary.sql`(已归档)
|
||||
|
||||
---
|
||||
|
||||
## 1. 表结构(三张表结构相同)
|
||||
|
||||
| DWS 列名 | 类型 | 业务含义 | 取值范围/示例 |
|
||||
|----------|------|---------|-------------|
|
||||
| `site_id` | BIGINT | 门店 ID(PK 之一) | 飞球门店 ID |
|
||||
| `tenant_id` | BIGINT | 租户 ID | 飞球租户 ID |
|
||||
| `stat_date` | DATE | 统计日期(PK 之一)。日度=当天日期,周度=ISO 周一日期,月度=月首日期 | 如 `2026-01-15`、`2026-01-13`(周一)、`2026-01-01`(月首) |
|
||||
| `site_goods_id` | BIGINT | 门店商品 ID(PK 之一),关联 `dim_store_goods.site_goods_id` | 飞球商品 ID |
|
||||
| `goods_name` | TEXT | 商品名称 | 如 `百威啤酒` |
|
||||
| `goods_unit` | TEXT | 计量单位 | 如 `瓶`、`包` |
|
||||
| `goods_category_id` | BIGINT | 一级商品分类 ID | 飞球分类 ID |
|
||||
| `goods_category_second_id` | BIGINT | 二级商品分类 ID | 飞球分类 ID |
|
||||
| `category_name` | TEXT | 一级分类名称 | 如 `酒水` |
|
||||
| `range_start_stock` | NUMERIC | 期初库存(统计周期起点的库存量) | 数值 |
|
||||
| `range_end_stock` | NUMERIC | 期末库存(统计周期终点的库存量) | 数值 |
|
||||
| `range_in` | NUMERIC | 入库数量(统计周期内的采购/调拨入库总量) | 数值 |
|
||||
| `range_out` | NUMERIC | 出库数量(统计周期内的调拨出库/报损总量) | 数值 |
|
||||
| `range_sale` | NUMERIC | 销售数量(统计周期内的销售出库总量) | 数值 |
|
||||
| `range_sale_money` | NUMERIC(12,2) | 销售金额(元),统计周期内的销售总金额 | 金额值 |
|
||||
| `range_inventory` | NUMERIC | 盘点调整量(统计周期内的盘盈/盘亏净量) | 正/负数值 |
|
||||
| `current_stock` | NUMERIC | 当前库存(统计周期末的实时库存量) | 数值 |
|
||||
| `stat_period` | TEXT | 汇总粒度标识 | `'daily'` / `'weekly'` / `'monthly'` |
|
||||
| `created_at` | TIMESTAMPTZ | 记录创建时间 | 自动填充 `now()` |
|
||||
| `updated_at` | TIMESTAMPTZ | 记录最后更新时间 | 自动填充 `now()` |
|
||||
|
||||
---
|
||||
|
||||
## 2. 主键
|
||||
|
||||
`(site_id, stat_date, site_goods_id)` — 按门店、日期、商品维度唯一。
|
||||
|
||||
---
|
||||
|
||||
## 3. 粒度说明
|
||||
|
||||
| 表名 | 粒度 | stat_date 规则 | stat_period |
|
||||
|------|------|---------------|-------------|
|
||||
| `dws_goods_stock_daily_summary` | 日 | 当天日期 | `'daily'` |
|
||||
| `dws_goods_stock_weekly_summary` | 周 | ISO 周一日期 | `'weekly'` |
|
||||
| `dws_goods_stock_monthly_summary` | 月 | 月首日期(如 `2026-01-01`) | `'monthly'` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 聚合逻辑
|
||||
|
||||
- extract:从 `dwd.dwd_goods_stock_summary` 按时间范围查询
|
||||
- transform:按粒度对 `fetched_at` 进行分组聚合
|
||||
- `range_start_stock`:取周期内最早记录的 `range_start_stock`
|
||||
- `range_end_stock`:取周期内最晚记录的 `range_end_stock`
|
||||
- `range_in` / `range_out` / `range_sale` / `range_inventory`:SUM 汇总
|
||||
- `range_sale_money`:SUM 汇总
|
||||
- `current_stock`:取周期内最晚记录的 `current_stock`
|
||||
- `goods_name` / `goods_unit` / `category_name`:取最晚记录的值
|
||||
- load:upsert 写入目标表,主键冲突时更新
|
||||
|
||||
---
|
||||
|
||||
## 5. 前置依赖
|
||||
|
||||
- `dwd.dwd_goods_stock_summary` 表必须已创建并有数据
|
||||
- ODS 任务配置 `requires_window=True` 必须已生效并重新采集
|
||||
|
||||
---
|
||||
|
||||
## 6. 代码引用
|
||||
|
||||
- 任务代码:`tasks/dws/goods_stock_daily_task.py`、`goods_stock_weekly_task.py`、`goods_stock_monthly_task.py`
|
||||
- 继承:`BaseDwsTask`(`tasks/dws/base_dws_task.py`)
|
||||
- 任务注册:`DWS_GOODS_STOCK_DAILY`、`DWS_GOODS_STOCK_WEEKLY`、`DWS_GOODS_STOCK_MONTHLY`
|
||||
@@ -0,0 +1,251 @@
|
||||
# BD_Manual:dws_member_spending_power_index(SPI 消费力指数)
|
||||
|
||||
> DWS 表:`dws.dws_member_spending_power_index`
|
||||
> DWD 数据源:`dwd.dwd_settlement_head`(消费订单)、`dwd.dwd_recharge_order`(充值订单)
|
||||
> 配置表:`dws.cfg_index_parameters`(`index_type='SPI'`)
|
||||
> 任务代码:`DWS_SPENDING_POWER_INDEX`
|
||||
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dws/index/spending_power_index_task.py`
|
||||
> DDL 位置:`docs/database/ddl/etl_feiqiu__dws.sql`
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2026-02-23_create_dws_member_spending_power_index.sql`
|
||||
> 种子数据:`db/etl_feiqiu/seeds/seed_index_parameters.sql`(`index_type='SPI'` 部分)
|
||||
|
||||
---
|
||||
|
||||
## 1. 表结构
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 | 取值范围/示例 |
|
||||
|------|------|--------|---------|-------------|
|
||||
| `spi_id` | BIGINT (SERIAL) | nextval 序列 | 自增主键(PK) | 自增 |
|
||||
| `site_id` | INTEGER NOT NULL | — | 门店 ID | 飞球门店 ID |
|
||||
| `member_id` | BIGINT NOT NULL | — | 会员 ID | 飞球会员 ID |
|
||||
| `spend_30` | NUMERIC(14,2) | 0 | 近 30 天消费总额(元) | `0.00` ~ 金额值 |
|
||||
| `spend_90` | NUMERIC(14,2) | 0 | 近 90 天消费总额(元) | `0.00` ~ 金额值 |
|
||||
| `recharge_90` | NUMERIC(14,2) | 0 | 近 90 天充值总额(元) | `0.00` ~ 金额值 |
|
||||
| `orders_30` | INTEGER | 0 | 近 30 天消费笔数 | `0` ~ 正整数 |
|
||||
| `orders_90` | INTEGER | 0 | 近 90 天消费笔数 | `0` ~ 正整数 |
|
||||
| `visit_days_30` | INTEGER | 0 | 近 30 天消费日数(按天去重) | `0` ~ `30` |
|
||||
| `visit_days_90` | INTEGER | 0 | 近 90 天消费日数(按天去重) | `0` ~ `90` |
|
||||
| `avg_ticket_90` | NUMERIC(14,2) | 0 | 90 天客单价(= spend_90 / max(orders_90, 1)) | `0.00` ~ 金额值 |
|
||||
| `active_weeks_90` | INTEGER | 0 | 近 90 天有消费的自然周数 | `0` ~ `13` |
|
||||
| `daily_spend_ewma_90` | NUMERIC(14,2) | 0 | 日消费 EWMA(指数加权移动平均) | `0.00` ~ 金额值 |
|
||||
| `score_level_raw` | NUMERIC(10,4) | 0 | Level 子分原始分(消费水平) | ≥ 0 |
|
||||
| `score_speed_raw` | NUMERIC(10,4) | 0 | Speed 子分原始分(消费速度) | ≥ 0 |
|
||||
| `score_stability_raw` | NUMERIC(10,4) | 0 | Stability 子分原始分(消费稳定性) | `0.0000` ~ `1.0000` |
|
||||
| `score_level_display` | NUMERIC(5,2) | 0 | Level 子分展示分 | `0.00` ~ `10.00` |
|
||||
| `score_speed_display` | NUMERIC(5,2) | 0 | Speed 子分展示分 | `0.00` ~ `10.00` |
|
||||
| `score_stability_display` | NUMERIC(5,2) | 0 | Stability 子分展示分 | `0.00` ~ `10.00` |
|
||||
| `raw_score` | NUMERIC(10,4) | 0 | SPI 总分原始分(加权合成) | ≥ 0 |
|
||||
| `display_score` | NUMERIC(5,2) | 0 | SPI 总分展示分 | `0.00` ~ `10.00` |
|
||||
| `calc_time` | TIMESTAMPTZ | NOW() | 本次计算时间 | ISO 时间戳 |
|
||||
| `created_at` | TIMESTAMPTZ | NOW() | 记录创建时间 | ISO 时间戳 |
|
||||
| `updated_at` | TIMESTAMPTZ | NOW() | 记录最后更新时间 | ISO 时间戳 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 主键与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|----|------|
|
||||
| `dws_member_spending_power_index_pkey` | PRIMARY KEY | `spi_id` | 物理主键(自增序列) |
|
||||
| `idx_spi_site_member` | UNIQUE INDEX | `(site_id, member_id)` | 业务主键:每个门店每个会员唯一一条记录 |
|
||||
| `idx_spi_display_score` | INDEX | `(site_id, display_score DESC)` | 按门店查询展示分排名,支持 TOP-N 查询 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据写入策略
|
||||
|
||||
- **delete-before-insert**:每次执行按 `site_id` 全量刷新
|
||||
1. `DELETE FROM dws.dws_member_spending_power_index WHERE site_id = %s`
|
||||
2. 批量 `INSERT` 新计算结果
|
||||
- 无数据时跳过(不删除、不插入),返回 `{'status': 'skipped', 'reason': 'no_data'}`
|
||||
|
||||
---
|
||||
|
||||
## 4. 算法概要
|
||||
|
||||
### 4.1 数据来源
|
||||
|
||||
| 来源表 | 筛选条件 | 提取内容 |
|
||||
|--------|---------|---------|
|
||||
| `dwd.dwd_settlement_head` | 近 90 天,`settle_type IN (1, 3)` | 消费金额、笔数、消费日数、周覆盖、日消费序列 |
|
||||
| `dwd.dwd_recharge_order` | 近 90 天,`settle_type = 5` | 充值总额 |
|
||||
|
||||
### 4.2 子分公式
|
||||
|
||||
- **Level**(消费水平,权重 0.60):
|
||||
`L = w_s30 × ln(1 + spend_30/M30) + w_s90 × ln(1 + spend_90/M90) + w_ticket × ln(1 + avg_ticket_90/T0) + w_r90 × ln(1 + recharge_90/R90)`
|
||||
|
||||
- **Speed**(消费速度,权重 0.30):
|
||||
`S = w_abs × V_abs + w_rel × max(0, V_rel) + w_ewma × V_ewma`
|
||||
- `V_abs = ln(1 + spend_30 / (max(visit_days_30, 1) × V0))`
|
||||
- `V_rel = ln((v_30 + ε) / (v_90 + ε))`,仅加速加分
|
||||
- `V_ewma = ln(1 + daily_spend_ewma_90 / E0)`
|
||||
|
||||
- **Stability**(消费稳定性,权重 0.10):
|
||||
`P = active_weeks_90 / 13`,取值 [0, 1]
|
||||
|
||||
### 4.3 总分合成
|
||||
|
||||
`SPI_raw = w_L × L + w_S × S + w_P × P`(默认 0.60 / 0.30 / 0.10)
|
||||
|
||||
### 4.4 展示分映射
|
||||
|
||||
Raw → Winsorize(P5, P95) → 可选压缩(log1p/asinh) → MinMax [0, 10] → 可选 EWMA 平滑
|
||||
|
||||
子分(Level/Speed/Stability)各自独立映射,使用 `SPI_LEVEL` / `SPI_SPEED` / `SPI_STABILITY` 隔离分位历史。
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置参数
|
||||
|
||||
所有参数存储在 `dws.cfg_index_parameters`(`index_type='SPI'`),缺失时回退到代码中 `DEFAULT_PARAMS`。
|
||||
|
||||
| 参数名 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `spend_window_short_days` | 30 | 短窗口天数 |
|
||||
| `spend_window_long_days` | 90 | 长窗口天数 |
|
||||
| `ewma_alpha_daily_spend` | 0.3 | 日消费 EWMA 平滑系数 |
|
||||
| `amount_base_spend_30` | 500.0 | 30 天消费金额压缩基数 |
|
||||
| `amount_base_spend_90` | 1500.0 | 90 天消费金额压缩基数 |
|
||||
| `amount_base_ticket_90` | 200.0 | 客单价压缩基数 |
|
||||
| `amount_base_recharge_90` | 1000.0 | 充值金额压缩基数 |
|
||||
| `amount_base_speed_abs` | 100.0 | 绝对速度压缩基数 |
|
||||
| `amount_base_ewma_90` | 50.0 | EWMA 速度压缩基数 |
|
||||
| `w_level_spend_30` | 0.30 | Level 子分中 spend_30 权重 |
|
||||
| `w_level_spend_90` | 0.35 | Level 子分中 spend_90 权重 |
|
||||
| `w_level_ticket_90` | 0.20 | Level 子分中 avg_ticket_90 权重 |
|
||||
| `w_level_recharge_90` | 0.15 | Level 子分中 recharge_90 权重 |
|
||||
| `w_speed_abs` | 0.50 | Speed 子分中绝对速度权重 |
|
||||
| `w_speed_rel` | 0.30 | Speed 子分中相对速度权重 |
|
||||
| `w_speed_ewma` | 0.20 | Speed 子分中 EWMA 速度权重 |
|
||||
| `weight_level` | 0.60 | 总分中 Level 权重 |
|
||||
| `weight_speed` | 0.30 | 总分中 Speed 权重 |
|
||||
| `weight_stability` | 0.10 | 总分中 Stability 权重 |
|
||||
| `stability_window_days` | 90 | 稳定性计算窗口 |
|
||||
| `use_stability` | 1 | 是否启用稳定性子分(0=禁用) |
|
||||
| `percentile_lower` | 5 | Winsorize 下分位 |
|
||||
| `percentile_upper` | 95 | Winsorize 上分位 |
|
||||
| `compression_mode` | 1 | 压缩模式:0=无,1=log1p,2=asinh |
|
||||
| `use_smoothing` | 1 | 是否启用 EWMA 分位平滑 |
|
||||
| `ewma_alpha` | 0.2 | 分位平滑 EWMA 系数 |
|
||||
| `speed_epsilon` | 1e-6 | 速度计算防除零小量 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 前置依赖
|
||||
|
||||
- 任务依赖:`DWS_MEMBER_CONSUMPTION`(需先完成会员消费汇总)
|
||||
- 数据源表:`dwd.dwd_settlement_head`、`dwd.dwd_recharge_order` 必须已有数据
|
||||
- 配置表:`dws.cfg_index_parameters` 中 `index_type='SPI'` 种子数据已插入(缺失时使用默认值)
|
||||
- 分位历史表:`dws.dws_index_percentile_history`(首次执行时无历史,不平滑)
|
||||
|
||||
---
|
||||
|
||||
## 7. 验证 SQL
|
||||
|
||||
### 7.1 检查表是否存在且有数据
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) AS total_rows,
|
||||
COUNT(DISTINCT site_id) AS site_count,
|
||||
MIN(calc_time) AS earliest_calc,
|
||||
MAX(calc_time) AS latest_calc
|
||||
FROM dws.dws_member_spending_power_index;
|
||||
```
|
||||
|
||||
### 7.2 检查展示分范围是否合规(应全部在 [0, 10])
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE display_score < 0 OR display_score > 10) AS spi_out_of_range,
|
||||
COUNT(*) FILTER (WHERE score_level_display < 0 OR score_level_display > 10) AS level_out_of_range,
|
||||
COUNT(*) FILTER (WHERE score_speed_display < 0 OR score_speed_display > 10) AS speed_out_of_range,
|
||||
COUNT(*) FILTER (WHERE score_stability_display < 0 OR score_stability_display > 10) AS stability_out_of_range
|
||||
FROM dws.dws_member_spending_power_index;
|
||||
-- 预期:所有列均为 0
|
||||
```
|
||||
|
||||
### 7.3 检查业务主键唯一性(不应有重复)
|
||||
|
||||
```sql
|
||||
SELECT site_id, member_id, COUNT(*) AS cnt
|
||||
FROM dws.dws_member_spending_power_index
|
||||
GROUP BY site_id, member_id
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 预期:无结果返回
|
||||
```
|
||||
|
||||
### 7.4 按门店查看 SPI 分布概况
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
site_id,
|
||||
COUNT(*) AS member_count,
|
||||
ROUND(AVG(display_score), 2) AS avg_spi,
|
||||
ROUND(MIN(display_score), 2) AS min_spi,
|
||||
ROUND(MAX(display_score), 2) AS max_spi,
|
||||
ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY display_score), 2) AS median_spi
|
||||
FROM dws.dws_member_spending_power_index
|
||||
GROUP BY site_id
|
||||
ORDER BY site_id;
|
||||
```
|
||||
|
||||
### 7.5 检查 Stability 子分原始分范围(应在 [0, 1])
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) AS out_of_range
|
||||
FROM dws.dws_member_spending_power_index
|
||||
WHERE score_stability_raw < 0 OR score_stability_raw > 1;
|
||||
-- 预期:0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 兼容性说明
|
||||
|
||||
| 影响范围 | 说明 |
|
||||
|---------|------|
|
||||
| ETL 任务 | 新增任务 `DWS_SPENDING_POWER_INDEX`,依赖 `DWS_MEMBER_CONSUMPTION`。不影响现有 WBI/NCI/RS/OS/MS/ML 指数任务 |
|
||||
| 后端 API | 当前无 API 直接读取此表。后续如需暴露 SPI 数据,需新增接口 |
|
||||
| 管理后台 | 当前无前端页面展示 SPI。后续可在会员详情页新增 SPI 展示 |
|
||||
| 小程序 | 无影响 |
|
||||
| 其他指数 | SPI 独立于现有指数体系,不修改任何已有表或任务逻辑 |
|
||||
| 分位历史 | SPI 会向 `dws.dws_index_percentile_history` 写入 `index_type='SPI'`/`SPI_LEVEL`/`SPI_SPEED`/`SPI_STABILITY` 的分位记录 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 回滚策略
|
||||
|
||||
### 9.1 删除数据(保留表结构)
|
||||
|
||||
```sql
|
||||
DELETE FROM dws.dws_member_spending_power_index;
|
||||
DELETE FROM dws.dws_index_percentile_history WHERE index_type LIKE 'SPI%';
|
||||
DELETE FROM dws.cfg_index_parameters WHERE index_type = 'SPI';
|
||||
```
|
||||
|
||||
### 9.2 完整回滚(删除表)
|
||||
|
||||
```sql
|
||||
DROP INDEX IF EXISTS dws.idx_spi_display_score;
|
||||
DROP INDEX IF EXISTS dws.idx_spi_site_member;
|
||||
DROP TABLE IF EXISTS dws.dws_member_spending_power_index;
|
||||
DROP SEQUENCE IF EXISTS dws.dws_member_spending_power_index_spi_id_seq;
|
||||
```
|
||||
|
||||
### 9.3 回滚任务注册
|
||||
|
||||
从 `orchestration/task_registry.py` 中移除 `DWS_SPENDING_POWER_INDEX` 注册行,并从 `tasks/dws/index/__init__.py` 和 `tasks/dws/__init__.py` 中移除 `SpendingPowerIndexTask` 导出。
|
||||
|
||||
---
|
||||
|
||||
## 10. 代码引用
|
||||
|
||||
- 任务类:`tasks/dws/index/spending_power_index_task.py` → `SpendingPowerIndexTask`
|
||||
- 继承:`BaseIndexTask`(`tasks/dws/index/base_index_task.py`)
|
||||
- 任务注册:`orchestration/task_registry.py` → `DWS_SPENDING_POWER_INDEX`
|
||||
- 属性测试:`tests/test_spi_properties.py`
|
||||
- 单元测试:`apps/etl/connectors/feiqiu/tests/unit/test_spi_task.py`
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/2026-02-23_create_dws_member_spending_power_index.sql`
|
||||
- 种子数据:`db/etl_feiqiu/seeds/seed_index_parameters.sql`
|
||||
@@ -0,0 +1,78 @@
|
||||
# BD 手册:dws_assistant_project_tag / dws_member_project_tag(项目标签表)
|
||||
|
||||
> 变更日期:2026-03-12
|
||||
> 变更类型:补充任务注册(seed 脚本遗漏修复)
|
||||
|
||||
## 变更说明
|
||||
|
||||
两张项目标签表(`dws.dws_assistant_project_tag`、`dws.dws_member_project_tag`)于 2026-03-07 创建,代码层(task class + TaskRegistry)已完整实现,但 `db/etl_feiqiu/seeds/seed_scheduler_tasks.sql` 遗漏了这两个任务码,导致 `meta.etl_task` 表中无记录,调度时报"未启用或不存在"。
|
||||
|
||||
本次修复:
|
||||
- 在 seed 脚本中补充 `DWS_ASSISTANT_PROJECT_TAG` 和 `DWS_MEMBER_PROJECT_TAG`
|
||||
- 在测试库 `test_etl_feiqiu` 的 `meta.etl_task` 表中插入两条记录(`enabled = TRUE`)
|
||||
|
||||
## 表概览
|
||||
|
||||
| 表名 | Schema | 粒度 | 时间窗口数 | 数据来源 |
|
||||
|------|--------|------|-----------|----------|
|
||||
| `dws_assistant_project_tag` | dws | 助教 + 时间窗口 + 项目类型 | 6 | `dwd_assistant_service_log` + `dim_table` + `cfg_area_category` |
|
||||
| `dws_member_project_tag` | dws | 会员 + 时间窗口 + 项目类型 | 2 | `dwd_table_fee_log` + `dim_table` + `cfg_area_category` |
|
||||
|
||||
两表共享相同的标签计算逻辑:按时间窗口聚合各项目时长,计算占比,≥25% 分配标签。
|
||||
|
||||
## 兼容性影响
|
||||
|
||||
- ETL 调度:修复后两个任务可正常被 admin 后台调度执行
|
||||
- 后端 API:无影响(API 层直接查询 DWS 表,表结构未变)
|
||||
- 小程序/管理后台:无影响(前端查询不依赖 `meta.etl_task`)
|
||||
- 其他 ETL 任务:无影响(两个任务独立运行,不被其他任务依赖)
|
||||
|
||||
## 回滚策略
|
||||
|
||||
```sql
|
||||
-- 1. 从 meta.etl_task 移除注册
|
||||
DELETE FROM meta.etl_task
|
||||
WHERE task_code IN ('DWS_ASSISTANT_PROJECT_TAG', 'DWS_MEMBER_PROJECT_TAG');
|
||||
|
||||
-- 2. 还原 seed 脚本:从 task_codes 数组中移除这两行
|
||||
-- 'DWS_ASSISTANT_PROJECT_TAG',
|
||||
-- 'DWS_MEMBER_PROJECT_TAG',
|
||||
```
|
||||
|
||||
## 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认 meta.etl_task 中两个任务已注册且启用
|
||||
SELECT task_code, store_id, enabled, created_at
|
||||
FROM meta.etl_task
|
||||
WHERE task_code IN ('DWS_ASSISTANT_PROJECT_TAG', 'DWS_MEMBER_PROJECT_TAG');
|
||||
-- 期望:2 行,enabled = TRUE
|
||||
|
||||
-- 2. 确认 seed 脚本与 task_registry 一致(无遗漏)
|
||||
-- 手动比对 seed_scheduler_tasks.sql 中的 task_codes 与 task_registry.py 中的注册列表
|
||||
|
||||
-- 3. 确认两张 DWS 表存在且结构正确
|
||||
SELECT table_name, column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'dws'
|
||||
AND table_name IN ('dws_assistant_project_tag', 'dws_member_project_tag')
|
||||
ORDER BY table_name, ordinal_position;
|
||||
-- 期望:每张表 15 个字段
|
||||
|
||||
-- 4. 确认调度可正常执行(在 admin 后台触发后检查)
|
||||
SELECT task_code, status, started_at, finished_at
|
||||
FROM meta.etl_run r
|
||||
JOIN meta.etl_task t ON r.task_id = t.task_id
|
||||
WHERE t.task_code IN ('DWS_ASSISTANT_PROJECT_TAG', 'DWS_MEMBER_PROJECT_TAG')
|
||||
ORDER BY r.started_at DESC
|
||||
LIMIT 4;
|
||||
```
|
||||
|
||||
## 详细 BD 手册
|
||||
|
||||
两张表的完整字段说明、索引、数据链路、业务规则见模块级 BD 手册:
|
||||
- `apps/etl/connectors/feiqiu/docs/database/DWS/main/BD_manual_dws_assistant_project_tag.md`
|
||||
- `apps/etl/connectors/feiqiu/docs/database/DWS/main/BD_manual_dws_member_project_tag.md`
|
||||
|
||||
ETL 任务详细说明见:
|
||||
- `apps/etl/connectors/feiqiu/docs/etl_tasks/dws_tasks.md`(项目标签域章节)
|
||||
@@ -1,6 +1,6 @@
|
||||
# cfg_area_category 台区分类映射表
|
||||
|
||||
> 生成时间:2026-02-03 | 更新时间:2026-03-07
|
||||
> 生成时间:2026-02-03 | 更新时间:2026-03-09
|
||||
|
||||
## 表信息
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
| 表名 | cfg_area_category |
|
||||
| 主键 | category_id |
|
||||
| 唯一约束 | (source_area_name, COALESCE(source_table_name, '')) |
|
||||
| 数据来源 | 手工维护/seed脚本(基于dim_table实际数据) |
|
||||
| 说明 | 将dim_table的台区/台桌映射到项目分类,支持台桌级细分 |
|
||||
| 数据来源 | 手工维护/seed脚本(基于用户提供的完整台桌清单) |
|
||||
| 说明 | 纯台桌级精确映射,每台桌一行 (区域, 台桌名) → 分类代码 |
|
||||
|
||||
## 字段说明
|
||||
|
||||
@@ -19,47 +19,124 @@
|
||||
|------|--------|------|------|------|------|
|
||||
| 1 | category_id | SERIAL | NO | PK | 分类ID(自增) |
|
||||
| 2 | source_area_name | VARCHAR(100) | NO | UK | 源区域名称(来自dim_table.site_table_area_name) |
|
||||
| 3 | source_table_name | VARCHAR(100) | YES | UK | 源台桌名称(来自dim_table.table_name),NULL表示区域级映射 |
|
||||
| 3 | source_table_name | VARCHAR(100) | YES | UK | 源台桌名称(来自dim_table.table_name),NULL仅用于DEFAULT兜底 |
|
||||
| 4 | category_code | VARCHAR(20) | NO | | 分类代码。**枚举值**: BILLIARD, SNOOKER, MAHJONG, KTV, SPECIAL, OTHER |
|
||||
| 5 | category_name | VARCHAR(50) | NO | | 分类名称(含emoji) |
|
||||
| 6 | display_name | VARCHAR(50) | YES | | 显示名称(用于筛选器) |
|
||||
| 7 | short_name | VARCHAR(20) | YES | | 简写(用于列表标签) |
|
||||
| 8 | match_type | VARCHAR(10) | NO | | 匹配类型。**枚举值**: EXACT(精确), LIKE(模糊), DEFAULT(兜底) |
|
||||
| 9 | match_priority | INTEGER | NO | | 匹配优先级(数字越小优先级越高) |
|
||||
| 8 | match_type | VARCHAR(10) | NO | | 匹配类型。**枚举值**: EXACT(精确), DEFAULT(兜底)。LIKE 已废弃 |
|
||||
| 9 | match_priority | INTEGER | NO | | 匹配优先级(统一为10,兜底999) |
|
||||
| 10 | is_active | BOOLEAN | NO | | 是否启用 |
|
||||
| 11 | description | TEXT | YES | | 说明 |
|
||||
| 12 | created_at | TIMESTAMPTZ | NO | | 创建时间 |
|
||||
| 13 | updated_at | TIMESTAMPTZ | NO | | 更新时间 |
|
||||
|
||||
## 变更说明(2026-03-07)
|
||||
## 变更说明(2026-03-09)
|
||||
|
||||
### 新增字段
|
||||
- `source_table_name`:支持台桌级细分映射(如 VIP包厢 V5 → SNOOKER)
|
||||
- `display_name`:前端筛选器显示名称
|
||||
- `short_name`:列表中的简写标签
|
||||
### 架构变更:纯台桌级精确映射
|
||||
- 删除所有 LIKE 模糊匹配规则(%VIP%、%斯诺克%、%麻将%、%K包%、%KTV%)
|
||||
- 删除所有区域级映射(source_table_name IS NULL 的 EXACT 记录)
|
||||
- 改为每台桌一行精确映射:source_area_name=区域, source_table_name=台桌名
|
||||
- 仅保留 DEFAULT 兜底规则(source_table_name IS NULL)
|
||||
|
||||
### 删除类型
|
||||
- `BILLIARD_VIP` 已废弃,VIP包厢 V1-V4 归入 `BILLIARD`,V5 归入 `SNOOKER`
|
||||
### 新增台桌
|
||||
- KTV:888、常乐、幸会(纯k)、虚拟188、大包、小包、纯k
|
||||
- MAHJONG:董事办、大包麻将房、666(台桌)
|
||||
- BILLIARD:VIP1-VIP3(台桌级)、TV、A1-A18、B1-B15、C1-C6
|
||||
- SNOOKER:S1-S4、VIP5
|
||||
- SPECIAL:补时长2-7、虚拟台1号
|
||||
|
||||
### 唯一约束变更
|
||||
- 从 `(source_area_name)` 改为 `(source_area_name, COALESCE(source_table_name, ''))`
|
||||
### ETL 代码变更
|
||||
- `get_area_category()` 去掉区域级精确匹配和 LIKE 模糊匹配分支
|
||||
- 仅保留台桌级精确匹配 + DEFAULT 兜底
|
||||
|
||||
## 匹配优先级
|
||||
## 匹配规则
|
||||
|
||||
| 优先级 | 匹配方式 | 说明 |
|
||||
|--------|---------|------|
|
||||
| 5 | 台桌级精确 | source_area_name + source_table_name 都匹配 |
|
||||
| 10 | 区域级精确 | source_area_name 匹配,source_table_name 为 NULL |
|
||||
| 50 | 模糊匹配 | source_area_name 包含模式匹配 |
|
||||
| 999 | 兜底 | 无法匹配的区域归入 OTHER |
|
||||
| 10 | 台桌级精确 | source_area_name + source_table_name 都匹配 |
|
||||
| 999 | 兜底 | 无法匹配的归入 OTHER |
|
||||
|
||||
## 分类映射
|
||||
## 分类映射(完整台桌清单)
|
||||
|
||||
| 分类代码 | 显示名称 | 简写 | 源区域 |
|
||||
|----------|---------|------|--------|
|
||||
| BILLIARD | 🎱 中式/追分 | 🎱 | A区、B区、C区、TV台、VIP包厢(V1-V4) |
|
||||
| SNOOKER | 斯诺克 | 斯 | 斯诺克区、VIP包厢(V5) |
|
||||
| MAHJONG | 🀄 麻将/棋牌 | 🀄 | 麻将房、M7、M8、666、发财 |
|
||||
| KTV | 🎤 团建/K歌 | 🎤 | K包、k包活动区、幸会158 |
|
||||
| SPECIAL | 补时长 | 补 | 补时长 |
|
||||
| OTHER | 其他 | 他 | 兜底 |
|
||||
### BILLIARD 🎱 中式/追分(43台)
|
||||
| 区域 | 台桌 |
|
||||
|------|------|
|
||||
| A区 | A1-A18(18台) |
|
||||
| B区 | B1-B15(15台) |
|
||||
| C区 | C1-C6(6台) |
|
||||
| VIP包厢 | VIP1, VIP2, VIP3 |
|
||||
| TV台 | TV |
|
||||
|
||||
### SNOOKER 斯诺克(5台)
|
||||
| 区域 | 台桌 |
|
||||
|------|------|
|
||||
| 斯诺克区 | S1, S2, S3, S4 |
|
||||
| VIP包厢 | VIP5 |
|
||||
|
||||
### MAHJONG 🀄 麻将/棋牌(11台)
|
||||
| 区域 | 台桌 |
|
||||
|------|------|
|
||||
| 麻将房 | M1, M2, M3, M4, M5 |
|
||||
| M7 | M7, 大包麻将房 |
|
||||
| M8 | M8 |
|
||||
| 666 | 董事办, 666 |
|
||||
| 发财 | 发财 |
|
||||
|
||||
### KTV 🎤 团建/K歌(7台)
|
||||
| 区域 | 台桌 |
|
||||
|------|------|
|
||||
| K包 | 常乐, 幸会(纯k), 虚拟188, 888 |
|
||||
| k包活动区 | 大包, 小包 |
|
||||
| 幸会158 | 纯k |
|
||||
|
||||
### SPECIAL 补时长(8台)
|
||||
| 区域 | 台桌 |
|
||||
|------|------|
|
||||
| 补时长 | 补时长, 补时长2-7 |
|
||||
| 虚拟台 | 虚拟台1号 |
|
||||
|
||||
### OTHER 其他
|
||||
| 匹配 | 说明 |
|
||||
|------|------|
|
||||
| DEFAULT | 兜底规则,无法匹配的归入其他 |
|
||||
|
||||
## 历史变更
|
||||
|
||||
| 日期 | 变更 |
|
||||
|------|------|
|
||||
| 2026-02-03 | 初始创建,区域级精确 + LIKE 模糊匹配 |
|
||||
| 2026-03-07 | 新增 source_table_name 支持台桌级细分;废弃 BILLIARD_VIP |
|
||||
| 2026-03-09 | 改为纯台桌级精确映射,删除所有 LIKE 和区域级映射 |
|
||||
|
||||
## 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认无 LIKE 匹配记录
|
||||
SELECT COUNT(*) AS like_count FROM dws.cfg_area_category WHERE match_type = 'LIKE';
|
||||
-- 期望: 0
|
||||
|
||||
-- 2. 确认所有 EXACT 记录都有 source_table_name
|
||||
SELECT COUNT(*) AS null_table_count
|
||||
FROM dws.cfg_area_category
|
||||
WHERE match_type = 'EXACT' AND source_table_name IS NULL;
|
||||
-- 期望: 0
|
||||
|
||||
-- 3. 确认 KTV 大类包含 888
|
||||
SELECT source_area_name, source_table_name, category_code
|
||||
FROM dws.cfg_area_category
|
||||
WHERE category_code = 'KTV' AND is_active = true
|
||||
ORDER BY source_area_name, source_table_name;
|
||||
-- 期望: 7 行(常乐, 幸会(纯k), 虚拟188, 888, 大包, 小包, 纯k)
|
||||
|
||||
-- 4. 确认总记录数
|
||||
SELECT COUNT(*) AS total FROM dws.cfg_area_category;
|
||||
-- 期望: 75(74 台桌 + 1 DEFAULT 兜底)
|
||||
|
||||
-- 5. 按分类汇总
|
||||
SELECT category_code, COUNT(*) AS cnt
|
||||
FROM dws.cfg_area_category
|
||||
WHERE match_type = 'EXACT'
|
||||
GROUP BY category_code ORDER BY category_code;
|
||||
-- 期望: BILLIARD=43, KTV=7, MAHJONG=11, SNOOKER=5, SPECIAL=8
|
||||
```
|
||||
|
||||
@@ -62,18 +62,27 @@
|
||||
|
||||
### 按区域汇总台费
|
||||
```sql
|
||||
-- 实际 ETL 通过 Python get_area_category(area_name, table_name) 映射
|
||||
-- 以下为等效 SQL 示意(台桌级精确匹配 + DEFAULT 兜底)
|
||||
SELECT
|
||||
DATE(tfl.ledger_end_time) AS stat_date,
|
||||
COALESCE(ac.category_code, 'OTHER') AS category_code,
|
||||
COALESCE(ac.category_name, '其他') AS category_name,
|
||||
COALESCE(ac.category_code, def.category_code, 'OTHER') AS category_code,
|
||||
COALESCE(ac.category_name, def.category_name, '其他') AS category_name,
|
||||
SUM(tfl.ledger_amount) AS income_amount,
|
||||
SUM(tfl.ledger_count) AS duration_seconds,
|
||||
COUNT(DISTINCT tfl.order_settle_id) AS order_count
|
||||
FROM dwd.dwd_table_fee_log tfl
|
||||
LEFT JOIN dwd.dim_table dt ON dt.table_id = tfl.site_table_id
|
||||
LEFT JOIN dws.cfg_area_category ac ON dt.site_table_area_name = ac.source_area_name
|
||||
WHERE tfl.is_delete = 0
|
||||
GROUP BY DATE(tfl.ledger_end_time), COALESCE(ac.category_code, 'OTHER'), COALESCE(ac.category_name, '其他');
|
||||
LEFT JOIN dwd.dim_table dt ON dt.table_id = tfl.site_table_id AND dt.scd2_is_current = 1
|
||||
LEFT JOIN dws.cfg_area_category ac
|
||||
ON ac.source_area_name = dt.site_table_area_name
|
||||
AND ac.source_table_name = dt.table_name
|
||||
AND ac.match_type = 'EXACT'
|
||||
LEFT JOIN dws.cfg_area_category def
|
||||
ON def.match_type = 'DEFAULT'
|
||||
WHERE COALESCE(tfl.is_delete, 0) = 0
|
||||
GROUP BY DATE(tfl.ledger_end_time),
|
||||
COALESCE(ac.category_code, def.category_code, 'OTHER'),
|
||||
COALESCE(ac.category_name, def.category_name, '其他');
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
Reference in New Issue
Block a user