This commit is contained in:
Neo
2026-03-15 10:15:02 +08:00
parent 2dd217522c
commit 72bb11b34f
916 changed files with 65306 additions and 16102803 deletions

View File

@@ -26,7 +26,7 @@ SCHEMA_ETL=meta
# API 配置(上游 SaaS API
# ------------------------------------------------------------------------------
API_BASE=https://pc.ficoo.vip/apiprod/admin/v1/
API_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnQtdHlwZSI6IjQiLCJ1c2VyLXR5cGUiOiIxIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiMTIiLCJyb2xlLWlkIjoiMTIiLCJ0ZW5hbnQtaWQiOiIyNzkwNjgzMTYwNzA5OTU3Iiwibmlja25hbWUiOiLnp5_miLfnrqHnkIblkZjvvJrmganmgakxIiwic2l0ZS1pZCI6IjAiLCJtb2JpbGUiOiIxMzgxMDUwMjMwNCIsInNpZCI6IjI5NTA0ODk2NTgzOTU4NDUiLCJzdGFmZi1pZCI6IjMwMDk5MTg2OTE1NTkwNDUiLCJvcmctaWQiOiIwIiwicm9sZS10eXBlIjoiMyIsInJlZnJlc2hUb2tlbiI6IlI5THQvRkVjSGZubkdiOTZJZ3lmdWhjaXU5WnIwREQrZFh1amhVY1RCSDQ9IiwicmVmcmVzaEV4cGlyeVRpbWUiOiIyMDI2LzMvMTEg5LiL5Y2INjo0MjozMSIsIm5lZWRDaGVja1Rva2VuIjoiZmFsc2UiLCJleHAiOjE3NzMyMjU3NTEsImlzcyI6InRlc3QiLCJhdWQiOiJVc2VyIn0.8H5V3W0NfGJrcYo9Ex-35D-SzxhC2tRaZGrgo2reYr4
API_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnQtdHlwZSI6IjQiLCJ1c2VyLXR5cGUiOiIxIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiMTIiLCJyb2xlLWlkIjoiMTIiLCJ0ZW5hbnQtaWQiOiIyNzkwNjgzMTYwNzA5OTU3Iiwibmlja25hbWUiOiLnp5_miLfnrqHnkIblkZjvvJrmganmgakxIiwic2l0ZS1pZCI6IjAiLCJtb2JpbGUiOiIxMzgxMDUwMjMwNCIsInNpZCI6IjI5NTA0ODk2NTgzOTU4NDUiLCJzdGFmZi1pZCI6IjMwMDk5MTg2OTE1NTkwNDUiLCJvcmctaWQiOiIwIiwicm9sZS10eXBlIjoiMyIsInJlZnJlc2hUb2tlbiI6IncxTkhzalRIeHU1b0Ric0hnQXp6SUgrU2Q2d2M3YndUTTU1ZTZnSXg0RTQ9IiwicmVmcmVzaEV4cGlyeVRpbWUiOiIyMDI2LzMvMTkg5LiK5Y2IMToxNzowMyIsIm5lZWRDaGVja1Rva2VuIjoiZmFsc2UiLCJleHAiOjE3NzM4NTQyMjMsImlzcyI6InRlc3QiLCJhdWQiOiJVc2VyIn0.E7oh3g_-3fyeC1-oHsJXZ-tTGqcvUCMDrd9TifJAs4U
API_TIMEOUT=20
API_PAGE_SIZE=200
API_RETRY_MAX=3

View File

@@ -58,10 +58,11 @@ class DatabaseConnection:
c.execute(sql, args)
return c.fetchall()
def execute(self, sql: str, args=None):
"""Execute a SQL statement without returning rows."""
def execute(self, sql: str, args=None) -> int:
"""Execute a SQL statement without returning rows. Returns rowcount."""
with self.conn.cursor() as c:
c.execute(sql, args)
return c.rowcount
def commit(self):
"""Commit current transaction."""

View File

@@ -119,9 +119,9 @@ class DatabaseOperations:
"""执行查询并返回结果"""
return self._connection.query(sql, args)
def execute(self, sql: str, args=None):
"""执行任意 SQL"""
self._connection.execute(sql, args)
def execute(self, sql: str, args=None) -> int:
"""执行任意 SQL,返回 rowcount"""
return self._connection.execute(sql, args)
def cursor(self):
"""暴露原生 cursor供特殊操作使用"""

View File

@@ -0,0 +1,88 @@
# BD_Manualdim_groupbuy_package_ex 新增团购详情字段
> 日期2026-03-05
> 涉及库:`etl_feiqiu` / `test_etl_feiqiu`
> 迁移脚本:`db/etl_feiqiu/migrations/2026-03-05__add_detail_fields_to_dim_groupbuy_package_ex.sql`
> 直接原因整合团购详情接口QueryPackageCouponInfo需在 DWD 扩展表中存储可用台区、助教服务、关联门店等维度信息
> Prompt 摘要etl-coupon-detail spec — 需求 4 验收标准 1
---
## 1. 变更说明
### 变更内容
`dwd.dim_groupbuy_package_ex` 表新增 4 个 JSONB 列,用于存储从团购详情接口提取的维度数据。
| Schema | 表 | 新增列 | 类型 | 说明 |
|--------|-----|--------|------|------|
| dwd | dim_groupbuy_package_ex | table_area_ids | JSONB | 可用台区 ID 列表(来自详情接口 tableAreaId |
| dwd | dim_groupbuy_package_ex | table_area_names | JSONB | 可用台区名称列表(来自详情接口 tableAreaNameList |
| dwd | dim_groupbuy_package_ex | assistant_services | JSONB | 助教服务关联(来自详情接口 packageCouponAssistants |
| dwd | dim_groupbuy_package_ex | groupon_site_infos | JSONB | 关联门店信息(来自详情接口 grouponSiteInfos |
所有列均为 NULLABLE使用 `ADD COLUMN IF NOT EXISTS` 确保幂等性。
### 数据来源
ODS 层 `ods.group_buy_package_details` 表(由 `ODS_GROUP_PACKAGE` 任务的详情拉取子流程写入),通过 `coupon_id = groupbuy_package_id` 关联后在 DWD 加载时合并。
---
## 2. 兼容性影响
| 组件 | 影响 | 说明 |
|------|------|------|
| ETL DWD 加载 | 需配合修改 | `dwd_load_task.py` 需新增 LEFT JOIN 逻辑从 ODS 详情表读取并映射到这 4 个字段Task 4.2/4.3 |
| ETL SCD2 | 自动兼容 | 新增 JSONB 字段自动纳入 `_is_row_changed` 变更检测 |
| 后端 API | 无影响 | 当前无接口直接查询 dim_groupbuy_package_ex 的详情字段 |
| 小程序 | 无影响 | 不直接使用 DWD 层表 |
| RLS 视图 | 无影响 | dim_groupbuy_package_ex 无 RLS 视图 |
---
## 3. 回滚策略
```sql
ALTER TABLE dwd.dim_groupbuy_package_ex
DROP COLUMN IF EXISTS table_area_ids,
DROP COLUMN IF EXISTS table_area_names,
DROP COLUMN IF EXISTS assistant_services,
DROP COLUMN IF EXISTS groupon_site_infos;
```
回滚后需同步撤销 `dwd_load_task.py` 中对应的 LEFT JOIN 和字段映射逻辑。
---
## 4. 验证 SQL
```sql
-- 1. 确认 4 个新列存在且类型正确
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'dwd'
AND table_name = 'dim_groupbuy_package_ex'
AND column_name IN ('table_area_ids', 'table_area_names', 'assistant_services', 'groupon_site_infos')
ORDER BY ordinal_position;
-- 预期4 行data_type 均为 'jsonb'is_nullable 均为 'YES'
-- 2. 确认列 COMMENT 已写入
SELECT a.attname AS column_name, d.description AS comment
FROM pg_catalog.pg_attribute a
JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
LEFT JOIN pg_catalog.pg_description d ON d.objoid = c.oid AND d.objsubid = a.attnum
WHERE n.nspname = 'dwd'
AND c.relname = 'dim_groupbuy_package_ex'
AND a.attname IN ('table_area_ids', 'table_area_names', 'assistant_services', 'groupon_site_infos')
ORDER BY a.attnum;
-- 预期4 行,每行 comment 非 NULL
-- 3. 确认表主键和索引未受影响
SELECT indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'dwd'
AND tablename = 'dim_groupbuy_package_ex';
-- 预期:原有 4 个索引不变pkey + 3 个辅助索引)
```

View File

@@ -29,10 +29,10 @@
| 10 | settle_type | INTEGER | YES | | 结账类型。**枚举值**: 1=台桌结账, 3=商城订单, 6=退货订单, 7=退款订单 |
| 11 | revoke_order_id | BIGINT | YES | | 撤销订单 ID当前数据全为 0 |
| 12 | member_id | BIGINT | YES | | 会员 ID → dim_member0=散客,占比约 82.8% |
| 13 | member_name | VARCHAR(100) | YES | | 会员名称 |
| 14 | member_phone | VARCHAR(50) | YES | | 会员电话 |
| 15 | member_card_account_id | BIGINT | YES | | 会员卡账户 ID(当前数据全为 0 |
| 16 | member_card_type_name | VARCHAR(100) | YES | | 卡类型名称(当前数据全为空) |
| 13 | member_name | VARCHAR(100) | YES | | 会员名称。⚠️ **DQ-6 断档**2025-12 起全为 NULL需通过 `member_id` JOIN `dim_member.nickname` 获取 |
| 14 | member_phone | VARCHAR(50) | YES | | 会员电话。⚠️ **DQ-6 断档**2025-12 起全为 NULL需通过 `member_id` JOIN `dim_member.mobile` 获取(`scd2_is_current=1` |
| 15 | member_card_account_id | BIGINT | YES | | 会员卡账户 ID。⚠️ **DQ-7 断档**:全为 0需通过 `member_id` JOIN `dim_member_card_account.tenant_member_id` 获取(`scd2_is_current=1` |
| 16 | member_card_type_name | VARCHAR(100) | YES | | 卡类型名称。⚠️ **DQ-7 断档**2025-07-21 起全为 NULL需通过 `member_id` JOIN `dim_member_card_account` 获取 |
| 17 | is_bind_member | BOOLEAN | YES | | 是否绑定会员。**枚举值**: False=否 |
| 18 | member_discount_amount | NUMERIC(18,2) | YES | | 会员折扣金额 |
| 19 | consume_money | NUMERIC(18,2) | YES | | 消费总金额(元)。⚠️ **口径不稳定**存在三种历史口径A/B/CDWS 层不应直接使用,应使用 `items_sum = table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money`。详见 [consume_money 口径](../../../../docs/reports/DWD-DOC/consume/consume-money-caliber.md) |

View File

@@ -0,0 +1,266 @@
# BD_Manualdws_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. 关联扩展字段说明
本次 Spec02-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`

View File

@@ -0,0 +1,80 @@
# BD_ManualDWS 库存汇总(日/周/月)
> 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 | 门店 IDPK 之一) | 飞球门店 ID |
| `tenant_id` | BIGINT | 租户 ID | 飞球租户 ID |
| `stat_date` | DATE | 统计日期PK 之一)。日度=当天日期,周度=ISO 周一日期,月度=月首日期 | 如 `2026-01-15``2026-01-13`(周一)、`2026-01-01`(月首) |
| `site_goods_id` | BIGINT | 门店商品 IDPK 之一),关联 `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`:取最晚记录的值
- loadupsert 写入目标表,主键冲突时更新
---
## 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`

View File

@@ -0,0 +1,251 @@
# BD_Manualdws_member_spending_power_indexSPI 消费力指数)
> 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=log1p2=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`

View File

@@ -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`(项目标签域章节)

View File

@@ -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_nameNULL表示区域级映射 |
| 3 | source_table_name | VARCHAR(100) | YES | UK | 源台桌名称来自dim_table.table_nameNULL仅用于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`
### 新增台桌
- KTV888、常乐、幸会(纯k)、虚拟188、大包、小包、纯k
- MAHJONG董事办、大包麻将房、666台桌
- BILLIARDVIP1-VIP3台桌级、TV、A1-A18、B1-B15、C1-C6
- SNOOKERS1-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-A1818台 |
| B区 | B1-B1515台 |
| C区 | C1-C66台 |
| VIP包厢 | VIP1, VIP2, VIP3 |
| TV台 | TV |
### SNOOKER 斯诺克5台
| 区域 | 台桌 |
|------|------|
| 斯诺克区 | S1, S2, S3, S4 |
| VIP包厢 | VIP5 |
### MAHJONG 🀄 麻将/棋牌11台
| 区域 | 台桌 |
|------|------|
| 麻将房 | 1, 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;
-- 期望: 7574 台桌 + 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
```

View File

@@ -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, '其他');
```
## 使用说明

View File

@@ -12,11 +12,14 @@ database/
│ └── mappings/ — API JSON → ODS 字段映射mapping_*.md
├── DWD/
│ ├── main/ — DWD 主表文档
── Ex/ — DWD 扩展表文档
── Ex/ — DWD 扩展表文档
│ └── changes/ — DWD 层变更记录
├── DWS/
── main/ — DWS 汇总表文档
── main/ — DWS 汇总表文档
│ └── changes/ — DWS 层变更记录
├── ETL_Admin/
│ └── main/ — meta schema 表文档
├── cross_layer/ — ODS→DWD 跨层字段映射(从 docs/database/ 迁入)
└── _archived/ — 过时的变更记录、DDL 对比报告、已删除表文档
```
@@ -27,12 +30,15 @@ database/
| 表级文档 | `BD_manual_{表名}.md` | 字段说明、主键、业务含义 |
| 扩展表文档 | `BD_manual_{表名}_ex.md` | SCD2 扩展字段、溢出字段 |
| 字段映射 | `mapping_{API端点}_{ODS表名}.md` | API JSON 字段 → ODS 列的映射关系 |
| 跨层映射 | `BD_Manual_{ODS表名}.md` | ODS → DWD 字段映射、装载方式、业务含义 |
## 与项目级文档的关系
| 内容 | 位置 | 说明 |
|------|------|------|
| DDL 基线 | `docs/database/ddl/` | 从数据库自动导出,按 schema 分文件 |
| ODS→DWD 字段映射 | `docs/database/BD_Manual_*.md` | 跨层映射ODS 表 → DWD 表) |
| 业务库 BD_Manual | `docs/database/BD_Manual_*.md` | zqyy_app 表结构、FDW、RLS 视图 |
| ODS→DWD 跨层映射 | 本目录 `cross_layer/BD_Manual_*.md` | ODS 表 → DWD 表字段映射 |
| 表级字段说明 | 本目录 `*/main/BD_manual_*.md` | 单表字段详情 |
| API→ODS 字段映射 | 本目录 `ODS/mappings/` | API JSON → ODS 列映射 |
| DWD/DWS 层变更记录 | 本目录 `DWD/changes/``DWS/changes/` | 加列、新表等变更说明 |

View File

@@ -0,0 +1,106 @@
# BD_Manualassistant_accounts_master助教账号档案
> ODS 表:`ods.assistant_accounts_master`
> DWD 表:`dwd.dim_assistant`(主表)、`dwd.dim_assistant_ex`(扩展表)
> API 接口:助教账号列表
> JSON 路径:`assistant_accounts_master.json → data.assistantInfos`
> 装载方式SCD2 维度合并(`DwdLoadTask`
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
---
## 1. dim_assistant主表
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `assistant_id` | BIGINT | `id` | FACT_MAPPINGS | 助教唯一标识PK 之一) | 飞球系统雪花 ID`2947562271297029` |
| `user_id` | BIGINT | `user_id` | FACT_MAPPINGS | 关联的系统用户 ID0 表示未绑定用户账号 | `0` 或飞球用户 ID |
| `assistant_no` | TEXT | `assistant_no` | 自动映射 | 助教编号(门店内序号),用于排班和展示 | 如 `31``1` |
| `real_name` | TEXT | `real_name` | 自动映射 | 助教真实姓名 | 如 `张静然` |
| `nickname` | TEXT | `nickname` | 自动映射 | 助教昵称,用于小程序端展示 | 如 `小然` |
| `mobile` | TEXT | `mobile` | 自动映射 | 助教手机号 | 11 位手机号 |
| `tenant_id` | BIGINT | `tenant_id` | 自动映射 | 所属租户 ID | 飞球租户 ID |
| `site_id` | BIGINT | `site_id` | 自动映射 | 所属门店 ID | 飞球门店 ID |
| `team_id` | BIGINT | `team_id` | 自动映射 | 所属团队 ID0 表示未分组 | `0` 或团队 ID |
| `team_name` | TEXT | `team_name` | 自动映射 | 团队名称 | 如 `1组`NULL 表示未分组 |
| `level` | INTEGER | `level` | 自动映射 | 助教等级(技能等级编号) | 如 `20`(对应"高级"等) |
| `entry_time` | TIMESTAMPTZ | `entry_time` | 自动映射 | 入职时间 | ISO 时间戳 |
| `resign_time` | TIMESTAMPTZ | `resign_time` | 自动映射 | 离职时间NULL 表示在职 | ISO 时间戳或 NULL |
| `leave_status` | INTEGER | `leave_status` | 自动映射 | 在职状态0=在职1=已离职 | `0` / `1` |
| `assistant_status` | INTEGER | `assistant_status` | 自动映射 | 助教状态1=正常2=停用 | `1` / `2` |
| `scd2_start_time` | TIMESTAMPTZ | — | DWD 元数据 | SCD2 版本生效起点 | — |
| `scd2_end_time` | TIMESTAMPTZ | — | DWD 元数据 | SCD2 版本失效时间,`9999-12-31` 表示当前版本 | — |
| `scd2_is_current` | INT | — | DWD 元数据 | 当前版本标记1=当前0=历史 | `0` / `1` |
| `scd2_version` | INT | — | DWD 元数据 | SCD2 版本号(自增) | — |
---
## 2. dim_assistant_ex扩展表
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `assistant_id` | BIGINT | `id` | FACT_MAPPINGS | 助教唯一标识PK 之一) | 同主表 |
| `gender` | INTEGER | `gender` | 自动映射 | 性别0=未设置 | `0` |
| `birth_date` | TIMESTAMPTZ | `birth_date` | 自动映射 | 出生日期 | `0001-01-01` 表示未设置 |
| `avatar` | TEXT | `avatar` | 自动映射 | 头像 URL | HTTPS 链接 |
| `introduce` | TEXT | `introduce` | FACT_MAPPINGS | 个人简介文本 | 自由文本或 NULL |
| `video_introduction_url` | TEXT | `video_introduction_url` | 自动映射 | 视频介绍 URL | HTTPS 链接或 NULL |
| `height` | NUMERIC(5,2) | `height` | 自动映射 | 身高cm0 表示未填写 | `0.00` 或实际身高 |
| `weight` | NUMERIC(5,2) | `weight` | 自动映射 | 体重kg0 表示未填写 | `0.00` 或实际体重 |
| `shop_name` | TEXT | `shop_name` | 自动映射 | 所属门店名称快照 | 如 `朗朗桌球` |
| `group_id` | BIGINT | `group_id` | 自动映射 | 分组 ID0 表示未分组 | `0` 或分组 ID |
| `group_name` | TEXT | `group_name` | FACT_MAPPINGS | 分组名称 | NULL 或分组名 |
| `person_org_id` | BIGINT | `person_org_id` | 自动映射 | 人事组织 ID | 飞球组织 ID |
| `staff_id` | BIGINT | `staff_id` | 自动映射 | 员工 ID0 表示未绑定员工档案 | `0` 或员工 ID |
| `staff_profile_id` | BIGINT | `staff_profile_id` | 自动映射 | 员工档案 ID0 表示无档案 | `0` 或档案 ID |
| `assistant_grade` | DOUBLE PRECISION | `assistant_grade` | 自动映射 | 助教评分(客户评价均分) | `0.0` ~ `5.0` |
| `sum_grade` | DOUBLE PRECISION | `sum_grade` | 自动映射 | 累计评分总和 | `0.0` 或累计值 |
| `get_grade_times` | INTEGER | `get_grade_times` | 自动映射 | 被评价次数 | `0` 或正整数 |
| `charge_way` | INTEGER | `charge_way` | 自动映射 | 计费方式2=按时长计费(当前门店全部为 2 | `2` |
| `allow_cx` | INTEGER | `allow_cx` | 自动映射 | 是否允许促销服务1=允许(当前全部为 1 | `1` |
| `is_guaranteed` | INTEGER | `is_guaranteed` | 自动映射 | 是否保底0=不保底1=保底 | `0` / `1` |
| `salary_grant_enabled` | INTEGER | `salary_grant_enabled` | 自动映射 | 工资发放开关2=启用 | `2` |
| `entry_type` | INTEGER | `entry_type` | 自动映射 | 入职类型1=正常入职 | `1` |
| `entry_sign_status` | INTEGER | `entry_sign_status` | 自动映射 | 入职签到状态0=未签到 | `0` |
| `resign_sign_status` | INTEGER | `resign_sign_status` | 自动映射 | 离职签到状态0=未签到 | `0` |
| `work_status` | INTEGER | `work_status` | 自动映射 | 工作状态1=在岗2=离岗 | `1` / `2` |
| `show_status` | INTEGER | `show_status` | 自动映射 | 展示状态1=展示 | `1` |
| `show_sort` | INTEGER | `show_sort` | 自动映射 | 展示排序序号 | 正整数 |
| `online_status` | INTEGER | `online_status` | 自动映射 | 在线状态1=在线 | `1` |
| `is_delete` | INTEGER | `is_delete` | 自动映射 | 软删除标记0=正常1=已删除 | `0` / `1` |
| `criticism_status` | INTEGER | `criticism_status` | 自动映射 | 差评处理状态1=正常 | `1` |
| `create_time` | TIMESTAMPTZ | `create_time` | 自动映射 | 助教记录创建时间 | ISO 时间戳 |
| `update_time` | TIMESTAMPTZ | `update_time` | 自动映射 | 助教记录最后更新时间 | ISO 时间戳 |
| `start_time` | TIMESTAMPTZ | `start_time` | 自动映射 | 合同/排班开始时间 | ISO 时间戳 |
| `end_time` | TIMESTAMPTZ | `end_time` | 自动映射 | 合同/排班结束时间 | ISO 时间戳 |
| `last_table_id` | BIGINT | `last_table_id` | 自动映射 | 最近服务的台桌 ID0 表示无 | `0` 或台桌 ID |
| `last_table_name` | TEXT | `last_table_name` | 自动映射 | 最近服务的台桌名称 | 如 `TV``1号台` |
| `last_update_name` | TEXT | `last_update_name` | 自动映射 | 最后操作人姓名(带职位前缀) | 如 `管理员:郑丽珊` |
| `order_trade_no` | BIGINT | `order_trade_no` | 自动映射 | 当前关联的订单交易号0 表示空闲 | `0` 或订单号 |
| `ding_talk_synced` | INTEGER | `ding_talk_synced` | 自动映射 | 钉钉同步状态1=已同步 | `1` |
| `site_light_cfg_id` | BIGINT | `site_light_cfg_id` | 自动映射 | 门店灯控配置 ID0 表示未配置 | `0` 或配置 ID |
| `light_equipment_id` | TEXT | `light_equipment_id` | FACT_MAPPINGS | 灯控设备 ID | NULL 或设备编号 |
| `light_status` | INTEGER | `light_status` | 自动映射 | 台灯状态2=已开灯 | `1` / `2` |
| `is_team_leader` | INTEGER | `is_team_leader` | 自动映射 | 是否组长0=否1=是 | `0` / `1` |
| `serial_number` | BIGINT | `serial_number` | 自动映射 | 序列号0 表示未分配 | `0` 或序列号 |
| `system_role_id` | BIGINT | `system_role_id` | FACT_MAPPINGS | 系统角色 ID标识助教在系统中的角色类型。当前门店全部为 10 | `10` |
| `job_num` | TEXT | `job_num` | FACT_MAPPINGS | 工号,助教的内部编号标识。当前门店未启用,全部为 NULL | NULL |
| `cx_unit_price` | NUMERIC(18,2) | `cx_unit_price` | FACT_MAPPINGS | 促销单价(元),助教提供促销服务时的计费单价。当前门店未在账号表层面设置,全部为 0.00 | `0.00` |
| `pd_unit_price` | NUMERIC(18,2) | `pd_unit_price` | FACT_MAPPINGS | 陪打单价(元),助教提供陪打服务时的计费单价。当前门店未在账号表层面设置,全部为 0.00 | `0.00` |
| `scd2_*` | — | — | DWD 元数据 | SCD2 慢变维度追踪字段(同主表) | — |
---
## 3. 跳过字段说明
| ODS 字段 | 跳过原因 |
|---------|---------|
| `siteprofile` | JSONB 嵌套列,已由 `dim_site` / `dim_site_ex` 通过 JSONB 提取映射 |
---
## 4. 代码引用
- FACT_MAPPINGS`dwd_load_task.py``FACT_MAPPINGS["dwd.dim_assistant"]` / `FACT_MAPPINGS["dwd.dim_assistant_ex"]`
- TABLE_MAP`"dwd.dim_assistant" → "ods.assistant_accounts_master"`
- DWS 下游:`dws_assistant_daily_task.py`(助教日业绩汇总)、`dws_salary_task.py`(工资计算)

View File

@@ -0,0 +1,114 @@
# BD_Manualassistant_service_records助教服务流水
> ODS 表:`ods.assistant_service_records`
> DWD 表:`dwd.dwd_assistant_service_log`(主表)、`dwd.dwd_assistant_service_log_ex`(扩展表)
> API 接口:助教服务记录列表
> JSON 路径:`assistant_service_records.json → data.orderAssistantLedgers`
> 装载方式:事实表增量插入(`DwdLoadTask`
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
---
## 1. dwd_assistant_service_log主表33 列)
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `assistant_service_id` | BIGINT | `id` | FACT_MAPPINGS | 助教服务记录唯一标识PK | 飞球雪花 ID |
| `order_trade_no` | BIGINT | `order_trade_no` | 自动映射 | 关联的订单交易号 | 飞球订单号 |
| `order_settle_id` | BIGINT | `order_settle_id` | 自动映射 | 关联的结算单 ID | 飞球结算单 ID |
| `order_pay_id` | BIGINT | `order_pay_id` | 自动映射 | 关联的支付单 ID | 飞球支付单 ID |
| `order_assistant_id` | BIGINT | `order_assistant_id` | 自动映射 | 订单级助教明细 ID标识本次服务在订单中的唯一记录 | 飞球雪花 ID |
| `order_assistant_type` | INTEGER | `order_assistant_type` | 自动映射 | 助教服务类型1=陪打2=促销 | `1` / `2` |
| `tenant_id` | BIGINT | `tenant_id` | 自动映射 | 租户 ID | 飞球租户 ID |
| `site_id` | BIGINT | `site_id` | 自动映射 | 门店 ID | 飞球门店 ID |
| `site_table_id` | BIGINT | `site_table_id` | 自动映射 | 服务台桌 ID关联 `dim_table.table_id` | 飞球台桌 ID |
| `tenant_member_id` | BIGINT | `tenant_member_id` | 自动映射 | 租户维度会员 ID | 飞球会员 ID 或 0 |
| `system_member_id` | BIGINT | `system_member_id` | 自动映射 | 系统维度会员 ID跨租户唯一 | 飞球会员 ID 或 0 |
| `assistant_no` | VARCHAR(64) | `assistantno` | FACT_MAPPINGS | 助教编号(门店内序号) | 如 `31` |
| `nickname` | VARCHAR(64) | `nickname` | 自动映射 | 助教昵称 | 如 `小张` |
| `site_assistant_id` | BIGINT | `site_assistant_id` | FACT_MAPPINGS | 门店维度助教档案 ID关联 `dim_assistant.assistant_id`。⚠️ 已于 2026-02-20 修正映射源(原错误映射自 `order_assistant_id` | 飞球助教 ID |
| `user_id` | BIGINT | `user_id` | 自动映射 | 助教用户 ID | 飞球用户 ID |
| `assistant_team_id` | BIGINT | `assistant_team_id` | 自动映射 | 助教团队 ID | 飞球团队 ID |
| `person_org_id` | BIGINT | `person_org_id` | 自动映射 | 人员组织 ID | 飞球组织 ID |
| `assistant_level` | INTEGER | `assistant_level` | 自动映射 | 助教等级编码 | 枚举值 |
| `level_name` | VARCHAR(64) | `levelname` | FACT_MAPPINGS | 助教等级名称 | 如 `高级``中级` |
| `skill_id` | BIGINT | `skill_id` | 自动映射 | 服务技能 ID | 飞球技能 ID |
| `skill_name` | VARCHAR(64) | `skillname` | FACT_MAPPINGS | 服务技能名称 | 如 `陪打``促销` |
| `ledger_unit_price` | NUMERIC(10,2) | `ledger_unit_price` | 自动映射 | 分账单价(元/小时) | 金额值 |
| `ledger_amount` | NUMERIC(10,2) | `ledger_amount` | 自动映射 | 分账总金额(元) | 金额值 |
| `projected_income` | NUMERIC(10,2) | `projected_income` | 自动映射 | 预计收入(元) | 金额值 |
| `coupon_deduct_money` | NUMERIC(10,2) | `coupon_deduct_money` | 自动映射 | 优惠券抵扣金额(元) | `0.00` ~ 金额值 |
| `income_seconds` | INTEGER | `income_seconds` | 自动映射 | 计费时长(秒) | 正整数 |
| `real_use_seconds` | INTEGER | `real_use_seconds` | 自动映射 | 实际使用时长(秒) | 正整数 |
| `add_clock` | INTEGER | `add_clock` | 自动映射 | 加钟次数 | `0` ~ 正整数 |
| `create_time` | TIMESTAMPTZ | `create_time` | 自动映射 | 记录创建时间 | ISO 时间戳 |
| `start_use_time` | TIMESTAMPTZ | `start_use_time` | 自动映射 | 服务开始时间 | ISO 时间戳 |
| `last_use_time` | TIMESTAMPTZ | `last_use_time` | 自动映射 | 最后使用时间(服务结束时间) | ISO 时间戳 |
| `is_delete` | INTEGER | `is_delete` | 自动映射 | 是否已删除0=正常1=已删除 | `0` / `1` |
| `real_service_money` | NUMERIC(18,2) | `real_service_money` | FACT_MAPPINGS | 实际服务金额(元),扣除折扣后的实收 | `0.00` ~ 金额值 |
---
## 2. dwd_assistant_service_log_ex扩展表33 列)
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `assistant_service_id` | BIGINT | `id` | FACT_MAPPINGS | 助教服务记录唯一标识PK | 同主表 |
| `table_name` | VARCHAR(64) | `tablename` | FACT_MAPPINGS | 台桌名称快照 | 如 `1号台` |
| `assistant_name` | VARCHAR(64) | `assistantname` | FACT_MAPPINGS | 助教姓名快照 | 如 `张静然` |
| `ledger_name` | VARCHAR(128) | `ledger_name` | 自动映射 | 分账项名称 | 如 `陪打费` |
| `ledger_group_name` | VARCHAR(128) | `ledger_group_name` | FACT_MAPPINGS | 分账组名称 | 分账组名或 NULL |
| `ledger_count` | INTEGER | `ledger_count` | 自动映射 | 分账数量 | 正整数 |
| `member_discount_amount` | NUMERIC(10,2) | `member_discount_amount` | 自动映射 | 会员折扣金额(元) | `0.00` ~ 金额值 |
| `manual_discount_amount` | NUMERIC(10,2) | `manual_discount_amount` | 自动映射 | 手动折扣金额(元) | `0.00` ~ 金额值 |
| `service_money` | NUMERIC(10,2) | `service_money` | 自动映射 | 服务原价金额(元) | 金额值 |
| `returns_clock` | INTEGER | `returns_clock` | 自动映射 | 退钟次数 | `0` ~ 正整数 |
| `ledger_start_time` | TIMESTAMPTZ | `ledger_start_time` | 自动映射 | 分账开始时间 | ISO 时间戳 |
| `ledger_end_time` | TIMESTAMPTZ | `ledger_end_time` | 自动映射 | 分账结束时间 | ISO 时间戳 |
| `ledger_status` | INTEGER | `ledger_status` | 自动映射 | 分账状态 | 枚举值 |
| `is_confirm` | INTEGER | `is_confirm` | 自动映射 | 是否已确认0=未确认1=已确认 | `0` / `1` |
| `is_single_order` | INTEGER | `is_single_order` | 自动映射 | 是否单独订单 | `0` / `1` |
| `is_not_responding` | INTEGER | `is_not_responding` | 自动映射 | 是否未响应 | `0` / `1` |
| `is_trash` | INTEGER | `is_trash` | 自动映射 | 是否已废除0=正常1=已废除 | `0` / `1` |
| `trash_applicant_id` | BIGINT | `trash_applicant_id` | 自动映射 | 废除申请人 ID | 员工 ID 或 NULL |
| `trash_applicant_name` | VARCHAR(64) | `trash_applicant_name` | FACT_MAPPINGS | 废除申请人姓名 | 姓名或 NULL |
| `trash_reason` | VARCHAR(255) | `trash_reason` | FACT_MAPPINGS | 废除原因 | 自由文本或 NULL |
| `salesman_user_id` | BIGINT | `salesman_user_id` | 自动映射 | 销售员用户 ID | 用户 ID 或 NULL |
| `salesman_name` | VARCHAR(64) | `salesman_name` | FACT_MAPPINGS | 销售员姓名 | 姓名或 NULL |
| `salesman_org_id` | BIGINT | `salesman_org_id` | 自动映射 | 销售员组织 ID | 组织 ID 或 NULL |
| `skill_grade` | INTEGER | `skill_grade` | 自动映射 | 技能评分 | 评分值 |
| `service_grade` | INTEGER | `service_grade` | 自动映射 | 服务评分 | 评分值 |
| `composite_grade` | NUMERIC(5,2) | `composite_grade` | 自动映射 | 综合评分 | 如 `4.50` |
| `sum_grade` | NUMERIC(10,2) | `sum_grade` | 自动映射 | 累计评分 | 累计值 |
| `get_grade_times` | INTEGER | `get_grade_times` | 自动映射 | 获评次数 | 正整数 |
| `grade_status` | INTEGER | `grade_status` | 自动映射 | 评分状态 | 枚举值 |
| `composite_grade_time` | TIMESTAMPTZ | `composite_grade_time` | 自动映射 | 综合评分时间 | ISO 时间戳 |
| `assistant_team_name` | TEXT | `assistantteamname` | FACT_MAPPINGS | 助教团队名称快照 | 如 `1组` |
| `operator_id` | BIGINT | `operator_id` | FACT_MAPPINGS | 操作员 ID录入/结算这条助教服务的员工。0 表示系统自动 | `0` 或员工 ID |
| `operator_name` | TEXT | `operator_name` | FACT_MAPPINGS | 操作员姓名(带职位前缀),便于直接阅读 | 如 `收银员:郑丽珊`NULL 表示系统自动 |
---
## 3. 映射修正记录
| 日期 | 字段 | 修正内容 |
|------|------|---------|
| 2026-02-20 | `site_assistant_id` | ODS 源从 `order_assistant_id`(订单级 ID修正为 `site_assistant_id`(助教档案 ID |
| 2026-02-26 | (下游)`table_area_name` | DWS 任务 `_extract_service_records()` 原错误引用 `asl.table_area_name`(本表无此列),改为 JOIN `dwd.dim_table``site_table_area_name`。详见 `BD_Manual_fix_dws_assistant_daily_table_area.md` |
---
## 4. 跳过字段说明
| ODS 字段 | 跳过原因 |
|---------|---------|
| `siteprofile` | JSONB 嵌套列,已由 `dim_site` / `dim_site_ex` 通过 JSONB 提取映射 |
---
## 5. 代码引用
- FACT_MAPPINGS`dwd_load_task.py``FACT_MAPPINGS["dwd.dwd_assistant_service_log"]` / `FACT_MAPPINGS["dwd.dwd_assistant_service_log_ex"]`
- TABLE_MAP`"dwd.dwd_assistant_service_log" → "ods.assistant_service_records"`
- DWS 下游:`dws_assistant_daily_task.py`(助教日业绩汇总)
- 迁移脚本:`db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/migrations/2026-02-20__fix_assistant_service_site_assistant_id.sql`(已归档)、`db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/migrations/2026-02-20__add_assistant_service_log_ex_fields.sql`(已归档)

View File

@@ -0,0 +1,62 @@
# BD_Manualgoods_stock_movements库存变动流水
> ODS 表:`ods.goods_stock_movements`
> DWD 表:`dwd.dwd_goods_stock_movement`(事实表,无 ex 扩展表)
> API 接口:库存变动记录列表
> JSON 路径:`goods_stock_movements.json → data.goodsStockList`
> 装载方式:事实表按 `create_time` 增量加载(`DwdLoadTask`
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
---
## 1. dwd_goods_stock_movement
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `site_goods_stock_id` | BIGINT | `sitegoodsstockid`ODS 驼峰 → PG 小写) | FACT_MAPPINGS (cast bigint) | 库存变动记录唯一标识PK | 飞球雪花 ID |
| `tenant_id` | BIGINT | `tenantid` | FACT_MAPPINGS (cast bigint) | 租户 ID | 飞球租户 ID |
| `site_id` | BIGINT | `siteid` | FACT_MAPPINGS (cast bigint) | 门店 ID | 飞球门店 ID |
| `site_goods_id` | BIGINT | `sitegoodsid` | FACT_MAPPINGS (cast bigint) | 门店商品 ID关联 `dim_store_goods.site_goods_id` | 飞球商品 ID |
| `goods_name` | TEXT | `goodsname` | FACT_MAPPINGS | 商品名称 | 如 `百威啤酒` |
| `goods_category_id` | BIGINT | `goodscategoryid` | FACT_MAPPINGS (cast bigint) | 一级商品分类 ID | 飞球分类 ID |
| `goods_second_category_id` | BIGINT | `goodssecondcategoryid` | FACT_MAPPINGS (cast bigint) | 二级商品分类 ID | 飞球分类 ID |
| `unit` | TEXT | `unit` | FACT_MAPPINGS | 计量单位 | 如 `瓶``包``张` |
| `price` | NUMERIC(18,4) | `price` | FACT_MAPPINGS (cast numeric) | 商品单价(元) | 金额值 |
| `stock_type` | INTEGER | `stocktype` | FACT_MAPPINGS (cast integer) | 库存变动类型枚举(详见下方枚举表) | `1`/`2`/`4`/`7`/`8`/`9` |
| `change_num` | NUMERIC(18,4) | `changenum` | FACT_MAPPINGS (cast numeric) | 变动数量(正数为增加,负数为减少) | 正/负数值 |
| `start_num` | NUMERIC(18,4) | `startnum` | FACT_MAPPINGS (cast numeric) | 变动前库存量 | `0.0000` ~ 正数 |
| `end_num` | NUMERIC(18,4) | `endnum` | FACT_MAPPINGS (cast numeric) | 变动后库存量 | `0.0000` ~ 正数 |
| `change_num_a` | NUMERIC(18,4) | `changenuma` | FACT_MAPPINGS (cast numeric) | 辅助单位变动量(用于双单位商品,如"箱→瓶"换算) | 数值或 0 |
| `start_num_a` | NUMERIC(18,4) | `startnuma` | FACT_MAPPINGS (cast numeric) | 辅助单位变动前库存 | 数值或 0 |
| `end_num_a` | NUMERIC(18,4) | `endnuma` | FACT_MAPPINGS (cast numeric) | 辅助单位变动后库存 | 数值或 0 |
| `remark` | TEXT | `remark` | FACT_MAPPINGS | 备注说明 | 如 `结账退货``采购退货``系统自动领用`,或 NULL |
| `operator_name` | TEXT | `operatorname` | FACT_MAPPINGS | 操作人姓名 | 姓名或 NULL |
| `create_time` | TIMESTAMPTZ | `createtime` | FACT_MAPPINGS (cast timestamptz) | 变动发生时间 | ISO 时间戳 |
| `fetched_at` | TIMESTAMPTZ | `fetched_at` | 自动映射 | ETL 采集时间戳 | ISO 时间戳 |
---
## 2. stock_type 枚举值详解
| stock_type | 含义 | 典型 remark | 数据量 |
|-----------|------|------------|--------|
| 1 | 销售出库 | NULL系统自动扣减 | 29,573 条 |
| 2 | 采购入库 | NULL | 1,033 条 |
| 4 | 退货入库 | `结账退货` | 3,300 条 |
| 7 | 采购退货(出库) | `采购退货` | 33 条 |
| 8 | 领用出库 | `系统自动领用` | 1,033 条 |
| 9 | 领用退回(入库) | `系统自动领用退回` | 33 条 |
---
## 3. ODS 列名映射说明
ODS DDL 中列名使用驼峰式(如 `siteGoodsStockId`PostgreSQL 在无引号时自动小写化。FACT_MAPPINGS 中使用带引号的 ODS 列名以正确引用。部分列(`unit``price``remark`)在 ODS 中已是小写,无需引号。
---
## 4. 代码引用
- FACT_MAPPINGS`dwd_load_task.py``FACT_MAPPINGS["dwd.dwd_goods_stock_movement"]`
- TABLE_MAP`"dwd.dwd_goods_stock_movement" → "ods.goods_stock_movements"`
- DWS 下游:暂无直接 DWS 汇总(库存汇总基于 `dwd_goods_stock_summary`

View File

@@ -0,0 +1,62 @@
# BD_Manualgoods_stock_summary库存汇总
> ODS 表:`ods.goods_stock_summary`
> DWD 表:`dwd.dwd_goods_stock_summary`(事实表,无 ex 扩展表)
> API 接口:库存汇总查询(支持 `startTime`/`endTime` 时间窗口参数)
> JSON 路径:`goods_stock_summary.json → data.goodsStockSummaryList`
> 装载方式:事实表按时间窗口增量加载(`DwdLoadTask`
> ODS 配置:`requires_window=True``time_fields=("startTime", "endTime")`
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
---
## 1. dwd_goods_stock_summary
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `site_goods_id` | BIGINT | `sitegoodsid`ODS DDL 驼峰 → PG 小写) | FACT_MAPPINGS (cast bigint) | 门店商品 IDPK 之一),关联 `dim_store_goods.site_goods_id` | 飞球商品 ID`3028609051954117` |
| `goods_name` | TEXT | `goodsname` | FACT_MAPPINGS | 商品名称 | 如 `酱香爆珠槟榔``百威啤酒` |
| `goods_unit` | TEXT | `goodsunit` | FACT_MAPPINGS | 计量单位 | 如 `包``瓶``张``罐` |
| `goods_category_id` | BIGINT | `goodscategoryid` | FACT_MAPPINGS (cast bigint) | 一级商品分类 ID | 飞球分类 ID |
| `goods_category_second_id` | BIGINT | `goodscategorysecondid` | FACT_MAPPINGS (cast bigint) | 二级商品分类 ID | 飞球分类 ID |
| `category_name` | TEXT | `categoryname` | FACT_MAPPINGS | 一级分类名称 | 如 `槟榔``酒水``其他` |
| `range_start_stock` | NUMERIC(18,4) | `rangestartstock` | FACT_MAPPINGS (cast numeric) | 期初库存(查询时间窗口起点的库存量) | 如 `100.0000``0.0000` |
| `range_end_stock` | NUMERIC(18,4) | `rangeendstock` | FACT_MAPPINGS (cast numeric) | 期末库存(查询时间窗口终点的库存量) | 如 `100.0000``0.0000` |
| `range_in` | NUMERIC(18,4) | `rangein` | FACT_MAPPINGS (cast numeric) | 入库数量(时间窗口内的采购/调拨入库总量) | `0.0000` ~ 正数 |
| `range_out` | NUMERIC(18,4) | `rangeout` | FACT_MAPPINGS (cast numeric) | 出库数量(时间窗口内的调拨出库/报损总量) | `0.0000` ~ 正数 |
| `range_sale` | NUMERIC(18,4) | `rangesale` | FACT_MAPPINGS (cast numeric) | 销售数量(时间窗口内的销售出库总量) | `0.0000` ~ 正数 |
| `range_sale_money` | NUMERIC(18,2) | `rangesalemoney` | FACT_MAPPINGS (cast numeric) | 销售金额(元),时间窗口内的销售总金额 | `0.00` ~ 金额值 |
| `range_inventory` | NUMERIC(18,4) | `rangeinventory` | FACT_MAPPINGS (cast numeric) | 盘点调整量(时间窗口内的盘盈/盘亏净量) | 正数(盘盈)或负数(盘亏) |
| `current_stock` | NUMERIC(18,4) | `currentstock` | FACT_MAPPINGS (cast numeric) | 当前库存API 返回时的实时库存量) | `0.0000` ~ 正数 |
| `site_id` | BIGINT | `site_id` | 自动映射 | 门店 IDETL 注入) | 飞球门店 ID |
| `tenant_id` | BIGINT | `tenant_id` | 自动映射 | 租户 IDETL 注入) | 飞球租户 ID |
| `fetched_at` | TIMESTAMPTZ | `fetched_at` | 自动映射 | ETL 采集时间戳PK 之一),标识本次快照的采集时间 | ISO 时间戳 |
---
## 2. 主键说明
主键为 `(site_goods_id, fetched_at)` 复合键。同一商品在不同采集时间窗口会产生多条记录,每条记录代表该时间窗口内的库存汇总快照。
---
## 3. ODS 列名映射说明
ODS DDL 中列名使用驼峰式(如 `siteGoodsId`),但 PostgreSQL 在无引号时自动小写化为 `sitegoodsid`。FACT_MAPPINGS 中使用带引号的 ODS 列名(如 `"siteGoodsId"`)以正确引用。
---
## 4. DWS 下游
- `dws.dws_goods_stock_daily_summary`:日度库存汇总
- `dws.dws_goods_stock_weekly_summary`周度库存汇总ISO 周)
- `dws.dws_goods_stock_monthly_summary`:月度库存汇总
---
## 5. 代码引用
- FACT_MAPPINGS`dwd_load_task.py``FACT_MAPPINGS["dwd.dwd_goods_stock_summary"]`
- TABLE_MAP`"dwd.dwd_goods_stock_summary" → "ods.goods_stock_summary"`
- ODS 任务配置:`tasks/ods/ods_tasks.py``OdsTaskSpec("ODS_GOODS_STOCK_SUMMARY", requires_window=True, time_fields=("startTime", "endTime"))`
- DWS 任务:`tasks/dws/goods_stock_daily_task.py``goods_stock_weekly_task.py``goods_stock_monthly_task.py`

View File

@@ -0,0 +1,102 @@
# BD Manual: goodsStockWarningInfo库存预警信息
## 变更概述
- 日期2026-02-24
- 触发:一致性检查报告发现 API 独有嵌套字段 `goodsStockWarningInfo` 未映射到 ODS/DWD
- 迁移脚本:`db/etl_feiqiu/migrations/2026-02-24__add_goods_stock_warning_info.sql`
## 字段来源
API 端点 `/TenantGoods/GetGoodsInventoryList` 返回的 `store_goods_master` 记录中包含嵌套对象:
```json
{
"goodsStockWarningInfo": {
"tenant_goods_id": 0, // 冗余,已有同名顶层字段
"site_goods_id": 0, // 冗余,对应顶层 id
"sales_day": 0.0, // → warning_sales_day
"warning_day_max": 0, // → warning_day_max
"warning_day_min": 0 // → warning_day_min
}
}
```
仅提取 3 个有效字段,冗余 ID 不重复收录。
## 新增字段
| 层 | 表 | 列名 | 类型 | 说明 |
|---|---|---|---|---|
| ODS | `ods.store_goods_master` | `warning_sales_day` | NUMERIC(18,2) | 库存预警参考的日均销量 |
| ODS | `ods.store_goods_master` | `warning_day_max` | INTEGER | 预警天数上限 |
| ODS | `ods.store_goods_master` | `warning_day_min` | INTEGER | 预警天数下限 |
| DWD | `dwd.dim_store_goods_ex` | `warning_sales_day` | NUMERIC(18,2) | 同 ODS直接映射 |
| DWD | `dwd.dim_store_goods_ex` | `warning_day_max` | INTEGER | 同 ODS直接映射 |
| DWD | `dwd.dim_store_goods_ex` | `warning_day_min` | INTEGER | 同 ODS直接映射 |
## 数据流
```
API goodsStockWarningInfo (嵌套 JSON)
↓ _merge_record_layers 扁平化_STOCK_WARNING_FIELD_MAP
ODS ods.store_goods_master (warning_sales_day / warning_day_max / warning_day_min)
↓ DWD FACT_MAPPINGS 直接映射
DWD dwd.dim_store_goods_ex (同名列)
```
## 代码变更
| 文件 | 变更 |
|---|---|
| `apps/etl/connectors/feiqiu/tasks/ods/ods_tasks.py` | `_merge_record_layers` 增加 `goodsStockWarningInfo` 扁平化逻辑 |
| `apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py` | `FACT_MAPPINGS["dwd.dim_store_goods_ex"]` 增加 3 个映射 |
| `db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/schemas/ods.sql` | baseline 同步 |
| `db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/schemas/dwd.sql` | baseline 同步 |
## 兼容性
- 后端 API无影响后端不直接读取 ODS/DWD 层这些字段)
- 小程序:无影响
- ETLODS schema-aware 插入自动识别新列DWD 通过 FACT_MAPPINGS 映射
- 管理后台:如需展示库存预警信息,可从 `dwd.dim_store_goods_ex` 读取
## 回滚策略
```sql
ALTER TABLE ods.store_goods_master
DROP COLUMN IF EXISTS warning_sales_day,
DROP COLUMN IF EXISTS warning_day_max,
DROP COLUMN IF EXISTS warning_day_min;
ALTER TABLE dwd.dim_store_goods_ex
DROP COLUMN IF EXISTS warning_sales_day,
DROP COLUMN IF EXISTS warning_day_max,
DROP COLUMN IF EXISTS warning_day_min;
```
## 验证 SQL
```sql
-- 1. 确认 ODS 新列存在
SELECT column_name, data_type FROM information_schema.columns
WHERE table_schema = 'ods' AND table_name = 'store_goods_master'
AND column_name IN ('warning_sales_day', 'warning_day_max', 'warning_day_min')
ORDER BY column_name;
-- 预期3 行
-- 2. 确认 DWD 新列存在
SELECT column_name, data_type FROM information_schema.columns
WHERE table_schema = 'dwd' AND table_name = 'dim_store_goods_ex'
AND column_name IN ('warning_sales_day', 'warning_day_max', 'warning_day_min')
ORDER BY column_name;
-- 预期3 行
-- 3. 确认注释已设置
SELECT c.column_name, pgd.description
FROM information_schema.columns c
JOIN pg_catalog.pg_statio_all_tables st ON st.schemaname = c.table_schema AND st.relname = c.table_name
JOIN pg_catalog.pg_description pgd ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
WHERE c.table_schema = 'ods' AND c.table_name = 'store_goods_master'
AND c.column_name LIKE 'warning_%';
-- 预期3 行description 非空
```

View File

@@ -0,0 +1,92 @@
# BD_Manualods.group_buy_package_details 团购套餐详情表
> 日期2026-03-05
> 涉及库:`etl_feiqiu` / `test_etl_feiqiu`
> DDL 路径:`db/etl_feiqiu/ods/group_buy_package_details.sql`
> 直接原因整合团购详情接口QueryPackageCouponInfo新建 ODS 详情表存储每个团购套餐的详情数据
> Prompt 摘要etl-coupon-detail spec — 需求 3 验收标准 1-4
---
## 1. 变更说明
### 变更内容
新建 `ods.group_buy_package_details` 表,用于存储 `QueryPackageCouponInfo` 详情接口的原始数据。
| Schema | 表 | 操作 | 说明 |
|--------|-----|------|------|
| ods | group_buy_package_details | 新建 | 团购套餐详情,主键 `coupon_id`,含 12 个结构化字段 + 6 个 JSONB 数组字段 + 3 个 ETL 元数据字段 |
### 数据获取方式
通过 `ODS_GROUP_PACKAGE` 任务的 `detail_endpoint` 二级详情拉取子流程:
- 主流程拉取团购列表 → `ods.group_buy_packages`
- 子流程遍历每个 `id`,串行调用 `QueryPackageCouponInfo` → 本表
- 全量快照模式UPSERT on `coupon_id`
### 关键字段
| 字段 | 类型 | 说明 |
|------|------|------|
| `coupon_id` | BIGINT PK | 团购套餐 ID= group_buy_packages.id |
| `table_area_ids` | JSONB | 可用台区 ID 列表 |
| `table_area_names` | JSONB | 可用台区名称列表 |
| `assistant_services` | JSONB | 助教服务关联数组 |
| `groupon_site_infos` | JSONB | 关联门店信息数组 |
| `package_services` | JSONB | 套餐服务数组(待调研) |
| `coupon_details_list` | JSONB | 券明细数组(待调研) |
| `content_hash` | TEXT | 内容哈希,用于变更检测 |
| `payload` | JSONB | 完整原始 JSON 响应 |
---
## 2. 兼容性影响
| 组件 | 影响 | 说明 |
|------|------|------|
| ETL ODS 层 | 新增表 | `ODS_GROUP_PACKAGE` 任务通过 `detail_endpoint` 配置自动写入 |
| ETL DWD 层 | 需配合修改 | `dwd_load_task.py` 需 LEFT JOIN 本表将 4 个 JSONB 字段合并到 `dim_groupbuy_package_ex` |
| 后端 API | 无影响 | 当前无接口直接查询本表 |
| 小程序 | 无影响 | 不直接使用 ODS 层表 |
---
## 3. 回滚策略
```sql
DROP TABLE IF EXISTS ods.group_buy_package_details;
```
回滚后需同步移除 `ODS_GROUP_PACKAGE` 任务中的 `detail_endpoint` 相关配置。
---
## 4. 验证 SQL
```sql
-- 1. 确认表存在且主键正确
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'ods'
AND table_name = 'group_buy_package_details'
ORDER BY ordinal_position;
-- 预期22 列coupon_id 为 BIGINT NOT NULL
-- 2. 确认主键约束
SELECT constraint_name, constraint_type
FROM information_schema.table_constraints
WHERE table_schema = 'ods'
AND table_name = 'group_buy_package_details'
AND constraint_type = 'PRIMARY KEY';
-- 预期1 行pk_group_buy_package_details
-- 3. 确认 JSONB 列存在
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'ods'
AND table_name = 'group_buy_package_details'
AND data_type = 'jsonb'
ORDER BY ordinal_position;
-- 预期8 行table_area_ids, table_area_names, assistant_services, groupon_site_infos, package_services, coupon_details_list, payload
```

View File

@@ -0,0 +1,74 @@
# BD_Manualmember_balance_changes会员余额变动
> ODS 表:`ods.member_balance_changes`
> DWD 表:`dwd.dwd_member_balance_change`(主表)、`dwd.dwd_member_balance_change_ex`(扩展表)
> API 接口:会员余额变动记录列表
> JSON 路径:`member_balance_changes.json → data.memberAccountChanges`
> 装载方式:事实表增量插入(`DwdLoadTask`
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
---
## 1. dwd_member_balance_change主表22 列)
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `balance_change_id` | BIGINT | `id` | FACT_MAPPINGS | 余额变动记录唯一标识PK | 飞球雪花 ID |
| `tenant_id` | BIGINT | `tenant_id` | 自动映射 | 租户 ID | 飞球租户 ID |
| `site_id` | BIGINT | `site_id` | 自动映射 | 门店 ID | 飞球门店 ID |
| `register_site_id` | BIGINT | `register_site_id` | 自动映射 | 会员注册门店 ID | 飞球门店 ID |
| `tenant_member_id` | BIGINT | `tenant_member_id` | 自动映射 | 租户维度会员 ID | 飞球会员 ID |
| `system_member_id` | BIGINT | `system_member_id` | 自动映射 | 系统维度会员 ID跨租户唯一 | 飞球会员 ID |
| `tenant_member_card_id` | BIGINT | `tenant_member_card_id` | 自动映射 | 会员卡 ID | 飞球会员卡 ID |
| `card_type_id` | BIGINT | `card_type_id` | 自动映射 | 会员卡类型 ID | 飞球卡类型 ID |
| `card_type_name` | VARCHAR(32) | `membercardtypename` | FACT_MAPPINGS | 会员卡类型名称快照 | 如 `普通会员卡` |
| `member_name` | VARCHAR(64) | `membername` | FACT_MAPPINGS | 会员姓名快照 | 姓名 |
| `member_mobile` | VARCHAR(20) | `membermobile` | FACT_MAPPINGS | 会员手机号快照 | 11 位手机号 |
| `balance_before` | NUMERIC(18,2) | `before` | FACT_MAPPINGS | 变动前余额(元) | 金额值 |
| `change_amount` | NUMERIC(18,2) | `account_data` | FACT_MAPPINGS | 变动金额(元),正数为充入,负数为扣减 | 正/负金额 |
| `balance_after` | NUMERIC(18,2) | `after` | FACT_MAPPINGS | 变动后余额(元) | 金额值 |
| `from_type` | INTEGER | `from_type` | 自动映射 | 变动来源类型1=结算扣款2=充值3=退款返还4=系统调整7=转账9=其他 | `1`/`2`/`3`/`4`/`7`/`9` |
| `payment_method` | INTEGER | `payment_method` | 自动映射 | 支付方式 | 枚举值 |
| `change_time` | TIMESTAMPTZ | `create_time` | FACT_MAPPINGS | 变动发生时间 | ISO 时间戳 |
| `is_delete` | INTEGER | `is_delete` | 自动映射 | 是否已删除0=正常1=已删除 | `0` / `1` |
| `remark` | VARCHAR(255) | `remark` | 自动映射 | 备注说明 | 自由文本或 NULL |
| `principal_before` | NUMERIC(18,2) | `principal_before` | FACT_MAPPINGS | 变动前本金余额(元),不含赠送金 | 金额值 |
| `principal_after` | NUMERIC(18,2) | `principal_after` | FACT_MAPPINGS | 变动后本金余额(元) | 金额值 |
| `principal_change_amount` | NUMERIC(18,2) | *计算列* | FACT_MAPPINGS | 本金变动金额(元),= `principal_after - principal_before` | 正/负金额 |
---
## 2. dwd_member_balance_change_ex扩展表8 列)
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `balance_change_id` | BIGINT | `id` | FACT_MAPPINGS | 余额变动记录唯一标识PK | 同主表 |
| `pay_site_name` | VARCHAR(64) | `paysitename` | FACT_MAPPINGS | 支付门店名称快照 | 如 `朗朗桌球` |
| `register_site_name` | VARCHAR(64) | `registersitename` | FACT_MAPPINGS | 注册门店名称快照 | 如 `朗朗桌球` |
| `refund_amount` | NUMERIC(18,2) | `refund_amount` | 自动映射 | 退款金额(元) | `0.00` ~ 金额值 |
| `operator_id` | BIGINT | `operator_id` | 自动映射 | 操作员 ID执行本次余额变动的员工。0 表示系统自动 | `0` 或员工 ID |
| `operator_name` | VARCHAR(64) | `operator_name` | 自动映射 | 操作员姓名 | 姓名或 NULL |
| `principal_data` | TEXT | `principal_data` | FACT_MAPPINGS | 本金变动金额(元),正数为充入本金,负数为扣减本金 | 正/负金额 |
| `relate_id` | BIGINT | `relate_id` | FACT_MAPPINGS | 关联业务单据 ID指向触发本次余额变动的业务记录。按 `from_type` 不同指向不同表1→结算单 ID2→充值单 ID3→退款单 ID7→转账单 ID。`from_type=4`(系统调整)和 `9`(其他)时为 0 | `0` 或业务单据 ID |
---
## 3. from_type 枚举值详解
| from_type | 含义 | relate_id 指向 | 数据量 |
|-----------|------|---------------|--------|
| 1 | 结算扣款(消费) | 结算单 ID`dwd_settlement_head.order_settle_id` | 5637 条 |
| 2 | 充值 | 充值单 ID`dwd_recharge_order.recharge_order_id` | 110 条 |
| 3 | 退款返还 | 退款单 ID`dwd_refund.refund_id` | 650 条 |
| 4 | 系统调整 | 0无关联单据 | 775 条 |
| 7 | 转账 | 转账单 ID | 15 条 |
| 9 | 其他 | 0无关联单据 | 179 条 |
---
## 4. 代码引用
- FACT_MAPPINGS`dwd_load_task.py``FACT_MAPPINGS["dwd.dwd_member_balance_change"]` / `FACT_MAPPINGS["dwd.dwd_member_balance_change_ex"]`
- TABLE_MAP`"dwd.dwd_member_balance_change" → "ods.member_balance_changes"`
- DWS 下游:`dws_member_analysis_task.py`(会员消费分析)
- 迁移脚本:`db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/migrations/2026-02-20__add_member_balance_change_ex_relate_id.sql`(已归档)

View File

@@ -0,0 +1,103 @@
# BD_Manualrecharge_settlements充值结算
> ODS 表:`ods.recharge_settlements`
> DWD 表:`dwd.dwd_recharge_order`(主表)、`dwd.dwd_recharge_order_ex`(扩展表)
> API 接口:充值结算记录列表
> JSON 路径:`recharge_settlements.json → data.orderSettles`
> 装载方式:事实表增量插入(`DwdLoadTask`
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
---
## 1. dwd_recharge_order主表
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `recharge_order_id` | BIGINT | `id` | FACT_MAPPINGS | 充值结算记录唯一标识PK | 飞球雪花 ID |
| `tenant_id` | BIGINT | `tenantid` | FACT_MAPPINGS | 租户 ID | 飞球租户 ID |
| `site_id` | BIGINT | `siteid` | FACT_MAPPINGS | 门店 ID | 飞球门店 ID |
| `member_id` | BIGINT | `memberid` | FACT_MAPPINGS | 会员 ID | 飞球会员 ID |
| `member_name_snapshot` | TEXT | `membername` | FACT_MAPPINGS | 会员姓名快照 | 姓名 |
| `member_phone_snapshot` | TEXT | `memberphone` | FACT_MAPPINGS | 会员手机号快照 | 11 位手机号 |
| `tenant_member_card_id` | BIGINT | `tenantmembercardid` | FACT_MAPPINGS | 会员卡 ID | 飞球会员卡 ID |
| `member_card_type_name` | TEXT | `membercardtypename` | FACT_MAPPINGS | 会员卡类型名称 | 如 `普通会员卡` |
| `settle_relate_id` | BIGINT | `settlerelateid` | FACT_MAPPINGS | 关联结算单 ID | 飞球结算 ID |
| `settle_type` | INTEGER | `settletype` | FACT_MAPPINGS | 结算类型枚举 | 枚举值 |
| `settle_name` | TEXT | `settlename` | FACT_MAPPINGS | 结算类型名称 | 如 `充值` |
| `is_first` | INTEGER | `isfirst` | FACT_MAPPINGS | 是否首次充值0=否1=是 | `0` / `1` |
| `pay_amount` | NUMERIC | `payamount` | FACT_MAPPINGS | 实付金额(元) | 金额值 |
| `refund_amount` | NUMERIC | `refundamount` | FACT_MAPPINGS | 退款金额(元) | `0.00` ~ 金额值 |
| `point_amount` | NUMERIC | `pointamount` | FACT_MAPPINGS | 积分抵扣金额(元) | `0.00` ~ 金额值 |
| `cash_amount` | NUMERIC | `cashamount` | FACT_MAPPINGS | 现金支付金额(元) | `0.00` ~ 金额值 |
| `payment_method` | TEXT | `paymentmethod` | FACT_MAPPINGS | 支付方式 | 如 `微信``支付宝` |
| `create_time` | TIMESTAMPTZ | `createtime` | FACT_MAPPINGS | 充值记录创建时间 | ISO 时间戳 |
| `pay_time` | TIMESTAMPTZ | `paytime` | FACT_MAPPINGS | 支付完成时间 | ISO 时间戳 |
| `pl_coupon_sale_amount` | NUMERIC | `plcouponsaleamount` | FACT_MAPPINGS | 平台券销售金额(元)。当前门店业务未启用,全部为 0 | `0.00` |
| `mervou_sales_amount` | NUMERIC | `mervousalesamount` | FACT_MAPPINGS | 储值券销售金额(元)。当前门店业务未启用,全部为 0 | `0.00` |
| `electricity_money` | NUMERIC | `electricitymoney` | FACT_MAPPINGS | 电费金额(元)。当前门店业务未启用,全部为 0 | `0.00` |
| `real_electricity_money` | NUMERIC | `realelectricitymoney` | FACT_MAPPINGS | 实际电费金额(元),扣除调整后的电费。当前门店业务未启用,全部为 0 | `0.00` |
| `electricity_adjust_money` | NUMERIC | `electricityadjustmoney` | FACT_MAPPINGS | 电费调整金额(元),电费手动调整的差额。当前门店业务未启用,全部为 0 | `0.00` |
---
## 2. dwd_recharge_order_ex扩展表
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `recharge_order_id` | BIGINT | `id` | FACT_MAPPINGS | 充值结算记录唯一标识PK | 同主表 |
| `site_name_snapshot` | TEXT | `sitename` | FACT_MAPPINGS | 门店名称快照 | 如 `朗朗桌球` |
| `settle_status` | INTEGER | `settlestatus` | FACT_MAPPINGS | 结算状态枚举 | 枚举值 |
| `is_bind_member` | BOOLEAN | `isbindmember` | FACT_MAPPINGS (cast boolean) | 是否绑定会员 | `true` / `false` |
| `is_activity` | BOOLEAN | `isactivity` | FACT_MAPPINGS (cast boolean) | 是否参与活动 | `true` / `false` |
| `is_use_coupon` | BOOLEAN | `isusecoupon` | FACT_MAPPINGS (cast boolean) | 是否使用优惠券 | `true` / `false` |
| `is_use_discount` | BOOLEAN | `isusediscount` | FACT_MAPPINGS (cast boolean) | 是否使用折扣 | `true` / `false` |
| `can_be_revoked` | BOOLEAN | `canberevoked` | FACT_MAPPINGS (cast boolean) | 是否可撤销 | `true` / `false` |
| `online_amount` | NUMERIC | `onlineamount` | FACT_MAPPINGS | 线上支付金额(元) | 金额值 |
| `balance_amount` | NUMERIC | `balanceamount` | FACT_MAPPINGS | 余额支付金额(元) | 金额值 |
| `card_amount` | NUMERIC | `cardamount` | FACT_MAPPINGS | 银行卡支付金额(元) | 金额值 |
| `coupon_amount` | NUMERIC | `couponamount` | FACT_MAPPINGS | 优惠券抵扣金额(元) | 金额值 |
| `recharge_card_amount` | NUMERIC | `rechargecardamount` | FACT_MAPPINGS | 充值卡支付金额(元) | 金额值 |
| `gift_card_amount` | NUMERIC | `giftcardamount` | FACT_MAPPINGS | 赠送卡支付金额(元) | 金额值 |
| `prepay_money` | NUMERIC | `prepaymoney` | FACT_MAPPINGS | 预付金额(元) | 金额值 |
| `consume_money` | NUMERIC | `consumemoney` | FACT_MAPPINGS | 消费总金额(元) | 金额值 |
| `goods_money` | NUMERIC | `goodsmoney` | FACT_MAPPINGS | 商品金额(元) | 金额值 |
| `real_goods_money` | NUMERIC | `realgoodsmoney` | FACT_MAPPINGS | 实收商品金额(元) | 金额值 |
| `table_charge_money` | NUMERIC | `tablechargemoney` | FACT_MAPPINGS | 台费金额(元) | 金额值 |
| `service_money` | NUMERIC | `servicemoney` | FACT_MAPPINGS | 服务费金额(元) | 金额值 |
| `activity_discount` | NUMERIC | `activitydiscount` | FACT_MAPPINGS | 活动折扣金额(元) | 金额值 |
| `all_coupon_discount` | NUMERIC | `allcoupondiscount` | FACT_MAPPINGS | 全部优惠券折扣金额(元) | 金额值 |
| `goods_promotion_money` | NUMERIC | `goodspromotionmoney` | FACT_MAPPINGS | 商品促销金额(元) | 金额值 |
| `assistant_promotion_money` | NUMERIC | `assistantpromotionmoney` | FACT_MAPPINGS | 助教促销金额(元) | 金额值 |
| `assistant_pd_money` | NUMERIC | `assistantpdmoney` | FACT_MAPPINGS | 助教陪打金额(元) | 金额值 |
| `assistant_cx_money` | NUMERIC | `assistantcxmoney` | FACT_MAPPINGS | 助教促销服务金额(元) | 金额值 |
| `assistant_manual_discount` | NUMERIC | `assistantmanualdiscount` | FACT_MAPPINGS | 助教手动折扣金额(元) | 金额值 |
| `coupon_sale_amount` | NUMERIC | `couponsaleamount` | FACT_MAPPINGS | 券销售金额(元) | 金额值 |
| `member_discount_amount` | NUMERIC | `memberdiscountamount` | FACT_MAPPINGS | 会员折扣金额(元) | 金额值 |
| `point_discount_price` | NUMERIC | `pointdiscountprice` | FACT_MAPPINGS | 积分折扣价格(元) | 金额值 |
| `point_discount_cost` | NUMERIC | `pointdiscountcost` | FACT_MAPPINGS | 积分折扣成本(元) | 金额值 |
| `adjust_amount` | NUMERIC | `adjustamount` | FACT_MAPPINGS | 调整金额(元) | 金额值 |
| `rounding_amount` | NUMERIC | `roundingamount` | FACT_MAPPINGS | 抹零金额(元) | 金额值 |
| `operator_id` | BIGINT | `operatorid` | FACT_MAPPINGS | 操作员 ID | 员工 ID |
| `operator_name_snapshot` | TEXT | `operatorname` | FACT_MAPPINGS | 操作员姓名快照 | 如 `郑丽珊` |
| `salesman_user_id` | BIGINT | `salesmanuserid` | FACT_MAPPINGS | 销售员用户 ID | 用户 ID |
| `salesman_name` | TEXT | `salesmanname` | FACT_MAPPINGS | 销售员姓名 | 姓名或 NULL |
| `order_remark` | TEXT | `orderremark` | FACT_MAPPINGS | 订单备注 | 自由文本或 NULL |
| `table_id` | BIGINT | `tableid` | FACT_MAPPINGS | 台桌 ID | 飞球台桌 ID |
| `serial_number` | INTEGER | `serialnumber` | FACT_MAPPINGS | 流水号 | 正整数 |
| `revoke_order_id` | BIGINT | `revokeorderid` | FACT_MAPPINGS | 撤销关联的订单 ID | 订单 ID 或 0 |
| `revoke_order_name` | TEXT | `revokeordername` | FACT_MAPPINGS | 撤销关联的订单名称 | 名称或 NULL |
| `revoke_time` | TIMESTAMPTZ | `revoketime` | FACT_MAPPINGS | 撤销时间 | ISO 时间戳或 NULL |
---
## 3. B 类表说明
本表属于 B 类(仅补 FACT_MAPPINGS5 个电费/券字段的 DWD 列在 DDL 中已存在,但之前缺少 FACT_MAPPINGS 条目导致数据未从 ODS 流入 DWD。2026-02-20 补充映射后,这 5 个字段的数据可正常流转。当前门店这 5 个字段的 ODS/DWD 数据全部为 0业务未启用电费和券销售功能
---
## 4. 代码引用
- FACT_MAPPINGS`dwd_load_task.py``FACT_MAPPINGS["dwd.dwd_recharge_order"]` / `FACT_MAPPINGS["dwd.dwd_recharge_order_ex"]`
- TABLE_MAP`"dwd.dwd_recharge_order" → "ods.recharge_settlements"`
- DWS 下游:`dws_finance_daily_task.py`(财务日报,充值汇总)

View File

@@ -0,0 +1,61 @@
# BD_Manualsite_tables_master台桌维表
> ODS 表:`ods.site_tables_master`
> DWD 表:`dwd.dim_table`(主表)、`dwd.dim_table_ex`(扩展表)
> API 接口:门店台桌列表
> JSON 路径:`site_tables_master.json → data.siteTables`
> 装载方式SCD2 维度合并(`DwdLoadTask`
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
---
## 1. dim_table主表
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `table_id` | BIGINT | `id` | FACT_MAPPINGS | 台桌唯一标识PK 之一) | 飞球雪花 ID |
| `site_id` | BIGINT | `site_id` | 自动映射 | 门店 ID | 飞球门店 ID |
| `table_name` | TEXT | `table_name` | 自动映射 | 台桌名称 | 如 `1号台``VIP1` |
| `site_table_area_id` | BIGINT | `site_table_area_id` | 自动映射 | 门店台桌区域 ID | 飞球区域 ID |
| `site_table_area_name` | TEXT | `areaname` | FACT_MAPPINGS | 台桌区域名称 | 如 `大厅``VIP区` |
| `tenant_table_area_id` | BIGINT | `site_table_area_id` | FACT_MAPPINGS | 租户级台桌区域 ID | 飞球区域 ID |
| `table_price` | NUMERIC | `table_price` | 自动映射 | 台费单价(元/小时) | 金额值 |
| `order_id` | BIGINT | `order_id` | FACT_MAPPINGS | 当前关联的订单 ID0 表示空闲 | `0` 或订单 ID |
| `scd2_*` | — | — | DWD 元数据 | SCD2 慢变维度追踪字段 | — |
---
## 2. dim_table_ex扩展表
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `table_id` | BIGINT | `id` | FACT_MAPPINGS | 台桌唯一标识PK 之一) | 同主表 |
| `show_status` | INTEGER | `show_status` | 自动映射 | 展示状态 | 枚举值 |
| `is_online_reservation` | INTEGER | `is_online_reservation` | 自动映射 | 是否支持在线预约0=否1=是 | `0` / `1` |
| `table_cloth_use_time` | INTEGER | `table_cloth_use_time` | FACT_MAPPINGS | 台布使用时间(累计使用次数或时长) | 整数 |
| `table_cloth_use_cycle` | INTEGER | `table_cloth_use_cycle` | 自动映射 | 台布更换周期 | 整数 |
| `table_status` | INTEGER | `table_status` | 自动映射 | 台桌状态枚举 | 枚举值 |
| `create_time` | TIMESTAMPTZ | `create_time` | FACT_MAPPINGS | 台桌配置的创建时间或最近一次创建/复制时间 | ISO 时间戳 |
| `light_status` | INTEGER | `light_status` | FACT_MAPPINGS | 台灯状态1=已关灯2=已开灯 | `1` / `2` |
| `tablestatusname` | TEXT | `tablestatusname` | FACT_MAPPINGS | 台桌状态中文名称(如"空闲中""使用中"仅展示用途。ODS 列 `tableStatusName` 在 PG 中小写化 | 如 `空闲中``使用中` |
| `sitename` | TEXT | `sitename` | FACT_MAPPINGS | 门店名称快照冗余字段。ODS 列 `siteName` 在 PG 中小写化 | 如 `朗朗桌球` |
| `applet_qr_code_url` | TEXT | `"appletQrCodeUrl"` | FACT_MAPPINGS | 小程序二维码 URL用于扫码开台等场景。ODS 列用双引号保留驼峰大小写 | HTTPS 链接或 NULL |
| `audit_status` | INTEGER | `audit_status` | FACT_MAPPINGS | 审核状态2=已审核通过(当前全部为 2 | `2` |
| `charge_free` | INTEGER | `charge_free` | FACT_MAPPINGS | 是否免费台0=收费1=免费(当前全部为 0 | `0` / `1` |
| `delay_lights_time` | INTEGER | `delay_lights_time` | FACT_MAPPINGS | 台灯熄灭延迟时间(秒),结账后延时关灯的秒数 | 正整数 |
| `is_rest_area` | INTEGER | `is_rest_area` | FACT_MAPPINGS | 是否休息区台桌0=否1=是(当前全部为 0 | `0` / `1` |
| `only_allow_groupon` | INTEGER | `only_allow_groupon` | FACT_MAPPINGS | 是否仅允许团购开台0=不限制1=仅团购2=不限制(当前全部为 2 | `0` / `1` / `2` |
| `order_delay_time` | INTEGER | `order_delay_time` | FACT_MAPPINGS | 订单自动延时时长(秒),超时未结账自动延长的时间 | 正整数 |
| `self_table` | INTEGER | `self_table` | FACT_MAPPINGS | 是否自有台桌1=自有(当前全部为 1 | `1` |
| `temporary_light_second` | INTEGER | `temporary_light_second` | FACT_MAPPINGS | 临时开灯秒数,临时开灯持续的时间 | 正整数 |
| `virtual_table` | INTEGER | `virtual_table` | FACT_MAPPINGS | 是否虚拟台桌0=实体台1=虚拟台(当前全部为 0 | `0` / `1` |
| `scd2_*` | — | — | DWD 元数据 | SCD2 慢变维度追踪字段 | — |
---
## 3. 代码引用
- FACT_MAPPINGS`dwd_load_task.py``FACT_MAPPINGS["dwd.dim_table"]` / `FACT_MAPPINGS["dwd.dim_table_ex"]`
- TABLE_MAP`"dwd.dim_table" → "ods.site_tables_master"`
- DWS 下游:`dws_finance_daily_task.py`(财务日报,台费汇总按区域分组)
- 迁移脚本:`db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/migrations/2026-02-20__add_dim_table_ex_fields.sql`(已归档)

View File

@@ -0,0 +1,103 @@
# BD_Manualstore_goods_master门店商品档案
> ODS 表:`ods.store_goods_master`
> DWD 表:`dwd.dim_store_goods`(主表)、`dwd.dim_store_goods_ex`(扩展表)
> API 接口:门店商品列表
> JSON 路径:`store_goods_master.json → data.orderGoodsList`
> 装载方式SCD2 维度合并(`DwdLoadTask`
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
---
## 1. dim_store_goods主表
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `site_goods_id` | BIGINT | `id` | FACT_MAPPINGS | 门店商品唯一标识PK 之一) | 飞球雪花 ID |
| `tenant_id` | BIGINT | `tenant_id` | 自动映射 | 租户 ID | 飞球租户 ID |
| `site_id` | BIGINT | `site_id` | 自动映射 | 门店 ID | 飞球门店 ID |
| `tenant_goods_id` | BIGINT | `tenant_goods_id` | 自动映射 | 租户商品 ID关联 `dim_tenant_goods` | 飞球商品 ID |
| `goods_name` | TEXT | `goods_name` | 自动映射 | 商品名称 | 如 `百威啤酒` |
| `goods_category_id` | BIGINT | `goods_category_id` | 自动映射 | 一级分类 ID | 飞球分类 ID |
| `goods_second_category_id` | BIGINT | `goods_second_category_id` | 自动映射 | 二级分类 ID | 飞球分类 ID |
| `category_level1_name` | TEXT | `onecategoryname` | FACT_MAPPINGS | 一级分类名称 | 如 `酒水` |
| `category_level2_name` | TEXT | `twocategoryname` | FACT_MAPPINGS | 二级分类名称 | 如 `啤酒` |
| `batch_stock_qty` | INTEGER | `batch_stock_quantity` | FACT_MAPPINGS | 批次库存数量(按批次管理的库存)。⚠️ 2026-02-20 修正映射源(原错误映射自 `stock`,即当前库存) | 数值 |
| `sale_qty` | INTEGER | `sale_num` | FACT_MAPPINGS | 累计销售数量 | 数值 |
| `total_sales_qty` | INTEGER | `total_sales` | FACT_MAPPINGS | 累计销售总额 | 数值 |
| `sale_price` | NUMERIC(18,2) | `sale_price` | 自动映射 | 商品售价(元) | 金额值 |
| `created_at` | TIMESTAMPTZ | `create_time` | FACT_MAPPINGS | 商品创建时间 | ISO 时间戳 |
| `updated_at` | TIMESTAMPTZ | `update_time` | FACT_MAPPINGS | 商品最后更新时间 | ISO 时间戳 |
| `avg_monthly_sales` | NUMERIC(18,4) | `average_monthly_sales` | FACT_MAPPINGS | 月均销量 | 数值 |
| `goods_state` | INTEGER | `goods_state` | 自动映射 | 商品状态 | 枚举值 |
| `enable_status` | INTEGER | `enable_status` | 自动映射 | 启用状态 | 枚举值 |
| `send_state` | INTEGER | `send_state` | 自动映射 | 配送状态 | 枚举值 |
| `is_delete` | INTEGER | `is_delete` | 自动映射 | 是否已删除0=正常1=已删除 | `0` / `1` |
| `commodity_code` | TEXT | `commodity_code` | FACT_MAPPINGS | 商品编码 | 编码字符串 |
| `not_sale` | INTEGER | `not_sale` | FACT_MAPPINGS | 是否停售0=在售1=停售 | `0` / `1` |
| `scd2_*` | — | — | DWD 元数据 | SCD2 慢变维度追踪字段 | — |
---
## 2. dim_store_goods_ex扩展表
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `site_goods_id` | BIGINT | `id` | FACT_MAPPINGS | 门店商品唯一标识PK 之一) | 同主表 |
| `site_name` | TEXT | `sitename` | FACT_MAPPINGS | 门店名称快照 | 如 `朗朗桌球` |
| `unit` | TEXT | `unit` | 自动映射 | 计量单位 | 如 `瓶``包` |
| `goods_barcode` | TEXT | `goods_bar_code` | FACT_MAPPINGS | 商品条码 | 条码字符串或 NULL |
| `goods_cover_url` | TEXT | `goods_cover` | FACT_MAPPINGS | 商品封面图 URL | HTTPS 链接或 NULL |
| `pinyin_initial` | TEXT | `pinyin_initial` | 自动映射 | 拼音首字母(用于搜索) | 如 `BWPJ` |
| `stock_qty` | INTEGER | `stock` | FACT_MAPPINGS | 当前库存数量(实时库存) | 数值 |
| `stock_secondary_qty` | INTEGER | `stock_a` | FACT_MAPPINGS | 辅助单位库存数量(双单位商品) | 数值 |
| `safety_stock_qty` | INTEGER | `safe_stock` | FACT_MAPPINGS | 安全库存数量(低于此值触发预警) | 数值 |
| `cost_price` | NUMERIC(18,4) | `cost_price` | 自动映射 | 成本价(元) | 金额值 |
| `cost_price_type` | INTEGER | `cost_price_type` | 自动映射 | 成本价类型 | 枚举值 |
| `provisional_total_cost` | NUMERIC(18,2) | `provisional_total_cost` | FACT_MAPPINGS | 暂估总成本(元),按暂估价计算的库存成本。⚠️ 2026-02-20 修正映射源(原错误映射自 `total_purchase_cost`,即实际采购成本) | 金额值 |
| `total_purchase_cost` | NUMERIC(18,2) | `total_purchase_cost` | 自动映射 | 实际采购总成本(元) | 金额值 |
| `min_discount_price` | NUMERIC(18,2) | `min_discount_price` | 自动映射 | 最低折扣价(元) | 金额值 |
| `is_discountable` | INTEGER | `able_discount` | FACT_MAPPINGS | 是否可打折0=不可1=可 | `0` / `1` |
| `days_on_shelf` | INTEGER | `days_available` | FACT_MAPPINGS | 上架天数 | 正整数 |
| `audit_status` | INTEGER | `audit_status` | 自动映射 | 审核状态 | 枚举值 |
| `sale_channel` | INTEGER | `sale_channel` | 自动映射 | 销售渠道 | 枚举值 |
| `is_warehousing` | INTEGER | `is_warehousing` | 自动映射 | 是否入库管理0=否1=是 | `0` / `1` |
| `freeze_status` | INTEGER | `freeze` | FACT_MAPPINGS | 冻结状态0=正常1=冻结 | `0` / `1` |
| `forbid_sell_status` | INTEGER | `forbid_sell_status` | 自动映射 | 禁售状态 | 枚举值 |
| `able_site_transfer` | INTEGER | `able_site_transfer` | 自动映射 | 是否允许门店间调拨0=否1=是 | `0` / `1` |
| `custom_label_type` | INTEGER | `custom_label_type` | 自动映射 | 自定义标签类型 | 枚举值 |
| `option_required` | INTEGER | `option_required` | 自动映射 | 是否必选规格0=否1=是 | `0` / `1` |
| `remark` | TEXT | `remark` | FACT_MAPPINGS | 商品备注 | 自由文本或 NULL |
| `sort_order` | INTEGER | `sort` | FACT_MAPPINGS | 排序序号 | 正整数 |
| `batch_stock_quantity` | NUMERIC | `batch_stock_quantity` | 自动映射 | 批次库存数量(冗余,与主表 `batch_stock_qty` 同源) | 数值 |
| `time_slot_sale` | INTEGER | `time_slot_sale` | FACT_MAPPINGS | 分时段销售标记(当前观测全部为 2。2026-02-21 新增 | `2` |
| `scd2_*` | — | — | DWD 元数据 | SCD2 慢变维度追踪字段 | — |
---
## 3. 映射修正记录
| 日期 | 字段 | 修正内容 |
|------|------|---------|
| 2026-02-20 | `batch_stock_qty` | ODS 源从 `stock`(当前库存)修正为 `batch_stock_quantity`(批次库存)。验证:仅 7.3% 行两列值相等 |
| 2026-02-20 | `provisional_total_cost` | ODS 源从 `total_purchase_cost`(实际采购成本)修正为 `provisional_total_cost`暂估成本。验证93.5% 行相等但 113 行不同 |
| 2026-02-21 | `time_slot_sale` | 新增字段。ODS `ods.store_goods_master` + DWD `dwd.dim_store_goods_ex` 同步新增 `time_slot_sale INTEGER`。API 返回值当前全部为 `2` |
---
## 4. 跳过字段说明
| ODS 字段 | 跳过原因 |
|---------|---------|
| ~~`time_slot_sale`~~ | ~~ODS 列不存在~~ → 2026-02-21 已新增,见映射修正记录 |
| `goodsStockWarningInfo` | JSONB 嵌套对象,展开不在本次范围内 |
---
## 5. 代码引用
- FACT_MAPPINGS`dwd_load_task.py``FACT_MAPPINGS["dwd.dim_store_goods"]` / `FACT_MAPPINGS["dwd.dim_store_goods_ex"]`
- TABLE_MAP`"dwd.dim_store_goods" → "ods.store_goods_master"`
- DWS 下游:`dws_finance_daily_task.py`(财务日报,商品维度关联)
- 迁移脚本:`db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/migrations/2026-02-20__fix_store_goods_master_mapping.sql`(已归档)
- 迁移脚本:`db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/migrations/2026-02-21__add_time_slot_sale_merge_commodity_code.sql`(已归档)

View File

@@ -0,0 +1,93 @@
# BD_Manualstore_goods_sales_records门店商品销售流水
> ODS 表:`ods.store_goods_sales_records`
> DWD 表:`dwd.dwd_store_goods_sale`(主表)、`dwd.dwd_store_goods_sale_ex`(扩展表)
> API 接口:门店商品销售记录列表
> JSON 路径:`store_goods_sales_records.json → data.orderGoodsLedgers`
> 装载方式:事实表增量插入(`DwdLoadTask`
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
---
## 1. dwd_store_goods_sale主表25 列)
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `store_goods_sale_id` | BIGINT | `id` | FACT_MAPPINGS | 销售记录唯一标识PK | 飞球雪花 ID |
| `order_trade_no` | BIGINT | `order_trade_no` | 自动映射 | 关联的订单交易号 | 飞球订单号 |
| `order_settle_id` | BIGINT | `order_settle_id` | 自动映射 | 关联的结算单 ID | 飞球结算单 ID |
| `order_pay_id` | BIGINT | `order_pay_id` | 自动映射 | 关联的支付单 ID | 飞球支付单 ID |
| `order_goods_id` | BIGINT | `order_goods_id` | 自动映射 | 订单商品明细 ID | 飞球雪花 ID |
| `site_id` | BIGINT | `site_id` | 自动映射 | 门店 ID | 飞球门店 ID |
| `tenant_id` | BIGINT | `tenant_id` | 自动映射 | 租户 ID | 飞球租户 ID |
| `site_goods_id` | BIGINT | `site_goods_id` | 自动映射 | 门店商品 ID关联 `dim_store_goods` | 飞球商品 ID |
| `tenant_goods_id` | BIGINT | `tenant_goods_id` | 自动映射 | 租户商品 ID关联 `dim_tenant_goods` | 飞球商品 ID |
| `tenant_goods_category_id` | BIGINT | `tenant_goods_category_id` | 自动映射 | 商品分类 ID | 飞球分类 ID |
| `tenant_goods_business_id` | BIGINT | `tenant_goods_business_id` | 自动映射 | 商品业务类型 ID | 飞球业务 ID |
| `site_table_id` | BIGINT | `site_table_id` | 自动映射 | 消费台桌 ID关联 `dim_table.table_id` | 飞球台桌 ID |
| `ledger_name` | VARCHAR(200) | `ledger_name` | 自动映射 | 分账项名称(商品名称快照) | 如 `百威啤酒` |
| `ledger_group_name` | VARCHAR(100) | `ledger_group_name` | 自动映射 | 分账组名称 | 分账组名或 NULL |
| `ledger_unit_price` | NUMERIC(18,2) | `ledger_unit_price` | 自动映射 | 分账单价(元/单位) | 金额值 |
| `ledger_count` | INTEGER | `ledger_count` | 自动映射 | 分账数量(销售数量) | 正整数 |
| `ledger_amount` | NUMERIC(18,2) | `ledger_amount` | 自动映射 | 分账总金额(元),= 单价 × 数量 | 金额值 |
| `discount_money` | NUMERIC(18,2) | `discount_money` | FACT_MAPPINGS | 折扣金额(元),会员折扣减免的金额。⚠️ 2026-02-20 由原 `discount_price` 重命名而来,修正列名误导 | `0.00` ~ 金额值 |
| `real_goods_money` | NUMERIC(18,2) | `real_goods_money` | 自动映射 | 实收商品金额(元),扣除折扣后 | 金额值 |
| `cost_money` | NUMERIC(18,2) | `cost_money` | 自动映射 | 成本金额(元) | 金额值 |
| `ledger_status` | INTEGER | `ledger_status` | 自动映射 | 分账状态 | 枚举值 |
| `is_delete` | INTEGER | `is_delete` | 自动映射 | 是否已删除0=正常1=已删除 | `0` / `1` |
| `create_time` | TIMESTAMPTZ | `create_time` | 自动映射 | 销售记录创建时间 | ISO 时间戳 |
| `coupon_share_money` | NUMERIC(18,2) | `coupon_share_money` | FACT_MAPPINGS | 优惠券分摊金额(元),该商品分摊的优惠券减免 | `0.00` ~ 金额值 |
| `discount_price` | NUMERIC(18,2) | `discount_price` | FACT_MAPPINGS | 折后单价(元),会员折扣后的商品单价。⚠️ 2026-02-20 新增,映射自 ODS 真正的 `discount_price` | `0.00` ~ 金额值 |
---
## 2. dwd_store_goods_sale_ex扩展表28 列)
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `store_goods_sale_id` | BIGINT | `id` | FACT_MAPPINGS | 销售记录唯一标识PK | 同主表 |
| `legacy_order_goods_id` | BIGINT | `ordergoodsid` | FACT_MAPPINGS | 旧版订单商品 ID兼容字段 | 飞球 ID |
| `site_name` | TEXT | `sitename` | FACT_MAPPINGS | 门店名称快照 | 如 `朗朗桌球` |
| `legacy_site_id` | BIGINT | `siteid` | FACT_MAPPINGS | 旧版门店 ID兼容字段 | 飞球门店 ID |
| `goods_remark` | TEXT | `goods_remark` | 自动映射 | 商品备注 | 自由文本或 NULL |
| `option_value_name` | TEXT | `option_value_name` | FACT_MAPPINGS | 商品规格选项名称 | 如 `大杯`、NULL |
| `operator_name` | TEXT | `operator_name` | 自动映射 | 操作员姓名 | 姓名或 NULL |
| `open_salesman_flag` | INTEGER | `opensalesman` | FACT_MAPPINGS (cast integer) | 是否开启销售员提成0=否1=是 | `0` / `1` |
| `salesman_user_id` | BIGINT | `salesman_user_id` | 自动映射 | 销售员用户 ID | 用户 ID 或 NULL |
| `salesman_name` | TEXT | `salesman_name` | FACT_MAPPINGS | 销售员姓名 | 姓名或 NULL |
| `salesman_role_id` | BIGINT | `salesman_role_id` | 自动映射 | 销售员角色 ID | 角色 ID 或 NULL |
| `salesman_org_id` | BIGINT | `sales_man_org_id` | FACT_MAPPINGS | 销售员组织 ID | 组织 ID 或 NULL |
| `discount_money` | NUMERIC(18,2) | `discount_money` | 自动映射 | 折扣金额(元),扩展表中的折扣明细 | `0.00` ~ 金额值 |
| `returns_number` | INTEGER | `returns_number` | 自动映射 | 退货数量 | `0` ~ 正整数 |
| `coupon_deduct_money` | NUMERIC(18,2) | `coupon_deduct_money` | 自动映射 | 优惠券抵扣金额(元) | `0.00` ~ 金额值 |
| `member_discount_amount` | NUMERIC(18,2) | `member_discount_amount` | 自动映射 | 会员折扣金额(元) | `0.00` ~ 金额值 |
| `point_discount_money` | NUMERIC(18,2) | `point_discount_money` | 自动映射 | 积分抵扣金额(元) | `0.00` ~ 金额值 |
| `point_discount_money_cost` | NUMERIC(18,2) | `point_discount_money_cost` | 自动映射 | 积分抵扣成本(元) | `0.00` ~ 金额值 |
| `package_coupon_id` | BIGINT | `package_coupon_id` | 自动映射 | 套餐券 ID | 券 ID 或 NULL |
| `order_coupon_id` | BIGINT | `order_coupon_id` | 自动映射 | 订单券 ID | 券 ID 或 NULL |
| `member_coupon_id` | BIGINT | `member_coupon_id` | 自动映射 | 会员券 ID | 券 ID 或 NULL |
| `option_price` | NUMERIC(18,2) | `option_price` | 自动映射 | 规格选项加价(元) | `0.00` ~ 金额值 |
| `option_member_discount_money` | NUMERIC(18,2) | `option_member_discount_money` | 自动映射 | 规格选项会员折扣金额(元) | `0.00` ~ 金额值 |
| `option_coupon_deduct_money` | NUMERIC(18,2) | `option_coupon_deduct_money` | 自动映射 | 规格选项优惠券抵扣金额(元) | `0.00` ~ 金额值 |
| `push_money` | NUMERIC(18,2) | `push_money` | 自动映射 | 推销提成金额(元) | `0.00` ~ 金额值 |
| `is_single_order` | INTEGER | `is_single_order` | 自动映射 | 是否单独订单 | `0` / `1` |
| `sales_type` | INTEGER | `sales_type` | 自动映射 | 销售类型 | 枚举值 |
| `operator_id` | BIGINT | `operator_id` | 自动映射 | 操作员 ID | 员工 ID 或 NULL |
---
## 3. 映射修正记录
| 日期 | 字段 | 修正内容 |
|------|------|---------|
| 2026-02-20 | `discount_price``discount_money` | 原 DWD `discount_price` 实际映射自 ODS `discount_money`(折扣金额),列名与语义不符,重命名为 `discount_money` |
| 2026-02-20 | `discount_price`(新增) | 新增 DWD 列,映射自 ODS 真正的 `discount_price`(折后单价) |
---
## 4. 代码引用
- FACT_MAPPINGS`dwd_load_task.py``FACT_MAPPINGS["dwd.dwd_store_goods_sale"]` / `FACT_MAPPINGS["dwd.dwd_store_goods_sale_ex"]`
- TABLE_MAP`"dwd.dwd_store_goods_sale" → "ods.store_goods_sales_records"`
- DWS 下游:`dws_finance_daily_task.py`(财务日报,商品销售汇总)
- 迁移脚本:`db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/migrations/2026-02-20__fix_store_goods_sale_discount_price.sql`(已归档)

View File

@@ -0,0 +1,80 @@
# BD_Manualtenant_goods_master租户商品档案
> ODS 表:`ods.tenant_goods_master`
> DWD 表:`dwd.dim_tenant_goods`(主表)、`dwd.dim_tenant_goods_ex`(扩展表)
> API 接口:租户商品列表
> JSON 路径:`tenant_goods_master.json → data.tenantGoodsList`
> 装载方式SCD2 维度合并(`DwdLoadTask`
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
---
## 1. dim_tenant_goods主表
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `tenant_goods_id` | BIGINT | `id` | FACT_MAPPINGS | 租户商品唯一标识PK 之一) | 飞球雪花 ID |
| `tenant_id` | BIGINT | `tenant_id` | 自动映射 | 租户 ID | 飞球租户 ID |
| `supplier_id` | BIGINT | `supplier_id` | 自动映射 | 供应商 ID | 飞球供应商 ID 或 0 |
| `category_name` | VARCHAR | `categoryname` | FACT_MAPPINGS | 一级分类名称 | 如 `酒水` |
| `goods_category_id` | BIGINT | `goods_category_id` | 自动映射 | 一级分类 ID | 飞球分类 ID |
| `goods_second_category_id` | BIGINT | `goods_second_category_id` | 自动映射 | 二级分类 ID | 飞球分类 ID |
| `goods_name` | VARCHAR | `goods_name` | 自动映射 | 商品名称 | 如 `百威啤酒` |
| `goods_number` | VARCHAR | `goods_number` | 自动映射 | 商品编号 | 编号字符串 |
| `unit` | VARCHAR | `unit` | 自动映射 | 计量单位 | 如 `瓶``包` |
| `market_price` | NUMERIC | `market_price` | 自动映射 | 市场价/建议售价(元) | 金额值 |
| `goods_state` | INTEGER | `goods_state` | 自动映射 | 商品状态 | 整数枚举 |
| `create_time` | TIMESTAMPTZ | `create_time` | 自动映射 | 商品创建时间 | ISO 时间戳 |
| `update_time` | TIMESTAMPTZ | `update_time` | 自动映射 | 商品最后更新时间 | ISO 时间戳 |
| `is_delete` | INTEGER | `is_delete` | 自动映射 | 软删除标记0=正常1=已删除 | `0` / `1` |
| `not_sale` | INTEGER | `not_sale` | FACT_MAPPINGS | 是否停售0=在售1=停售 | `0` / `1` |
| `scd2_*` | — | — | DWD 元数据 | SCD2 慢变维度追踪字段 | — |
---
## 2. dim_tenant_goods_ex扩展表
| DWD 列名 | 类型 | ODS 源列 | 映射方式 | 业务含义 | 取值范围/示例 |
|----------|------|---------|---------|---------|-------------|
| `tenant_goods_id` | BIGINT | `id` | FACT_MAPPINGS | 租户商品唯一标识PK 之一) | 同主表 |
| `remark_name` | VARCHAR(128) | `remark_name` | FACT_MAPPINGS | 商品备注名称 | 自由文本或 NULL |
| `pinyin_initial` | VARCHAR | `pinyin_initial` | 自动映射 | 商品名称拼音首字母,用于快速检索 | 如 `BWPJ` |
| `goods_cover` | VARCHAR | `goods_cover` | 自动映射 | 商品封面图 URL | HTTPS 链接或 NULL |
| `goods_bar_code` | VARCHAR | `goods_bar_code` | FACT_MAPPINGS | 商品条码 | 条码字符串或 NULL |
| `commodity_code` | VARCHAR | `commodity_code` | 自动映射 | 商品编码(单值,旧字段) | 编码字符串或 NULL |
| `commodity_code_list` | TEXT[] | `commoditycode` | FACT_MAPPINGS (cast TEXT[]) | 商品编码数组。ODS `commoditycode` 存储 PG 数组格式 `{CODE1}`CAST 为 `TEXT[]`。2026-02-21 从 VARCHAR(256) 改为 TEXT[] | `['1234571']` |
| `min_discount_price` | NUMERIC | `min_discount_price` | 自动映射 | 最低折扣价(元) | 金额值 |
| `cost_price` | NUMERIC | `cost_price` | 自动映射 | 成本价(元) | 金额值 |
| `cost_price_type` | INTEGER | `cost_price_type` | 自动映射 | 成本价类型 | 整数枚举 |
| `able_discount` | INTEGER | `able_discount` | 自动映射 | 是否允许折扣0=不允许1=允许 | `0` / `1` |
| `sale_channel` | INTEGER | `sale_channel` | 自动映射 | 销售渠道 | 整数枚举 |
| `is_warehousing` | INTEGER | `is_warehousing` | 自动映射 | 是否入库管理0=否1=是 | `0` / `1` |
| `is_in_site` | BOOLEAN | `isinsite` | FACT_MAPPINGS (cast boolean) | 是否已分配到门店 | `true` / `false` |
| `able_site_transfer` | INTEGER | `able_site_transfer` | 自动映射 | 是否允许门店间调拨 | `0` / `1` |
| `common_sale_royalty` | INTEGER | `common_sale_royalty` | 自动映射 | 普通销售提成(分) | 整数 |
| `point_sale_royalty` | INTEGER | `point_sale_royalty` | 自动映射 | 积分销售提成(分) | 整数 |
| `out_goods_id` | BIGINT | `out_goods_id` | 自动映射 | 外部商品 ID第三方系统关联 | 外部 ID 或 0 |
| `scd2_*` | — | — | DWD 元数据 | SCD2 慢变维度追踪字段 | — |
## 3. 映射修正记录
| 日期 | 字段 | 修正内容 |
|------|------|---------|
| 2026-02-21 | `commodity_code_list` | 类型从 `VARCHAR(256)` 改为 `TEXT[]`。映射源从 `commodity_code`(单值)改为 `commoditycode`PG 数组格式 `{xxx}`),通过 `::TEXT[]` CAST。现有数据通过 `ARRAY[commodity_code_list]` 迁移 |
---
## 4. 跳过字段说明
所有 ODS 业务字段均已映射到 DWD 主表或扩展表,无跳过字段。
> `commoditycode`ODS 列)已作为 `commodity_code_list` 的映射源(`commoditycode::TEXT[]`),同时 `commodity_code`(单值旧字段)也保留在扩展表中。
---
## 5. 代码引用
- FACT_MAPPINGS`dwd_load_task.py``FACT_MAPPINGS["dwd.dim_tenant_goods"]` / `FACT_MAPPINGS["dwd.dim_tenant_goods_ex"]`
- TABLE_MAP`"dwd.dim_tenant_goods" → "ods.tenant_goods_master"`
- DWS 下游:无直接 DWS 汇总引用
- 迁移脚本:`db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/migrations/2026-02-21__add_time_slot_sale_merge_commodity_code.sql`(已归档)

View File

@@ -199,7 +199,7 @@ class ConfigCache:
| `performance_tiers` | `dws.cfg_performance_tier` | 绩效档位(小时阈值 → 抽成/休假) |
| `level_prices` | `dws.cfg_assistant_level_price` | 助教等级单价(基础课/附加课) |
| `bonus_rules` | `dws.cfg_bonus_rules` | 奖金规则(冲刺奖金/Top 排名奖金) |
| `area_categories` | `dws.cfg_area_category` | 区域分类映射(精确/模糊/兜底) |
| `area_categories` | `dws.cfg_area_category` | 台桌级分类映射(台桌精确匹配/兜底) |
| `skill_types` | `dws.cfg_skill_type` | 技能 → 课程类型映射BASE/BONUS/ROOM |
#### 生效期过滤
@@ -214,7 +214,7 @@ class ConfigCache:
| `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` |
| `get_area_category(area_name, table_name)` | 台桌 → 分类 | 台桌级精确匹配 → 兜底 `OTHER`2026-03-09 改版,无 LIKE |
| `calculate_sprint_bonus(hours, date)` | 冲刺奖金 | 不累计,取满足阈值的最高档 |
| `calculate_top_rank_bonus(rank, date)` | Top 排名奖金 | 第 1/2/3 名分别对应配置金额,>3 返回 0 |
@@ -996,7 +996,7 @@ total_discount = adjust_amount + member_discount_amount + rounding_amount
**8. 区域分类**
通过 `ConfigCache` 加载 `cfg_area_category` 配置,调用 `get_area_category(area_name)` 将台桌区域名称映射为分类标签。匹配逻辑:精确匹配 → 模糊匹配 → 兜底 `OTHER`
通过 `ConfigCache` 加载 `cfg_area_category` 配置,调用 `get_area_category(area_name, table_name)` 将台桌映射为分类标签。匹配逻辑:台桌级精确匹配`source_area_name` + `source_table_name`)→ 兜底 `OTHER`2026-03-09 改版,已移除 LIKE 模糊匹配和区域级映射)
**9. 手机号脱敏**
@@ -1270,9 +1270,9 @@ total_card_balance = cash_card_balance + gift_card_balance
**维度 2按区域`structure_type = 'AREA'`**
通过 CTE 合并台费流水和助教服务流水,关联 `dim_table` 获取 `site_table_area_name`,再通过 `get_area_category(area_name)` 映射到分类代码。
通过 CTE 合并台费流水和助教服务流水,关联 `dim_table` 获取 `site_table_area_name``table_name`,再通过 `get_area_category(area_name, table_name)` 映射到分类代码。
区域映射逻辑(与 `DWS_MEMBER_VISIT` 相同):精确匹配 → 模糊匹配 → 兜底 `OTHER`
区域映射逻辑(与 `DWS_MEMBER_VISIT` 相同):台桌级精确匹配 → 兜底 `OTHER`2026-03-09 改版,无 LIKE
相同 `category_code` 的不同区域名称会被合并聚合。每条记录额外输出 `duration_minutes`(台费秒数 + 助教服务秒数,转换为分钟)。

View File

@@ -223,9 +223,12 @@ class AssistantOrderContributionTask(BaseDwsTask):
def _extract_settlements(
self, site_id: int, start_date: date, end_date: date
) -> List[Dict[str, Any]]:
"""提取台桌结账订单的结算主表
"""提取消费结算订单的结算主表
settle_type=1 为台桌结账,包含台费、酒水食品等金额。
settle_type IN (1, 3)
- 1 = 台桌结账(含台费、商品、陪打、超休)
- 3 = 商城订单(含纯超休/激励课 477 笔、商品、超休+商品混合)
settle_type=3 中陪打(pd)为 0、台费为 0但超休(cx)占全口径 85%+。
"""
# CHANGE 2026-03-01 | business-day-cutoff 6.2: DATE(pay_time) → 营业日归属表达式
cutoff = self.config.get("app.business_day_start_hour", 8)
@@ -240,7 +243,7 @@ class AssistantOrderContributionTask(BaseDwsTask):
{biz_expr} AS stat_date
FROM dwd.dwd_settlement_head
WHERE site_id = %s
AND settle_type = 1
AND settle_type IN (1, 3)
AND {biz_expr} >= %s
AND {biz_expr} <= %s
"""

View File

@@ -207,8 +207,7 @@ class AssistantProjectTagTask(BaseDwsTask):
# 全量删除该门店的标签数据后重建
delete_sql = "DELETE FROM dws.dws_assistant_project_tag WHERE site_id = %s"
self.db.execute(delete_sql, (site_id,))
deleted = self.db.cursor.rowcount if hasattr(self.db, "cursor") else 0
deleted = self.db.execute(delete_sql, (site_id,))
insert_sql = """
INSERT INTO dws.dws_assistant_project_tag (

View File

@@ -735,11 +735,14 @@ class BaseDwsTask(BaseTask):
def get_area_category(self, area_name: Optional[str], table_name: Optional[str] = None) -> Dict[str, str]:
"""
获取区域分类(支持台桌级精确 > 区域精确 > 模糊 > 兜底)
获取区域分类(台桌级精确匹配 + 兜底)
2026-03-09 改为纯台桌级精确映射,去掉 LIKE 模糊匹配和区域级映射。
每台桌一行 (area_name, table_name) → category_code。
Args:
area_name: 原始区域名称dim_table.site_table_area_name
table_name: 台桌名称dim_table.table_name用于台桌级细分映射
table_name: 台桌名称dim_table.table_name必须提供以精确匹配
Returns:
包含 category_code, category_name, display_name, short_name 的字典
@@ -766,19 +769,7 @@ class BaseDwsTask(BaseTask):
if key in cats and cats[key].get('match_type') == 'EXACT':
return _pick(cats[key])
# 2. 区域级精确匹配area_name + 空 table_name
key = f"{area_name}\x00"
if key in cats and cats[key].get('match_type') == 'EXACT':
return _pick(cats[key])
# 3. 模糊匹配(按优先级,已排序)
for k, cat in cats.items():
if cat.get('match_type') == 'LIKE':
pattern = cat['source_area_name'].replace('%', '')
if pattern and pattern in area_name:
return _pick(cat)
# 4. 兜底
# 2. 兜底
fallback_key = f"DEFAULT\x00"
if fallback_key in cats:
return _pick(cats[fallback_key])

View File

@@ -195,8 +195,7 @@ class MemberProjectTagTask(BaseDwsTask):
site_id = transformed[0]["site_id"]
delete_sql = "DELETE FROM dws.dws_member_project_tag WHERE site_id = %s"
self.db.execute(delete_sql, (site_id,))
deleted = self.db.cursor.rowcount if hasattr(self.db, "cursor") else 0
deleted = self.db.execute(delete_sql, (site_id,))
insert_sql = """
INSERT INTO dws.dws_member_project_tag (