在前后端开发联调前 的提交20260223

This commit is contained in:
Neo
2026-02-23 23:02:20 +08:00
parent 254ccb1e77
commit fafc95e64c
1142 changed files with 10366960 additions and 36957 deletions

View File

@@ -1,80 +1,29 @@
# database/ — 数据库
# db/etl_feiqiu/ — ETL 数据库资产
## 文件说明
## 六层 Schema
| Schema | 用途 | DDL 基线 |
|--------|------|---------|
| meta | ETL 调度元数据(任务注册、游标、运行记录) | `docs/database/ddl/etl_feiqiu__meta.sql` |
| ods | 原始数据层API payload 落地) | `docs/database/ddl/etl_feiqiu__ods.sql` |
| dwd | 明细数据层(维度 SCD2 + 事实增量) | `docs/database/ddl/etl_feiqiu__dwd.sql` |
| core | 跨门店标准化维度/事实 | `docs/database/ddl/etl_feiqiu__core.sql` |
| dws | 汇总数据层(业绩、财务、会员、工资、指数) | `docs/database/ddl/etl_feiqiu__dws.sql` |
| app | RLS 视图层(仅视图,无表) | `docs/database/ddl/etl_feiqiu__app.sql` |
## 种子数据
| 文件 | 用途 |
|------|------|
| `connection.py` | 数据库连接管理(带超时的 psycopg2 封装) |
| `operations.py` | 批量操作upsert、execute、query |
| `base.py` | 数据库操作基础类 |
## DDL Schema 文件
> CHANGE 2026-02-15 | 对齐新库 etl_feiqiu 六层架构,旧 schema 名已弃用
| 文件 | Schema | 说明 |
|------|-------------|------|
| `schema_ODS_doc.sql` | `ods` | ODS 层表结构(含字段注释) |
| `schema_dwd_doc.sql` | `dwd` | DWD 层表结构(维度 + 事实,含 SCD2 列) |
| `schema_dws.sql` | `dws` | DWS 层表结构(汇总表 + 配置表) |
| `schema_etl_admin.sql` | `meta` | ETL 元数据(任务注册、游标、运行记录) |
| `schema_verify_perf_indexes.sql` | 各 Schema | 校验性能索引(仅索引 + ANALYZE |
## 种子脚本
| 文件 | 用途 |
|------|------|
| `seed_ods_tasks.sql` | 注册 ODS 任务到 `meta.etl_task` |
| `seed_scheduler_tasks.sql` | 初始化调度任务配置 |
| `seed_dws_config.sql` | DWS 配置数据(绩效档位、等级定价、技能映射等) |
| `seed_index_parameters.sql` | 指数算法参数WBI/NCI/RS/OS/MS/ML |
## 迁移脚本
位于 `migrations/` 子目录,纯 SQL按日期前缀命名
```
migrations/
├── 20260208_relation_index_manual_ml.sql
├── 20260214_drop_ods_settlelist.sql
├── 20260214_drop_dwd_settle_list.sql
└── 20260214_drop_ods_option_name_able_site_transfer.sql
```
新增迁移时,文件名格式:`YYYYMMDD_描述.sql`
| `seeds/seed_ods_tasks.sql` | 注册 ODS 抓取任务到 `meta.etl_task` |
| `seeds/seed_scheduler_tasks.sql` | 初始化调度任务配置 |
| `seeds/seed_dws_config.sql` | DWS 配置(绩效档位、等级定价、技能映射等) |
| `seeds/seed_index_parameters.sql` | 指数算法参数WBI/NCI/RS/OS/MS/ML |
## Schema 约定
- 所有 DDL 使用 `CREATE TABLE IF NOT EXISTS`,支持幂等执行
- 表名小写蛇形,带 Schema 前缀(如 `dwd.dim_member`
- 维度表包含 SCD2 列:`scd2_start_time``scd2_end_time``scd2_is_current``scd2_version`
- ODS 表包含元数据列:`content_hash``payload``fetched_at``source_file`
- 金额字段统一 `NUMERIC(12,2)`ID 字段统一 `BIGINT`
- 不使用 ORM所有 SQL 通过 `psycopg2` 直接执行
<!--
AI_CHANGELOG:
- 日期: 2026-02-14
- Prompt: P20260214-030000 — 上下文传递续接,完成 settlelist 删除后的变更影响审查
- 直接原因: Change Impact Review 要求将新增迁移脚本同步到 database/README.md
- 变更摘要: 迁移脚本列表新增 20260214_drop_ods_settlelist.sql 条目
- 风险与验证: 纯文档变更,无运行时影响;验证方式:`ls database/migrations/` 确认文件存在
-->
<!--
AI_CHANGELOG:
- 日期: 2026-02-14
- Prompt: P20260214-040000 — "dwd_settlement_head_ex.settle_list 也没有必要保留了"
- 直接原因: 新增迁移脚本需同步到 README 迁移列表
- 变更摘要: 迁移脚本列表新增 20260214_drop_dwd_settle_list.sql
- 风险与验证: 纯文档变更验证ls database/migrations/ 确认文件存在
-->
<!--
AI_CHANGELOG:
- 日期: 2026-02-14
- Prompt: P20260214-070000 — ODS 清理(删除 option_name/able_site_transfer变更影响审查
- 直接原因: Change Impact Review 要求将新增迁移脚本同步到 database/README.md
- 变更摘要: 迁移脚本列表新增 20260214_drop_ods_option_name_able_site_transfer.sql
- 风险与验证: 纯文档变更验证ls database/migrations/ 确认文件存在
-->
- 表名小写蛇形,带 schema 前缀(如 `dwd.dim_member`
- 维度表含 SCD2 列:`scd2_start_time``scd2_end_time``scd2_is_current``scd2_version`
- ODS 表含元数据列:`content_hash``payload``fetched_at``source_file`
- 金额字段 `NUMERIC(12,2)`ID 字段 `BIGINT`
- 不使用 ORMSQL 通过 `psycopg2` 直接执行

View File

@@ -1,97 +0,0 @@
-- 迁移脚本:为 ODS 表添加"取最新版本"复合索引
-- 支持 DISTINCT ON (pk) ORDER BY pk, fetched_at DESC 查询模式
-- 注意CREATE INDEX CONCURRENTLY 不能在事务块内执行,需逐条手动运行或用支持单语句模式的工具
-- 日期2026-02-17
-- 关联需求ods-dedup-standardize Requirements 6.1, 6.2, 6.3
-- 1. assistant_accounts_master (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_assistant_accounts_master_latest
ON ods.assistant_accounts_master (id, fetched_at DESC);
-- 2. settlement_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_settlement_records_latest
ON ods.settlement_records (id, fetched_at DESC);
-- 3. table_fee_transactions (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_table_fee_transactions_latest
ON ods.table_fee_transactions (id, fetched_at DESC);
-- 4. assistant_service_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_assistant_service_records_latest
ON ods.assistant_service_records (id, fetched_at DESC);
-- 5. assistant_cancellation_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_assistant_cancellation_records_latest
ON ods.assistant_cancellation_records (id, fetched_at DESC);
-- 6. store_goods_sales_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_store_goods_sales_records_latest
ON ods.store_goods_sales_records (id, fetched_at DESC);
-- 7. payment_transactions (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_payment_transactions_latest
ON ods.payment_transactions (id, fetched_at DESC);
-- 8. refund_transactions (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_refund_transactions_latest
ON ods.refund_transactions (id, fetched_at DESC);
-- 9. platform_coupon_redemption_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_platform_coupon_redemption_records_latest
ON ods.platform_coupon_redemption_records (id, fetched_at DESC);
-- 10. member_profiles (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_member_profiles_latest
ON ods.member_profiles (id, fetched_at DESC);
-- 11. member_stored_value_cards (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_member_stored_value_cards_latest
ON ods.member_stored_value_cards (id, fetched_at DESC);
-- 12. member_balance_changes (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_member_balance_changes_latest
ON ods.member_balance_changes (id, fetched_at DESC);
-- 13. recharge_settlements (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_recharge_settlements_latest
ON ods.recharge_settlements (id, fetched_at DESC);
-- 14. group_buy_packages (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_group_buy_packages_latest
ON ods.group_buy_packages (id, fetched_at DESC);
-- 15. group_buy_redemption_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_group_buy_redemption_records_latest
ON ods.group_buy_redemption_records (id, fetched_at DESC);
-- 16. goods_stock_summary (pk: siteGoodsId)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_goods_stock_summary_latest
ON ods.goods_stock_summary (siteGoodsId, fetched_at DESC);
-- 17. goods_stock_movements (pk: siteGoodsStockId)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_goods_stock_movements_latest
ON ods.goods_stock_movements (siteGoodsStockId, fetched_at DESC);
-- 18. site_tables_master (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_site_tables_master_latest
ON ods.site_tables_master (id, fetched_at DESC);
-- 19. stock_goods_category_tree (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_stock_goods_category_tree_latest
ON ods.stock_goods_category_tree (id, fetched_at DESC);
-- 20. store_goods_master (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_store_goods_master_latest
ON ods.store_goods_master (id, fetched_at DESC);
-- 21. table_fee_discount_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_table_fee_discount_records_latest
ON ods.table_fee_discount_records (id, fetched_at DESC);
-- 22. tenant_goods_master (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_tenant_goods_master_latest
ON ods.tenant_goods_master (id, fetched_at DESC);
-- 23. settlement_ticket_details (pk: orderSettleId)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_settlement_ticket_details_latest
ON ods.settlement_ticket_details (orderSettleId, fetched_at DESC);

View File

@@ -0,0 +1,139 @@
-- =============================================================================
-- 迁移脚本:创建员工档案 ODS + DWD 表
-- 日期2026-02-22
-- 说明:从 SearchSystemStaffInfo API 抓取员工数据ODS 落地后清洗至 DWD 维度表
-- 需求US-1, US-2, US-3
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. ODS 层ods.staff_info_master
-- ---------------------------------------------------------------------------
CREATE TABLE ods.staff_info_master (
id BIGINT NOT NULL,
tenant_id BIGINT,
site_id BIGINT,
tenant_org_id BIGINT,
system_user_id BIGINT,
staff_name TEXT,
alias_name TEXT,
mobile TEXT,
avatar TEXT,
gender INTEGER,
job TEXT,
job_num TEXT,
staff_identity INTEGER,
status INTEGER,
account_status INTEGER,
system_role_id INTEGER,
rank_id INTEGER,
rank_name TEXT,
new_rank_id INTEGER,
new_staff_identity INTEGER,
leave_status INTEGER,
entry_time TIMESTAMP WITHOUT TIME ZONE,
resign_time TIMESTAMP WITHOUT TIME ZONE,
create_time TIMESTAMP WITHOUT TIME ZONE,
is_delete INTEGER,
is_reserve INTEGER,
shop_name TEXT,
site_label TEXT,
cashier_point_id BIGINT,
cashier_point_name TEXT,
group_id BIGINT,
group_name TEXT,
staff_profile_id BIGINT,
auth_code TEXT,
auth_code_create TIMESTAMP WITHOUT TIME ZONE,
ding_talk_synced INTEGER,
salary_grant_enabled INTEGER,
entry_type INTEGER,
entry_sign_status INTEGER,
resign_sign_status INTEGER,
criticism_status INTEGER,
user_roles JSONB,
-- ETL 元数据
content_hash TEXT NOT NULL,
source_file TEXT,
fetched_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
payload JSONB NOT NULL
);
COMMENT ON TABLE ods.staff_info_master IS '员工档案主数据来源SearchSystemStaffInfo API';
-- ---------------------------------------------------------------------------
-- 2. DWD 层dwd.dim_staff主表核心业务字段SCD2
-- ---------------------------------------------------------------------------
CREATE TABLE dwd.dim_staff (
staff_id BIGINT NOT NULL,
staff_name TEXT,
alias_name TEXT,
mobile TEXT,
gender INTEGER,
job TEXT,
tenant_id BIGINT,
site_id BIGINT,
system_role_id INTEGER,
staff_identity INTEGER,
status INTEGER,
leave_status INTEGER,
entry_time TIMESTAMP WITH TIME ZONE,
resign_time TIMESTAMP WITH TIME ZONE,
is_delete INTEGER,
-- SCD2
scd2_start_time TIMESTAMP WITH TIME ZONE NOT NULL,
scd2_end_time TIMESTAMP WITH TIME ZONE,
scd2_is_current INTEGER,
scd2_version INTEGER,
PRIMARY KEY (staff_id, scd2_start_time)
);
COMMENT ON TABLE dwd.dim_staff IS '员工档案维度主表SCD2';
-- ---------------------------------------------------------------------------
-- 3. DWD 层dwd.dim_staff_ex扩展表次要/低频变更字段SCD2
-- ---------------------------------------------------------------------------
CREATE TABLE dwd.dim_staff_ex (
staff_id BIGINT NOT NULL,
avatar TEXT,
job_num TEXT,
account_status INTEGER,
rank_id INTEGER,
rank_name TEXT,
new_rank_id INTEGER,
new_staff_identity INTEGER,
is_reserve INTEGER,
shop_name TEXT,
site_label TEXT,
tenant_org_id BIGINT,
system_user_id BIGINT,
cashier_point_id BIGINT,
cashier_point_name TEXT,
group_id BIGINT,
group_name TEXT,
staff_profile_id BIGINT,
auth_code TEXT,
auth_code_create TIMESTAMP WITH TIME ZONE,
ding_talk_synced INTEGER,
salary_grant_enabled INTEGER,
entry_type INTEGER,
entry_sign_status INTEGER,
resign_sign_status INTEGER,
criticism_status INTEGER,
create_time TIMESTAMP WITH TIME ZONE,
user_roles JSONB,
-- SCD2
scd2_start_time TIMESTAMP WITH TIME ZONE NOT NULL,
scd2_end_time TIMESTAMP WITH TIME ZONE,
scd2_is_current INTEGER,
scd2_version INTEGER,
PRIMARY KEY (staff_id, scd2_start_time)
);
COMMENT ON TABLE dwd.dim_staff_ex IS '员工档案维度扩展表SCD2';
-- =============================================================================
-- 回滚脚本(如需撤销)
-- DROP TABLE IF EXISTS dwd.dim_staff_ex;
-- DROP TABLE IF EXISTS dwd.dim_staff;
-- DROP TABLE IF EXISTS ods.staff_info_master;
-- =============================================================================

View File

@@ -0,0 +1,70 @@
-- =============================================================================
-- 迁移脚本:创建 dws.dws_member_spending_power_index 表
-- 日期2026-02-23
-- 说明SPI消费力指数结果表存储会员级消费力评分及中间特征
-- 需求1.1, 1.2, 1.3, 1.4, 1.5
-- =============================================================================
-- 序列BIGSERIAL 自动创建,此处显式声明以保持与项目风格一致)
CREATE SEQUENCE IF NOT EXISTS dws.dws_member_spending_power_index_spi_id_seq AS bigint;
-- 主表
CREATE TABLE dws.dws_member_spending_power_index (
spi_id BIGINT DEFAULT nextval('dws.dws_member_spending_power_index_spi_id_seq'::regclass) NOT NULL,
site_id INTEGER NOT NULL,
member_id BIGINT NOT NULL,
-- 基础特征
spend_30 NUMERIC(14,2) DEFAULT 0, -- 近30天消费总额
spend_90 NUMERIC(14,2) DEFAULT 0, -- 近90天消费总额
recharge_90 NUMERIC(14,2) DEFAULT 0, -- 近90天充值总额
orders_30 INTEGER DEFAULT 0, -- 近30天消费笔数
orders_90 INTEGER DEFAULT 0, -- 近90天消费笔数
visit_days_30 INTEGER DEFAULT 0, -- 近30天消费日数按天去重
visit_days_90 INTEGER DEFAULT 0, -- 近90天消费日数按天去重
avg_ticket_90 NUMERIC(14,2) DEFAULT 0, -- 90天客单价
active_weeks_90 INTEGER DEFAULT 0, -- 近90天有消费的自然周数最多13
daily_spend_ewma_90 NUMERIC(14,2) DEFAULT 0, -- 日消费 EWMA
-- 子分Raw算法直接计算的未归一化分数
score_level_raw NUMERIC(10,4) DEFAULT 0, -- Level 子分原始分
score_speed_raw NUMERIC(10,4) DEFAULT 0, -- Speed 子分原始分
score_stability_raw NUMERIC(10,4) DEFAULT 0, -- Stability 子分原始分
-- 子分Display归一化到 0-10 的展示分)
score_level_display NUMERIC(5,2) DEFAULT 0,
score_speed_display NUMERIC(5,2) DEFAULT 0,
score_stability_display NUMERIC(5,2) DEFAULT 0,
-- 总分
raw_score NUMERIC(10,4) DEFAULT 0, -- SPI 原始分(加权合成)
display_score NUMERIC(5,2) DEFAULT 0, -- SPI 展示分0-10
-- 元数据
calc_time TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- 主键
CONSTRAINT dws_member_spending_power_index_pkey PRIMARY KEY (spi_id)
);
-- 唯一约束(业务主键:一个门店一个会员只有一条记录)
CREATE UNIQUE INDEX idx_spi_site_member
ON dws.dws_member_spending_power_index (site_id, member_id);
-- 查询索引(按门店查询展示分排名)
CREATE INDEX idx_spi_display_score
ON dws.dws_member_spending_power_index (site_id, display_score DESC);
-- 序列归属
ALTER SEQUENCE dws.dws_member_spending_power_index_spi_id_seq
OWNED BY dws.dws_member_spending_power_index.spi_id;
-- =============================================================================
-- 回滚脚本(如需撤销)
-- 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;
-- =============================================================================

View File

@@ -1,144 +0,0 @@
-- =============================================================================
-- 关系指数与 ML 人工台账迁移脚本
-- 版本: 2026-02-08
-- AI_CHANGELOG [2026-02-13] 移除 INSERT 中的 source_mode 参数ML 仅用人工台账)
-- 说明:
-- 1) 新增关系指数结果表 dws_member_assistant_relation_index
-- 2) 新增 ML 人工台账宽表/窄表
-- 3) 补充 RS/OS/MS/ML 参数并下线 INTIMACY
-- =============================================================================
BEGIN;
-- -----------------------------------------------------------------------------
-- 1) 关系指数结果表
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS billiards_dws.dws_member_assistant_relation_index (
relation_id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
assistant_id BIGINT NOT NULL,
session_count INTEGER NOT NULL DEFAULT 0,
total_duration_minutes INTEGER NOT NULL DEFAULT 0,
basic_session_count INTEGER NOT NULL DEFAULT 0,
incentive_session_count INTEGER NOT NULL DEFAULT 0,
days_since_last_session INTEGER,
rs_f NUMERIC(14,6) NOT NULL DEFAULT 0,
rs_d NUMERIC(14,6) NOT NULL DEFAULT 0,
rs_r NUMERIC(14,6) NOT NULL DEFAULT 0,
rs_raw NUMERIC(14,6) NOT NULL DEFAULT 0,
rs_display NUMERIC(4,2) NOT NULL DEFAULT 0,
os_share NUMERIC(10,6) NOT NULL DEFAULT 0,
os_label VARCHAR(20) NOT NULL DEFAULT 'POOL',
os_rank INTEGER,
ms_f_short NUMERIC(14,6) NOT NULL DEFAULT 0,
ms_f_long NUMERIC(14,6) NOT NULL DEFAULT 0,
ms_raw NUMERIC(14,6) NOT NULL DEFAULT 0,
ms_display NUMERIC(4,2) NOT NULL DEFAULT 0,
ml_order_count INTEGER NOT NULL DEFAULT 0,
ml_allocated_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
ml_raw NUMERIC(14,6) NOT NULL DEFAULT 0,
ml_display NUMERIC(4,2) NOT NULL DEFAULT 0,
calc_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uk_dws_member_assistant_relation_index UNIQUE (site_id, member_id, assistant_id)
);
CREATE INDEX IF NOT EXISTS idx_dws_relation_member
ON billiards_dws.dws_member_assistant_relation_index (site_id, member_id, os_share DESC);
CREATE INDEX IF NOT EXISTS idx_dws_relation_assistant
ON billiards_dws.dws_member_assistant_relation_index (site_id, assistant_id, rs_display DESC);
CREATE INDEX IF NOT EXISTS idx_dws_relation_calc_time
ON billiards_dws.dws_member_assistant_relation_index (calc_time);
-- -----------------------------------------------------------------------------
-- 2) ML 人工台账宽表
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS billiards_dws.dws_ml_manual_order_source (
source_id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
biz_date DATE NOT NULL,
external_id VARCHAR(128) NOT NULL,
member_id BIGINT NOT NULL DEFAULT 0,
pay_time TIMESTAMPTZ NOT NULL,
order_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
assistant_id_1 BIGINT,
assistant_name_1 VARCHAR(128),
assistant_id_2 BIGINT,
assistant_name_2 VARCHAR(128),
assistant_id_3 BIGINT,
assistant_name_3 VARCHAR(128),
assistant_id_4 BIGINT,
assistant_name_4 VARCHAR(128),
assistant_id_5 BIGINT,
assistant_name_5 VARCHAR(128),
import_batch_no VARCHAR(64) NOT NULL,
import_file_name VARCHAR(255) NOT NULL,
import_scope_key VARCHAR(128) NOT NULL,
import_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
import_user VARCHAR(64),
row_no INTEGER NOT NULL,
remark TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uk_dws_ml_manual_order_source UNIQUE (site_id, external_id, import_scope_key, row_no)
);
CREATE INDEX IF NOT EXISTS idx_dws_ml_source_scope
ON billiards_dws.dws_ml_manual_order_source (site_id, biz_date);
CREATE INDEX IF NOT EXISTS idx_dws_ml_source_external
ON billiards_dws.dws_ml_manual_order_source (site_id, external_id);
-- -----------------------------------------------------------------------------
-- 3) ML 人工台账窄表
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS billiards_dws.dws_ml_manual_order_alloc (
alloc_id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
biz_date DATE NOT NULL,
external_id VARCHAR(128) NOT NULL,
member_id BIGINT NOT NULL DEFAULT 0,
pay_time TIMESTAMPTZ NOT NULL,
order_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
assistant_id BIGINT NOT NULL,
assistant_name VARCHAR(128),
share_ratio NUMERIC(14,8) NOT NULL DEFAULT 0,
allocated_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
import_scope_key VARCHAR(128) NOT NULL,
import_batch_no VARCHAR(64) NOT NULL,
import_file_name VARCHAR(255) NOT NULL,
import_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
import_user VARCHAR(64),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uk_dws_ml_manual_order_alloc UNIQUE (site_id, external_id, assistant_id)
);
CREATE INDEX IF NOT EXISTS idx_dws_ml_alloc_scope
ON billiards_dws.dws_ml_manual_order_alloc (site_id, biz_date);
CREATE INDEX IF NOT EXISTS idx_dws_ml_alloc_member_assistant
ON billiards_dws.dws_ml_manual_order_alloc (site_id, member_id, assistant_id);
-- -----------------------------------------------------------------------------
-- 4) 参数切换
-- -----------------------------------------------------------------------------
UPDATE billiards_dws.cfg_index_parameters
SET effective_to = DATE '2025-12-31',
updated_at = NOW()
WHERE index_type = 'INTIMACY'
AND (effective_to IS NULL OR effective_to > DATE '2025-12-31');
INSERT INTO billiards_dws.cfg_index_parameters
(index_type, param_name, param_value, description, effective_from)
VALUES
('OS', 'ownership_gap_threshold', 0.150000, '主责与次席份额差阈值', DATE '2026-01-01')
ON CONFLICT (index_type, param_name, effective_from) DO UPDATE SET
param_value = EXCLUDED.param_value,
description = EXCLUDED.description,
updated_at = NOW();
COMMIT;

View File

@@ -1,14 +0,0 @@
-- ============================================================
-- ODS 表与 API JSON 字段对齐迁移
-- 自动生成于 2026-02-13
-- 基于: docs/api-reference/ 文档 vs billiards_ods 实际表结构
-- 比对逻辑: camelCase → snake_case 归一化后再比较
-- ============================================================
--
-- 结论: 22 张 ODS 表全部与 API JSON 字段对齐,无需任何 ALTER 操作。
--
-- stock_goods_category_tree 的 goodsCategoryList/total 为响应包装层字段,
-- ODS 表已正确展开存储数组内的记录级字段id, category_name, pid 等),
-- 不需要将包装层字段作为列添加。
--
-- 无需执行此文件。

View File

@@ -1,72 +0,0 @@
-- =============================================================================
-- 迁移移除旧版指数RECALL / INTIMACY及 ML last-touch 备用参数
-- 日期: 2026-02-13
-- AI_CHANGELOG [2026-02-13] 新建迁移DROP recall/intimacy 表DELETE 旧版参数/分位点/调度任务
-- 原因: 旧版 RecallIndexTask / IntimacyIndexTask 已被 WBI+NCI / RelationIndexTask 替代
-- ML 仅使用人工台账,不再需要 last-touch 备用路径
-- =============================================================================
BEGIN;
-- 1. 删除旧版指数参数
DELETE FROM billiards_dws.cfg_index_parameters
WHERE index_type IN ('RECALL', 'INTIMACY');
-- 2. 删除 ML 已废弃参数
DELETE FROM billiards_dws.cfg_index_parameters
WHERE index_type = 'ML'
AND param_name IN ('source_mode', 'recharge_attribute_hours');
-- 3. 删除旧版指数分位点历史
DELETE FROM billiards_dws.cfg_index_percentile_history
WHERE index_type IN ('RECALL', 'INTIMACY');
-- 4. 删除调度器中的旧版任务
DELETE FROM etl_admin.etl_task
WHERE task_code IN ('DWS_RECALL_INDEX', 'DWS_INTIMACY_INDEX');
-- 5. 删除旧版 recall 表(数据已由 WBI+NCI 替代)
DROP TABLE IF EXISTS billiards_dws.dws_member_recall_index CASCADE;
-- 6. 删除旧版 intimacy 表(数据已由 RelationIndexTask 替代)
DROP TABLE IF EXISTS billiards_dws.dws_member_assistant_intimacy CASCADE;
COMMIT;
-- =============================================================================
-- 验证
-- =============================================================================
DO $
DECLARE
recall_params INTEGER;
intimacy_params INTEGER;
ml_legacy INTEGER;
recall_table BOOLEAN;
intimacy_table BOOLEAN;
BEGIN
SELECT COUNT(*) INTO recall_params
FROM billiards_dws.cfg_index_parameters WHERE index_type = 'RECALL';
SELECT COUNT(*) INTO intimacy_params
FROM billiards_dws.cfg_index_parameters WHERE index_type = 'INTIMACY';
SELECT COUNT(*) INTO ml_legacy
FROM billiards_dws.cfg_index_parameters
WHERE index_type = 'ML' AND param_name IN ('source_mode', 'recharge_attribute_hours');
SELECT EXISTS(
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'billiards_dws' AND table_name = 'dws_member_recall_index'
) INTO recall_table;
SELECT EXISTS(
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'billiards_dws' AND table_name = 'dws_member_assistant_intimacy'
) INTO intimacy_table;
RAISE NOTICE 'RECALL 参数残留: % (应为 0)', recall_params;
RAISE NOTICE 'INTIMACY 参数残留: % (应为 0)', intimacy_params;
RAISE NOTICE 'ML 废弃参数残留: % (应为 0)', ml_legacy;
RAISE NOTICE 'recall 表存在: % (应为 false)', recall_table;
RAISE NOTICE 'intimacy 表存在: % (应为 false)', intimacy_table;
END $;

View File

@@ -1,12 +0,0 @@
-- 迁移:删除 DWD 层 dwd_settlement_head_ex 的 settle_list 列
-- 原因settle_list 存储结算明细 JSON与 ODS 层 payload 中的 settleList 完全重复
-- ODS 层 settlelist 列已在 20260214_drop_ods_settlelist.sql 中删除
-- DWD 层该列同样冗余,结算明细可随时从 ODS payload 提取
-- 回滚ALTER TABLE billiards_dwd.dwd_settlement_head_ex ADD COLUMN settle_list JSONB;
-- UPDATE billiards_dwd.dwd_settlement_head_ex e
-- SET settle_list = o.payload->'settleList'
-- FROM billiards_ods.settlement_records o
-- WHERE e.order_settle_id = o.id;
-- Prompt: P20260214-040000
ALTER TABLE billiards_dwd.dwd_settlement_head_ex DROP COLUMN IF EXISTS settle_list;

View File

@@ -1,23 +0,0 @@
-- 迁移:删除 ODS 层两个冗余列
-- 日期2026-02-14
-- Prompt-IDP20260214-070000
-- 原因option_namestore_goods_sales_records和 able_site_transfermember_stored_value_cards
-- 在 API JSON 响应中不存在ODS 中全部为 NULL0 条非空数据),属于冗余列
-- 回滚ALTER TABLE billiards_ods.store_goods_sales_records ADD COLUMN option_name TEXT;
-- ALTER TABLE billiards_ods.member_stored_value_cards ADD COLUMN able_site_transfer INTEGER;
-- CHANGE: intent=删除 API 中不存在的冗余 ODS 列; assumptions=两列全 NULL 无数据丢失风险; Prompt=P20260214-070000
BEGIN;
ALTER TABLE billiards_ods.store_goods_sales_records DROP COLUMN IF EXISTS option_name;
ALTER TABLE billiards_ods.member_stored_value_cards DROP COLUMN IF EXISTS able_site_transfer;
COMMIT;
-- AI_CHANGELOG:
-- - 日期: 2026-02-14
-- - Prompt: P20260214-070000 — ODS 清理与文档标注5 项任务)
-- - 直接原因: option_name 和 able_site_transfer 在 API JSON 中不存在ODS 全 NULL需删除冗余列
-- - 变更摘要: 新建迁移脚本DROP COLUMN 两张表各一列
-- - 风险与验证: 已执行成功验证information_schema 查询确认列不存在

View File

@@ -1,34 +0,0 @@
-- 迁移:删除 ODS 层 settlement_records / recharge_settlements 的 settlelist jsonb 列
-- 原因settlelist 与 payload 列数据重复payload 存储完整 API 响应 JSON已包含 settleList 对象)
-- 日期2026-02-14
-- Prompt-ID: P20260214-023000
--
-- 前置条件:确认 DWD 加载逻辑已修改为从 payload 提取 settleList而非直接读 settlelist 列)
-- 回滚:见文件末尾 ROLLBACK 部分
-- CHANGE: intent=删除冗余 settlelist 列,减少存储浪费
-- assumptions=payload 列已包含完整 settleList 数据DWD 加载已改为从 payload 提取
-- edge_cases=历史数据中 payload 为 NULL 的行将丢失 settleList 信息
-- prompt=P20260214-023000
-- 1) settlement_records
ALTER TABLE billiards_ods.settlement_records DROP COLUMN IF EXISTS settlelist;
-- 2) recharge_settlements
ALTER TABLE billiards_ods.recharge_settlements DROP COLUMN IF EXISTS settlelist;
-- 验证 SQL
-- SELECT column_name FROM information_schema.columns
-- WHERE table_schema = 'billiards_ods'
-- AND table_name IN ('settlement_records', 'recharge_settlements')
-- AND column_name = 'settlelist';
-- 预期结果0 行
-- ============================================================
-- ROLLBACK回滚
-- ============================================================
-- ALTER TABLE billiards_ods.settlement_records ADD COLUMN settlelist jsonb;
-- ALTER TABLE billiards_ods.recharge_settlements ADD COLUMN settlelist jsonb;
-- 注意:回滚后列数据为 NULL需从 payload 中重新提取:
-- UPDATE billiards_ods.settlement_records SET settlelist = payload->'settleList' WHERE payload IS NOT NULL;
-- UPDATE billiards_ods.recharge_settlements SET settlelist = payload->'settleList' WHERE payload IS NOT NULL;

View File

@@ -1,240 +0,0 @@
-- =============================================================================
-- app schema DDL — 面向外部访问的视图/函数 + RLS 策略
-- 说明:以视图封装 DWS/Core 层数据,所有视图启用 RLS以 site_id 过滤
-- 不存储实际数据,仅做访问层
-- =============================================================================
CREATE SCHEMA IF NOT EXISTS app;
SET search_path TO app;
-- -----------------------------------------------------------------------------
-- 应用角色(供 FDW 和外部应用使用)
-- -----------------------------------------------------------------------------
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_reader') THEN
CREATE ROLE app_reader;
END IF;
END
$$;
GRANT USAGE ON SCHEMA app TO app_reader;
GRANT USAGE ON SCHEMA core TO app_reader;
GRANT USAGE ON SCHEMA dws TO app_reader;
-- =============================================================================
-- 第一部分:基于 Core 层的视图
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 1. v_site — 门店视图
-- -----------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_site AS
SELECT
s.site_id,
s.tenant_id,
s.shop_name,
s.site_label,
s.shop_status
FROM core.dim_site s;
COMMENT ON VIEW app.v_site IS '门店视图:封装 core.dim_site供外部应用访问。';
-- -----------------------------------------------------------------------------
-- 2. v_member — 会员视图
-- -----------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_member AS
SELECT
m.member_id,
m.system_member_id,
m.tenant_id,
m.register_site_id AS site_id,
m.mobile,
m.nickname,
m.member_card_grade_name,
m.status
FROM core.dim_member m;
COMMENT ON VIEW app.v_member IS '会员视图:封装 core.dim_member以 register_site_id 作为 site_id。';
-- -----------------------------------------------------------------------------
-- 3. v_assistant — 助教视图
-- -----------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_assistant AS
SELECT
a.assistant_id,
a.tenant_id,
a.site_id,
a.real_name,
a.nickname,
a.mobile,
a.level,
a.assistant_status,
a.leave_status
FROM core.dim_assistant a;
COMMENT ON VIEW app.v_assistant IS '助教视图:封装 core.dim_assistant。';
-- =============================================================================
-- 第二部分:基于 DWS 层的汇总视图
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 4. v_assistant_daily — 助教日明细视图
-- -----------------------------------------------------------------------------
-- CHANGE 2026-02-15 | 修复视图列名,对齐 dws.dws_assistant_daily_detail 实际表结构
CREATE OR REPLACE VIEW app.v_assistant_daily AS
SELECT
d.id,
d.site_id,
d.tenant_id,
d.assistant_id,
d.assistant_nickname,
d.stat_date,
d.total_service_count,
d.total_hours,
d.base_hours,
d.bonus_hours,
d.room_hours,
d.total_ledger_amount,
d.unique_customers,
d.unique_tables,
d.created_at
FROM dws.dws_assistant_daily_detail d;
COMMENT ON VIEW app.v_assistant_daily IS '助教日明细视图:封装 dws.dws_assistant_daily_detail。';
-- -----------------------------------------------------------------------------
-- 5. v_finance_daily — 财务日报视图
-- -----------------------------------------------------------------------------
-- CHANGE 2026-02-15 | 修复视图列名,对齐 dws.dws_finance_daily_summary 实际表结构
CREATE OR REPLACE VIEW app.v_finance_daily AS
SELECT
f.id,
f.site_id,
f.tenant_id,
f.stat_date,
f.gross_amount,
f.table_fee_amount,
f.goods_amount,
f.assistant_pd_amount,
f.assistant_cx_amount,
f.discount_total,
f.confirmed_income,
f.cash_inflow_total,
f.recharge_count,
f.recharge_total,
f.order_count,
f.member_order_count,
f.guest_order_count,
f.avg_order_amount,
f.created_at
FROM dws.dws_finance_daily_summary f;
COMMENT ON VIEW app.v_finance_daily IS '财务日报视图:封装 dws.dws_finance_daily_summary。';
-- -----------------------------------------------------------------------------
-- 6. v_member_consumption — 会员消费汇总视图
-- -----------------------------------------------------------------------------
-- CHANGE 2026-02-15 | 修复视图列名,对齐 dws.dws_member_consumption_summary 实际表结构
CREATE OR REPLACE VIEW app.v_member_consumption AS
SELECT
mc.id,
mc.site_id,
mc.tenant_id,
mc.member_id,
mc.stat_date,
mc.member_nickname,
mc.card_grade_name,
mc.total_visit_count,
mc.total_consume_amount,
mc.total_recharge_amount,
mc.last_consume_date,
mc.first_consume_date,
mc.days_since_last,
mc.customer_tier,
mc.created_at
FROM dws.dws_member_consumption_summary mc;
COMMENT ON VIEW app.v_member_consumption IS '会员消费汇总视图:封装 dws.dws_member_consumption_summary。';
-- -----------------------------------------------------------------------------
-- 7. v_order_summary — 订单汇总视图
-- -----------------------------------------------------------------------------
-- CHANGE 2026-02-15 | 修复视图列名,对齐 dws.dws_order_summary 实际表结构
CREATE OR REPLACE VIEW app.v_order_summary AS
SELECT
os.site_id,
os.order_settle_id,
os.order_trade_no,
os.order_date,
os.tenant_id,
os.member_id,
os.member_flag,
os.order_original_amount,
os.order_final_amount,
os.total_paid_amount,
os.refund_amount,
os.net_income,
os.created_at
FROM dws.dws_order_summary os;
COMMENT ON VIEW app.v_order_summary IS '订单汇总视图:封装 dws.dws_order_summary。';
-- =============================================================================
-- 第三部分RLS 策略
-- 说明:所有视图基于 site_id 隔离,通过会话变量 app.current_site_id 过滤
-- 使用方式SET app.current_site_id = '2790685415443269';
-- =============================================================================
-- 对视图底层表启用 RLS视图本身不支持 RLS需在底层表上设置
-- 由于 app schema 的视图引用 core/dws 表RLS 需在源表上配置
-- core 层 RLS
ALTER TABLE core.dim_site ENABLE ROW LEVEL SECURITY;
ALTER TABLE core.dim_member ENABLE ROW LEVEL SECURITY;
ALTER TABLE core.dim_assistant ENABLE ROW LEVEL SECURITY;
ALTER TABLE core.dim_table ENABLE ROW LEVEL SECURITY;
ALTER TABLE core.fact_settlement ENABLE ROW LEVEL SECURITY;
ALTER TABLE core.fact_payment ENABLE ROW LEVEL SECURITY;
-- core 层策略
CREATE POLICY site_isolation_dim_site ON core.dim_site
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
CREATE POLICY site_isolation_dim_member ON core.dim_member
FOR SELECT TO app_reader
USING (register_site_id = current_setting('app.current_site_id')::bigint);
CREATE POLICY site_isolation_dim_assistant ON core.dim_assistant
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
CREATE POLICY site_isolation_dim_table ON core.dim_table
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
CREATE POLICY site_isolation_fact_settlement ON core.fact_settlement
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
CREATE POLICY site_isolation_fact_payment ON core.fact_payment
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
-- =============================================================================
-- 第四部分:授权
-- =============================================================================
-- 授予 app_reader 对 core 表的 SELECT 权限
GRANT SELECT ON ALL TABLES IN SCHEMA core TO app_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_reader;
-- 授予对 dws 表的 SELECT 权限(视图需要)
GRANT SELECT ON ALL TABLES IN SCHEMA dws TO app_reader;
-- 设置默认权限(未来新建的表自动授权)
ALTER DEFAULT PRIVILEGES IN SCHEMA core GRANT SELECT ON TABLES TO app_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT ON TABLES TO app_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA dws GRANT SELECT ON TABLES TO app_reader;

View File

@@ -1,161 +0,0 @@
-- =============================================================================
-- core schema DDL — 统一维度/事实最小字段集层
-- 说明:从 DWD 维度表和事实表提取跨系统共享的核心字段
-- 仅包含 ID、名称、状态、site_id 等最小字段集
-- 第一版保持精简,后续按需扩展
-- =============================================================================
CREATE SCHEMA IF NOT EXISTS core;
SET search_path TO core;
-- -----------------------------------------------------------------------------
-- 1. dim_site — 门店维度(核心字段)
-- 来源dwd.dim_site14 字段 → 6 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dim_site (
site_id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
shop_name TEXT NOT NULL,
site_label TEXT,
shop_status INTEGER,
site_id_alias BIGINT GENERATED ALWAYS AS (site_id) STORED
);
COMMENT ON TABLE core.dim_site IS '门店维度核心表:仅保留跨系统共享的最小字段集。';
COMMENT ON COLUMN core.dim_site.site_id IS '门店 ID主键';
COMMENT ON COLUMN core.dim_site.tenant_id IS '租户/品牌 ID。';
COMMENT ON COLUMN core.dim_site.shop_name IS '门店名称。';
COMMENT ON COLUMN core.dim_site.site_label IS '门店标签(如 A/B 店)。';
COMMENT ON COLUMN core.dim_site.shop_status IS '门店状态枚举。';
-- site_id_alias 用于 RLS 策略中统一使用 site_id 过滤
-- -----------------------------------------------------------------------------
-- 2. dim_member — 会员维度(核心字段)
-- 来源dwd.dim_member12 字段 → 8 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dim_member (
member_id BIGINT PRIMARY KEY,
system_member_id BIGINT,
tenant_id BIGINT NOT NULL,
register_site_id BIGINT NOT NULL,
mobile TEXT,
nickname TEXT,
member_card_grade_name TEXT,
status INTEGER
);
COMMENT ON TABLE core.dim_member IS '会员维度核心表:仅保留跨系统共享的最小字段集。';
COMMENT ON COLUMN core.dim_member.member_id IS '会员 ID主键';
COMMENT ON COLUMN core.dim_member.system_member_id IS '系统级会员 ID跨门店统一';
COMMENT ON COLUMN core.dim_member.tenant_id IS '租户/品牌 ID。';
COMMENT ON COLUMN core.dim_member.register_site_id IS '注册门店 ID等同 site_id';
COMMENT ON COLUMN core.dim_member.mobile IS '手机号。';
COMMENT ON COLUMN core.dim_member.nickname IS '昵称/姓名。';
COMMENT ON COLUMN core.dim_member.member_card_grade_name IS '会员卡等级名称。';
COMMENT ON COLUMN core.dim_member.status IS '会员状态。';
-- -----------------------------------------------------------------------------
-- 3. dim_assistant — 助教维度(核心字段)
-- 来源dwd.dim_assistant16 字段 → 9 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dim_assistant (
assistant_id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
site_id BIGINT NOT NULL,
real_name TEXT NOT NULL,
nickname TEXT,
mobile TEXT,
level INTEGER,
assistant_status INTEGER,
leave_status INTEGER
);
COMMENT ON TABLE core.dim_assistant IS '助教维度核心表:仅保留跨系统共享的最小字段集。';
COMMENT ON COLUMN core.dim_assistant.assistant_id IS '助教 ID主键';
COMMENT ON COLUMN core.dim_assistant.tenant_id IS '租户/品牌 ID。';
COMMENT ON COLUMN core.dim_assistant.site_id IS '门店 ID。';
COMMENT ON COLUMN core.dim_assistant.real_name IS '真实姓名。';
COMMENT ON COLUMN core.dim_assistant.nickname IS '昵称。';
COMMENT ON COLUMN core.dim_assistant.mobile IS '手机号。';
COMMENT ON COLUMN core.dim_assistant.level IS '助教等级。';
COMMENT ON COLUMN core.dim_assistant.assistant_status IS '助教状态。';
COMMENT ON COLUMN core.dim_assistant.leave_status IS '离职状态。';
-- -----------------------------------------------------------------------------
-- 4. dim_table — 台桌维度(核心字段)
-- 来源dwd.dim_table8 字段 → 5 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dim_table (
table_id BIGINT PRIMARY KEY,
site_id BIGINT NOT NULL,
table_name TEXT NOT NULL,
site_table_area_name TEXT,
table_price NUMERIC(18,2)
);
COMMENT ON TABLE core.dim_table IS '台桌维度核心表:仅保留跨系统共享的最小字段集。';
COMMENT ON COLUMN core.dim_table.table_id IS '台桌 ID主键';
COMMENT ON COLUMN core.dim_table.site_id IS '门店 ID。';
COMMENT ON COLUMN core.dim_table.table_name IS '台桌名称。';
COMMENT ON COLUMN core.dim_table.site_table_area_name IS '区域名称。';
COMMENT ON COLUMN core.dim_table.table_price IS '台桌单价。';
-- -----------------------------------------------------------------------------
-- 5. dim_goods_category — 商品分类维度(核心字段)
-- 来源dwd.dim_goods_category10 字段 → 5 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dim_goods_category (
category_id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
category_name TEXT NOT NULL,
parent_id BIGINT,
level INTEGER
);
COMMENT ON TABLE core.dim_goods_category IS '商品分类维度核心表。';
-- -----------------------------------------------------------------------------
-- 6. fact_settlement — 结算事实(核心字段)
-- 来源dwd.dwd_settlement_head约 30 字段 → 12 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS fact_settlement (
order_settle_id BIGINT PRIMARY KEY,
site_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
order_trade_no BIGINT,
member_id BIGINT,
total_amount NUMERIC(18,2),
actual_amount NUMERIC(18,2),
discount_amount NUMERIC(18,2),
pay_status INTEGER,
settle_time TIMESTAMPTZ,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ
);
COMMENT ON TABLE core.fact_settlement IS '结算事实核心表:仅保留跨系统共享的最小字段集。';
COMMENT ON COLUMN core.fact_settlement.order_settle_id IS '结账单 ID主键';
COMMENT ON COLUMN core.fact_settlement.site_id IS '门店 ID。';
COMMENT ON COLUMN core.fact_settlement.tenant_id IS '租户 ID。';
COMMENT ON COLUMN core.fact_settlement.total_amount IS '应收总额。';
COMMENT ON COLUMN core.fact_settlement.actual_amount IS '实收金额。';
COMMENT ON COLUMN core.fact_settlement.discount_amount IS '优惠金额。';
-- -----------------------------------------------------------------------------
-- 7. fact_payment — 支付事实(核心字段)
-- 来源dwd.dwd_payment约 12 字段 → 7 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS fact_payment (
payment_id BIGINT PRIMARY KEY,
site_id BIGINT NOT NULL,
order_settle_id BIGINT,
pay_type INTEGER,
pay_amount NUMERIC(18,2),
pay_time TIMESTAMPTZ,
status INTEGER
);
COMMENT ON TABLE core.fact_payment IS '支付事实核心表。';
-- -----------------------------------------------------------------------------
-- 索引
-- -----------------------------------------------------------------------------
CREATE INDEX IF NOT EXISTS idx_core_member_site ON core.dim_member (register_site_id);
CREATE INDEX IF NOT EXISTS idx_core_assistant_site ON core.dim_assistant (site_id);
CREATE INDEX IF NOT EXISTS idx_core_table_site ON core.dim_table (site_id);
CREATE INDEX IF NOT EXISTS idx_core_settlement_site ON core.fact_settlement (site_id);
CREATE INDEX IF NOT EXISTS idx_core_settlement_time ON core.fact_settlement (settle_time);
CREATE INDEX IF NOT EXISTS idx_core_payment_site ON core.fact_payment (site_id);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,105 +0,0 @@
-- 文件说明meta 调度元数据 DDL独立文件便于初始化任务单独执行
-- 包含任务注册表、游标表、运行记录表;字段注释使用中文。
CREATE SCHEMA IF NOT EXISTS meta;
CREATE TABLE IF NOT EXISTS meta.etl_task (
task_id BIGSERIAL PRIMARY KEY,
task_code TEXT NOT NULL,
store_id BIGINT NOT NULL,
enabled BOOLEAN DEFAULT TRUE,
cursor_field TEXT,
window_minutes_default INT DEFAULT 30,
overlap_seconds INT DEFAULT 600,
page_size INT DEFAULT 200,
retry_max INT DEFAULT 3,
params JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (task_code, store_id)
);
COMMENT ON TABLE meta.etl_task IS '任务注册表:调度依据的任务清单(与 task_registry 中的任务码对应)。';
COMMENT ON COLUMN meta.etl_task.task_code IS '任务编码,需与代码中的任务码一致。';
COMMENT ON COLUMN meta.etl_task.store_id IS '门店/租户粒度,区分多门店执行。';
COMMENT ON COLUMN meta.etl_task.enabled IS '是否启用此任务。';
COMMENT ON COLUMN meta.etl_task.cursor_field IS '增量游标字段名(可选)。';
COMMENT ON COLUMN meta.etl_task.window_minutes_default IS '默认时间窗口(分钟)。';
COMMENT ON COLUMN meta.etl_task.overlap_seconds IS '窗口重叠秒数,用于防止遗漏。';
COMMENT ON COLUMN meta.etl_task.page_size IS '默认分页大小。';
COMMENT ON COLUMN meta.etl_task.retry_max IS 'API重试次数上限。';
COMMENT ON COLUMN meta.etl_task.params IS '任务级自定义参数 JSON。';
COMMENT ON COLUMN meta.etl_task.created_at IS '创建时间。';
COMMENT ON COLUMN meta.etl_task.updated_at IS '更新时间。';
CREATE TABLE IF NOT EXISTS meta.etl_cursor (
cursor_id BIGSERIAL PRIMARY KEY,
task_id BIGINT NOT NULL REFERENCES meta.etl_task(task_id) ON DELETE CASCADE,
store_id BIGINT NOT NULL,
last_start TIMESTAMPTZ,
last_end TIMESTAMPTZ,
last_id BIGINT,
last_run_id BIGINT,
extra JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (task_id, store_id)
);
COMMENT ON TABLE meta.etl_cursor IS '任务游标表:记录每个任务/门店的增量窗口及最后 run。';
COMMENT ON COLUMN meta.etl_cursor.task_id IS '关联 etl_task.task_id。';
COMMENT ON COLUMN meta.etl_cursor.store_id IS '门店/租户粒度。';
COMMENT ON COLUMN meta.etl_cursor.last_start IS '上次窗口开始时间(含重叠偏移)。';
COMMENT ON COLUMN meta.etl_cursor.last_end IS '上次窗口结束时间。';
COMMENT ON COLUMN meta.etl_cursor.last_id IS '上次处理的最大主键/游标值(可选)。';
COMMENT ON COLUMN meta.etl_cursor.last_run_id IS '上次运行ID对应 etl_run.run_id。';
COMMENT ON COLUMN meta.etl_cursor.extra IS '附加游标信息 JSON。';
COMMENT ON COLUMN meta.etl_cursor.created_at IS '创建时间。';
COMMENT ON COLUMN meta.etl_cursor.updated_at IS '更新时间。';
CREATE TABLE IF NOT EXISTS meta.etl_run (
run_id BIGSERIAL PRIMARY KEY,
run_uuid TEXT NOT NULL,
task_id BIGINT NOT NULL REFERENCES meta.etl_task(task_id) ON DELETE CASCADE,
store_id BIGINT NOT NULL,
status TEXT NOT NULL,
started_at TIMESTAMPTZ DEFAULT now(),
ended_at TIMESTAMPTZ,
window_start TIMESTAMPTZ,
window_end TIMESTAMPTZ,
window_minutes INT,
overlap_seconds INT,
fetched_count INT DEFAULT 0,
loaded_count INT DEFAULT 0,
updated_count INT DEFAULT 0,
skipped_count INT DEFAULT 0,
error_count INT DEFAULT 0,
unknown_fields INT DEFAULT 0,
export_dir TEXT,
log_path TEXT,
request_params JSONB DEFAULT '{}'::jsonb,
manifest JSONB DEFAULT '{}'::jsonb,
error_message TEXT,
extra JSONB DEFAULT '{}'::jsonb
);
COMMENT ON TABLE meta.etl_run IS '运行记录表:记录每次任务执行的窗口、状态、计数与日志路径。';
COMMENT ON COLUMN meta.etl_run.run_uuid IS '本次调度的唯一标识。';
COMMENT ON COLUMN meta.etl_run.task_id IS '关联 etl_task.task_id。';
COMMENT ON COLUMN meta.etl_run.store_id IS '门店/租户粒度。';
COMMENT ON COLUMN meta.etl_run.status IS '运行状态SUCC/FAIL/PARTIAL 等)。';
COMMENT ON COLUMN meta.etl_run.started_at IS '开始时间。';
COMMENT ON COLUMN meta.etl_run.ended_at IS '结束时间。';
COMMENT ON COLUMN meta.etl_run.window_start IS '本次窗口开始时间。';
COMMENT ON COLUMN meta.etl_run.window_end IS '本次窗口结束时间。';
COMMENT ON COLUMN meta.etl_run.window_minutes IS '窗口跨度(分钟)。';
COMMENT ON COLUMN meta.etl_run.overlap_seconds IS '窗口重叠秒数。';
COMMENT ON COLUMN meta.etl_run.fetched_count IS '抓取/读取的记录数。';
COMMENT ON COLUMN meta.etl_run.loaded_count IS '插入的记录数。';
COMMENT ON COLUMN meta.etl_run.updated_count IS '更新的记录数。';
COMMENT ON COLUMN meta.etl_run.skipped_count IS '跳过的记录数。';
COMMENT ON COLUMN meta.etl_run.error_count IS '错误记录数。';
COMMENT ON COLUMN meta.etl_run.unknown_fields IS '未知字段计数(清洗阶段)。';
COMMENT ON COLUMN meta.etl_run.export_dir IS '抓取/导出目录。';
COMMENT ON COLUMN meta.etl_run.log_path IS '日志路径。';
COMMENT ON COLUMN meta.etl_run.request_params IS '请求参数 JSON。';
COMMENT ON COLUMN meta.etl_run.manifest IS '运行产出清单/统计 JSON。';
COMMENT ON COLUMN meta.etl_run.error_message IS '错误信息(若失败)。';
COMMENT ON COLUMN meta.etl_run.extra IS '附加字段,保留扩展。';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,105 +0,0 @@
-- 文件说明etl_admin 调度元数据 DDL独立文件便于初始化任务单独执行
-- 包含任务注册表、游标表、运行记录表;字段注释使用中文。
CREATE SCHEMA IF NOT EXISTS etl_admin;
CREATE TABLE IF NOT EXISTS etl_admin.etl_task (
task_id BIGSERIAL PRIMARY KEY,
task_code TEXT NOT NULL,
store_id BIGINT NOT NULL,
enabled BOOLEAN DEFAULT TRUE,
cursor_field TEXT,
window_minutes_default INT DEFAULT 30,
overlap_seconds INT DEFAULT 600,
page_size INT DEFAULT 200,
retry_max INT DEFAULT 3,
params JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (task_code, store_id)
);
COMMENT ON TABLE etl_admin.etl_task IS '任务注册表:调度依据的任务清单(与 task_registry 中的任务码对应)。';
COMMENT ON COLUMN etl_admin.etl_task.task_code IS '任务编码,需与代码中的任务码一致。';
COMMENT ON COLUMN etl_admin.etl_task.store_id IS '门店/租户粒度,区分多门店执行。';
COMMENT ON COLUMN etl_admin.etl_task.enabled IS '是否启用此任务。';
COMMENT ON COLUMN etl_admin.etl_task.cursor_field IS '增量游标字段名(可选)。';
COMMENT ON COLUMN etl_admin.etl_task.window_minutes_default IS '默认时间窗口(分钟)。';
COMMENT ON COLUMN etl_admin.etl_task.overlap_seconds IS '窗口重叠秒数,用于防止遗漏。';
COMMENT ON COLUMN etl_admin.etl_task.page_size IS '默认分页大小。';
COMMENT ON COLUMN etl_admin.etl_task.retry_max IS 'API重试次数上限。';
COMMENT ON COLUMN etl_admin.etl_task.params IS '任务级自定义参数 JSON。';
COMMENT ON COLUMN etl_admin.etl_task.created_at IS '创建时间。';
COMMENT ON COLUMN etl_admin.etl_task.updated_at IS '更新时间。';
CREATE TABLE IF NOT EXISTS etl_admin.etl_cursor (
cursor_id BIGSERIAL PRIMARY KEY,
task_id BIGINT NOT NULL REFERENCES etl_admin.etl_task(task_id) ON DELETE CASCADE,
store_id BIGINT NOT NULL,
last_start TIMESTAMPTZ,
last_end TIMESTAMPTZ,
last_id BIGINT,
last_run_id BIGINT,
extra JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (task_id, store_id)
);
COMMENT ON TABLE etl_admin.etl_cursor IS '任务游标表:记录每个任务/门店的增量窗口及最后 run。';
COMMENT ON COLUMN etl_admin.etl_cursor.task_id IS '关联 etl_task.task_id。';
COMMENT ON COLUMN etl_admin.etl_cursor.store_id IS '门店/租户粒度。';
COMMENT ON COLUMN etl_admin.etl_cursor.last_start IS '上次窗口开始时间(含重叠偏移)。';
COMMENT ON COLUMN etl_admin.etl_cursor.last_end IS '上次窗口结束时间。';
COMMENT ON COLUMN etl_admin.etl_cursor.last_id IS '上次处理的最大主键/游标值(可选)。';
COMMENT ON COLUMN etl_admin.etl_cursor.last_run_id IS '上次运行ID对应 etl_run.run_id。';
COMMENT ON COLUMN etl_admin.etl_cursor.extra IS '附加游标信息 JSON。';
COMMENT ON COLUMN etl_admin.etl_cursor.created_at IS '创建时间。';
COMMENT ON COLUMN etl_admin.etl_cursor.updated_at IS '更新时间。';
CREATE TABLE IF NOT EXISTS etl_admin.etl_run (
run_id BIGSERIAL PRIMARY KEY,
run_uuid TEXT NOT NULL,
task_id BIGINT NOT NULL REFERENCES etl_admin.etl_task(task_id) ON DELETE CASCADE,
store_id BIGINT NOT NULL,
status TEXT NOT NULL,
started_at TIMESTAMPTZ DEFAULT now(),
ended_at TIMESTAMPTZ,
window_start TIMESTAMPTZ,
window_end TIMESTAMPTZ,
window_minutes INT,
overlap_seconds INT,
fetched_count INT DEFAULT 0,
loaded_count INT DEFAULT 0,
updated_count INT DEFAULT 0,
skipped_count INT DEFAULT 0,
error_count INT DEFAULT 0,
unknown_fields INT DEFAULT 0,
export_dir TEXT,
log_path TEXT,
request_params JSONB DEFAULT '{}'::jsonb,
manifest JSONB DEFAULT '{}'::jsonb,
error_message TEXT,
extra JSONB DEFAULT '{}'::jsonb
);
COMMENT ON TABLE etl_admin.etl_run IS '运行记录表:记录每次任务执行的窗口、状态、计数与日志路径。';
COMMENT ON COLUMN etl_admin.etl_run.run_uuid IS '本次调度的唯一标识。';
COMMENT ON COLUMN etl_admin.etl_run.task_id IS '关联 etl_task.task_id。';
COMMENT ON COLUMN etl_admin.etl_run.store_id IS '门店/租户粒度。';
COMMENT ON COLUMN etl_admin.etl_run.status IS '运行状态SUCC/FAIL/PARTIAL 等)。';
COMMENT ON COLUMN etl_admin.etl_run.started_at IS '开始时间。';
COMMENT ON COLUMN etl_admin.etl_run.ended_at IS '结束时间。';
COMMENT ON COLUMN etl_admin.etl_run.window_start IS '本次窗口开始时间。';
COMMENT ON COLUMN etl_admin.etl_run.window_end IS '本次窗口结束时间。';
COMMENT ON COLUMN etl_admin.etl_run.window_minutes IS '窗口跨度(分钟)。';
COMMENT ON COLUMN etl_admin.etl_run.overlap_seconds IS '窗口重叠秒数。';
COMMENT ON COLUMN etl_admin.etl_run.fetched_count IS '抓取/读取的记录数。';
COMMENT ON COLUMN etl_admin.etl_run.loaded_count IS '插入的记录数。';
COMMENT ON COLUMN etl_admin.etl_run.updated_count IS '更新的记录数。';
COMMENT ON COLUMN etl_admin.etl_run.skipped_count IS '跳过的记录数。';
COMMENT ON COLUMN etl_admin.etl_run.error_count IS '错误记录数。';
COMMENT ON COLUMN etl_admin.etl_run.unknown_fields IS '未知字段计数(清洗阶段)。';
COMMENT ON COLUMN etl_admin.etl_run.export_dir IS '抓取/导出目录。';
COMMENT ON COLUMN etl_admin.etl_run.log_path IS '日志路径。';
COMMENT ON COLUMN etl_admin.etl_run.request_params IS '请求参数 JSON。';
COMMENT ON COLUMN etl_admin.etl_run.manifest IS '运行产出清单/统计 JSON。';
COMMENT ON COLUMN etl_admin.etl_run.error_message IS '错误信息(若失败)。';
COMMENT ON COLUMN etl_admin.etl_run.extra IS '附加字段,保留扩展。';

View File

@@ -1,173 +0,0 @@
SET client_encoding TO "UTF8";
-- ============================================================================
-- 校验性能索引ODS / DWD
-- ----------------------------------------------------------------------------
-- 用途:
-- 1) 加速校验查询(主键查找、窗口扫描、当前版本扫描)。
-- 2) 保持数据语义不变(仅添加索引 + ANALYZE不改写业务数据
--
-- 注意事项:
-- 1) 本脚本具有幂等性(`CREATE INDEX IF NOT EXISTS`)。
-- 2) 如有严格的在线 DDL 要求,请手动使用 `CREATE INDEX CONCURRENTLY`
-- 在维护安全模式下执行(不可在事务块内运行)。
-- ============================================================================
DO $$
DECLARE
rec RECORD;
pk_cols TEXT[];
pk_cols_sql TEXT;
idx_name TEXT;
BEGIN
FOR rec IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'billiards_ods'
AND table_type = 'BASE TABLE'
LOOP
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'billiards_ods'
AND table_name = rec.table_name
AND column_name = 'fetched_at'
) THEN
idx_name := left(format('idx_%s_vfy_fetched_at', rec.table_name), 50)
|| '_' || substr(md5(rec.table_name || '_vfy_fetched_at'), 1, 8);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON billiards_ods.%I (fetched_at)',
idx_name, rec.table_name
);
SELECT array_agg(kcu.column_name ORDER BY kcu.ordinal_position)
INTO pk_cols
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.table_schema = kcu.table_schema
AND tc.table_name = kcu.table_name
AND tc.constraint_name = kcu.constraint_name
WHERE tc.table_schema = 'billiards_ods'
AND tc.table_name = rec.table_name
AND tc.constraint_type = 'PRIMARY KEY';
IF pk_cols IS NOT NULL AND coalesce(array_length(pk_cols, 1), 0) <= 3 THEN
SELECT string_agg(format('%I', c), ', ')
INTO pk_cols_sql
FROM unnest(pk_cols) AS c;
idx_name := left(format('idx_%s_vfy_fetched_pk', rec.table_name), 50)
|| '_' || substr(md5(rec.table_name || '_vfy_fetched_pk'), 1, 8);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON billiards_ods.%I (fetched_at, %s)',
idx_name, rec.table_name, pk_cols_sql
);
END IF;
END IF;
END LOOP;
END
$$;
DO $$
DECLARE
rec RECORD;
tcol TEXT;
pk_cols TEXT[];
pk_cols_sql TEXT;
idx_name TEXT;
time_candidates TEXT[] := ARRAY[
'pay_time',
'create_time',
'start_use_time',
'scd2_start_time',
'calc_time',
'order_date',
'fetched_at'
];
BEGIN
FOR rec IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'billiards_dwd'
AND table_type = 'BASE TABLE'
LOOP
SELECT array_agg(kcu.column_name ORDER BY kcu.ordinal_position)
INTO pk_cols
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.table_schema = kcu.table_schema
AND tc.table_name = kcu.table_name
AND tc.constraint_name = kcu.constraint_name
WHERE tc.table_schema = 'billiards_dwd'
AND tc.table_name = rec.table_name
AND tc.constraint_type = 'PRIMARY KEY';
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'billiards_dwd'
AND table_name = rec.table_name
AND column_name = 'scd2_is_current'
) AND pk_cols IS NOT NULL
AND coalesce(array_length(pk_cols, 1), 0) BETWEEN 1 AND 4 THEN
SELECT string_agg(format('%I', c), ', ')
INTO pk_cols_sql
FROM unnest(pk_cols) AS c;
idx_name := left(format('idx_%s_vfy_pk_current', rec.table_name), 50)
|| '_' || substr(md5(rec.table_name || '_vfy_pk_current'), 1, 8);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON billiards_dwd.%I (%s, scd2_is_current)',
idx_name, rec.table_name, pk_cols_sql
);
END IF;
FOREACH tcol IN ARRAY time_candidates
LOOP
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'billiards_dwd'
AND table_name = rec.table_name
AND column_name = tcol
) THEN
idx_name := left(format('idx_%s_vfy_%s', rec.table_name, tcol), 50)
|| '_' || substr(md5(rec.table_name || '_vfy_' || tcol), 1, 8);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON billiards_dwd.%I (%I)',
idx_name, rec.table_name, tcol
);
IF pk_cols IS NOT NULL AND coalesce(array_length(pk_cols, 1), 0) <= 3 THEN
SELECT string_agg(format('%I', c), ', ')
INTO pk_cols_sql
FROM unnest(pk_cols) AS c;
idx_name := left(format('idx_%s_vfy_%s_pk', rec.table_name, tcol), 50)
|| '_' || substr(md5(rec.table_name || '_vfy_' || tcol || '_pk'), 1, 8);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON billiards_dwd.%I (%I, %s)',
idx_name, rec.table_name, tcol, pk_cols_sql
);
END IF;
END IF;
END LOOP;
END LOOP;
END
$$;
DO $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN
SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_schema IN ('billiards_ods', 'billiards_dwd')
AND table_type = 'BASE TABLE'
LOOP
EXECUTE format('ANALYZE %I.%I', rec.table_schema, rec.table_name);
END LOOP;
END
$$;

View File

@@ -1,116 +1,96 @@
-- =============================================================================
-- DWS 配置表初始数据
-- 版本: v3.0
-- 版本: v4.0
-- 创建日期: 2026-02-01
-- 更新日期: 2026-02-21
-- AI_CHANGELOG [2026-02-21] 取消全文注释,数据已写入 test_etl_feiqiu
-- 新增 2025-01-01~2026-02-28 统一提成档位基础课18元/小时打赏课40%
-- 新增 GUARANTEE 保底奖金规则按等级初级12000/中级16000/高级18000/星级23000
-- 历史分档口径截止日期调整为 2024-12-31
-- 描述: 初始化配置表数据,包含绩效档位、等级定价、奖金规则、区域分类、技能映射
-- =============================================================================
-- NOTE: 当前数据库 cfg_* 配置表为空(以数据库现状为准)
-- 下方默认配置仅作参考,已整体注释
/*
-- =============================================================================
-- 1. cfg_performance_tier - 绩效档位配置(含历史口径)
-- 数据来源DWS 数据库处理需求.md
-- 旧方案历史口径至2026-02-28:
-- 0档 淘汰压力 H <100 28 50% 3
-- 1档 及格档(重点激励) 100≤ H <130 18 40% 4
-- 2档 良好档(重点激励) 130≤ H <160 15 38% 4
-- 3档 优秀档 160≤ H <190 13 35% 5
-- 4档 卓越加速档(高端人才倾斜) 190≤ H <220 10 33% 6
-- 5档 冠军加速档(高端人才倾斜) H ≥220 8 30% 休假自由
-- 新方案2026-03-01起:
-- 0档 淘汰压力 H <120 28 50% 3
-- 1档 及格档 120≤ H <150 18 40% 4
-- 2档 良好档 150≤ H <180 13 35% 5
-- 3档 优秀档 180≤ H <210 10 30% 6
-- 4档 销冠竞争 H ≥210 8 25% 休假自由
-- 三段时间线:
-- 2000-01-01 ~ 2024-12-31: 旧方案6档阶梯抽成
-- 2025-01-01 ~ 2026-02-28: 统一提成不分档基础课18元/小时,打赏课40%
-- 2026-03-01 ~ 9999-12-31: 新方案5档阶梯抽成
-- =============================================================================
TRUNCATE TABLE dws.cfg_performance_tier RESTART IDENTITY CASCADE;
INSERT INTO dws.cfg_performance_tier (
tier_code, tier_name, tier_level,
min_hours, max_hours,
tier_code, tier_name, tier_level,
min_hours, max_hours,
base_deduction, bonus_deduction_ratio, vacation_days, vacation_unlimited,
is_new_hire_tier, effective_from, effective_to, description
) VALUES
-- 旧方案至2026-02-28
-- 0档 淘汰压力: H<100, 专业课抽成28元/小时, 打赏课抽成50%, 休假3天
('T0', '0档-淘汰压力', 0,
0, 100,
-- 旧方案至2024-12-31
('T0', '0档-淘汰压力', 0,
0, 100,
28.00, 0.50, 3, FALSE,
FALSE, '2000-01-01', '2026-02-28',
FALSE, '2000-01-01', '2024-12-31',
'旧方案H<100专业课抽成28元/小时打赏课抽成50%休假3天'),
-- 1档 及格档: 100≤H<130, 专业课抽成18元/小时, 打赏课抽成40%, 休假4天
('T1', '1档-及格档', 1,
100, 130,
('T1', '1档-及格档', 1,
100, 130,
18.00, 0.40, 4, FALSE,
FALSE, '2000-01-01', '2026-02-28',
FALSE, '2000-01-01', '2024-12-31',
'旧方案100≤H<130专业课抽成18元/小时打赏课抽成40%休假4天'),
-- 2档 良好档: 130≤H<160, 专业课抽成15元/小时, 打赏课抽成38%, 休假4天
('T2', '2档-良好档', 2,
130, 160,
('T2', '2档-良好档', 2,
130, 160,
15.00, 0.38, 4, FALSE,
FALSE, '2000-01-01', '2026-02-28',
FALSE, '2000-01-01', '2024-12-31',
'旧方案130≤H<160专业课抽成15元/小时打赏课抽成38%休假4天'),
-- 3档 优秀档: 160≤H<190, 专业课抽成13元/小时, 打赏课抽成35%, 休假5天
('T3', '3档-优秀档', 3,
160, 190,
('T3', '3档-优秀档', 3,
160, 190,
13.00, 0.35, 5, FALSE,
FALSE, '2000-01-01', '2026-02-28',
FALSE, '2000-01-01', '2024-12-31',
'旧方案160≤H<190专业课抽成13元/小时打赏课抽成35%休假5天'),
-- 4档 卓越加速档: 190≤H<220, 专业课抽成10元/小时, 打赏课抽成33%, 休假6天
('T4', '4档-卓越加速档', 4,
190, 220,
('T4', '4档-卓越加速档', 4,
190, 220,
10.00, 0.33, 6, FALSE,
FALSE, '2000-01-01', '2026-02-28',
FALSE, '2000-01-01', '2024-12-31',
'旧方案190≤H<220专业课抽成10元/小时打赏课抽成33%休假6天'),
-- 5档 冠军加速档: H≥220, 专业课抽成8元/小时, 打赏课抽成30%, 休假自由
('T5', '5档-冠军加速档', 5,
220, NULL,
('T5', '5档-冠军加速档', 5,
220, NULL,
8.00, 0.30, 0, TRUE,
FALSE, '2000-01-01', '2026-02-28',
FALSE, '2000-01-01', '2024-12-31',
'旧方案H≥220专业课抽成8元/小时打赏课抽成30%,休假自由'),
-- 2025-01-01 ~ 2026-02-28: 统一提成(不分档,所有助教统一规则)
-- CHANGE 2026-02-21 | 新增统一提成档位基础课球房提成18元/小时打赏课球房提成40%
('T0', '统一档', 0,
0, NULL,
18.00, 0.40, 0, FALSE,
FALSE, '2025-01-01', '2026-02-28',
'2025-01-01~2026-02-28统一规则基础课球房提成18元/小时打赏课球房提成40%,不分档位'),
-- 新方案2026-03-01起
-- 0档 淘汰压力: H<120, 专业课抽成28元/小时, 打赏课抽成50%, 休假3天
('T0', '0档-淘汰压力', 0,
0, 120,
('T0', '0档-淘汰压力', 0,
0, 120,
28.00, 0.50, 3, FALSE,
FALSE, '2026-03-01', '9999-12-31',
FALSE, '2026-03-01', '9999-12-31',
'新方案H<120专业课抽成28元/小时打赏课抽成50%休假3天'),
-- 1档 及格档: 120≤H<150, 专业课抽成18元/小时, 打赏课抽成40%, 休假4天
('T1', '1档-及格档', 1,
120, 150,
('T1', '1档-及格档', 1,
120, 150,
18.00, 0.40, 4, FALSE,
FALSE, '2026-03-01', '9999-12-31',
FALSE, '2026-03-01', '9999-12-31',
'新方案120≤H<150专业课抽成18元/小时打赏课抽成40%休假4天'),
-- 2档 良好档: 150≤H<180, 专业课抽成13元/小时, 打赏课抽成35%, 休假5天
('T2', '2档-良好档', 2,
150, 180,
('T2', '2档-良好档', 2,
150, 180,
13.00, 0.35, 5, FALSE,
FALSE, '2026-03-01', '9999-12-31',
FALSE, '2026-03-01', '9999-12-31',
'新方案150≤H<180专业课抽成13元/小时打赏课抽成35%休假5天'),
-- 3档 优秀档: 180≤H<210, 专业课抽成10元/小时, 打赏课抽成30%, 休假6天
('T3', '3档-优秀档', 3,
180, 210,
('T3', '3档-优秀档', 3,
180, 210,
10.00, 0.30, 6, FALSE,
FALSE, '2026-03-01', '9999-12-31',
FALSE, '2026-03-01', '9999-12-31',
'新方案180≤H<210专业课抽成10元/小时打赏课抽成30%休假6天'),
-- 4档 销冠竞争: H≥210, 专业课抽成8元/小时, 打赏课抽成25%, 休假自由
('T4', '4档-销冠竞争', 4,
210, NULL,
('T4', '4档-销冠竞争', 4,
210, NULL,
8.00, 0.25, 0, TRUE,
FALSE, '2026-03-01', '9999-12-31',
FALSE, '2026-03-01', '9999-12-31',
'新方案H≥210专业课抽成8元/小时打赏课抽成25%,休假自由');
@@ -121,51 +101,46 @@ INSERT INTO dws.cfg_performance_tier (
-- - 8=助教管理, 10=初级, 20=中级, 30=高级, 40=星级
-- - 价格为客户支付价格(对外价格),助教收入=客户支付-档位抽成
-- - 包厢课基础课统一138元/小时(不随等级变化)
-- - 数据来源DWS 数据库处理需求.md
-- =============================================================================
TRUNCATE TABLE dws.cfg_assistant_level_price RESTART IDENTITY CASCADE;
INSERT INTO dws.cfg_assistant_level_price (
level_code, level_name,
level_code, level_name,
base_course_price, bonus_course_price,
effective_from, effective_to, description
) VALUES
-- 初级助教基础课对客户收费98元/小时
(10, '初级',
(10, '初级',
98.00, 190.00,
'2000-01-01', '9999-12-31',
'2000-01-01', '9999-12-31',
'初级助教基础课98元/时附加课190元/时(客户支付价格)'),
-- 中级助教基础课对客户收费108元/小时
(20, '中级',
(20, '中级',
108.00, 190.00,
'2000-01-01', '9999-12-31',
'2000-01-01', '9999-12-31',
'中级助教基础课108元/时附加课190元/时(客户支付价格)'),
-- 高级助教基础课对客户收费118元/小时
(30, '高级',
(30, '高级',
118.00, 190.00,
'2000-01-01', '9999-12-31',
'2000-01-01', '9999-12-31',
'高级助教基础课118元/时附加课190元/时(客户支付价格)'),
-- 星级助教基础课对客户收费138元/小时
(40, '星级',
(40, '星级',
138.00, 190.00,
'2000-01-01', '9999-12-31',
'2000-01-01', '9999-12-31',
'星级助教基础课138元/时附加课190元/时(客户支付价格)'),
-- 助教管理level_code=8通常不参与客户服务计费此处设置默认值
(8, '助教管理',
(8, '助教管理',
98.00, 190.00,
'2000-01-01', '9999-12-31',
'2000-01-01', '9999-12-31',
'助教管理:不参与客户服务计费,默认按初级价格');
-- =============================================================================
-- 3. cfg_bonus_rules - 奖金规则配置
-- 说明:
-- - SPRINT: 冲刺奖金历史口径至2026-02-28
-- - SPRINT: 冲刺奖金历史口径至2024-12-31
-- - GUARANTEE: 保底月薪线2025-01-01~2026-02-28按等级区分
-- * 保底规则:总课时达标 + 打赏课≥10小时 → 触发保底月薪线
-- * 保底含义:实发 = MAX(课时收入+奖金, 保底金额),非额外奖金
-- * rule_code 中 LV10/LV20/LV30/LV40 对应 level_code
-- - TOP_RANK: Top3排名奖金2026-03-01起
-- CHANGE 2026-02-21 | 新增 GUARANTEE 保底奖金规则
-- =============================================================================
TRUNCATE TABLE dws.cfg_bonus_rules RESTART IDENTITY CASCADE;
@@ -175,39 +150,56 @@ INSERT INTO dws.cfg_bonus_rules (
is_cumulative, priority,
effective_from, effective_to, description
) VALUES
-- 冲刺奖金: H>=190 得300元(历史口径)
-- 冲刺奖金(历史口径至2024-12-31
('SPRINT', 'SPRINT_190', '冲刺奖金190',
190.00, NULL, 300.00,
FALSE, 1,
'2000-01-01', '2026-02-28',
'2000-01-01', '2024-12-31',
'历史口径业绩≥190小时获得300元冲刺奖金不累计'),
-- 冲刺奖金: H>=220 得800元历史口径优先级更高覆盖190档
('SPRINT', 'SPRINT_220', '冲刺奖金220',
220.00, NULL, 800.00,
FALSE, 2,
'2000-01-01', '2026-02-28',
'2000-01-01', '2024-12-31',
'历史口径业绩≥220小时获得800元冲刺奖金覆盖190档'),
-- Top1排名奖金: 1000元2026-03-01起
-- 保底奖金2025-01-01 ~ 2026-02-28
-- 按助教等级区分需同时满足总课时和打赏课最低时数≥10小时
('GUARANTEE', 'GUAR_LV10', '初级保底奖金',
130.00, NULL, 12000.00,
FALSE, 10,
'2025-01-01', '2026-02-28',
'初级保底完成130小时课程含≥10小时打赏课保底月薪线12000元实发=MAX(课时收入+奖金, 12000)'),
('GUARANTEE', 'GUAR_LV20', '中级保底奖金',
150.00, NULL, 16000.00,
FALSE, 20,
'2025-01-01', '2026-02-28',
'中级保底完成150小时课程含≥10小时打赏课保底月薪线16000元实发=MAX(课时收入+奖金, 16000)'),
('GUARANTEE', 'GUAR_LV30', '高级保底奖金',
160.00, NULL, 18000.00,
FALSE, 30,
'2025-01-01', '2026-02-28',
'高级保底完成160小时课程含≥10小时打赏课保底月薪线18000元实发=MAX(课时收入+奖金, 18000)'),
('GUARANTEE', 'GUAR_LV40', '星级保底奖金',
170.00, NULL, 23000.00,
FALSE, 40,
'2025-01-01', '2026-02-28',
'星级保底完成170小时课程含≥10小时打赏课保底月薪线23000元实发=MAX(课时收入+奖金, 23000)'),
-- Top排名奖金2026-03-01起
('TOP_RANK', 'TOP_1', 'Top1排名奖金',
NULL, 1, 1000.00,
FALSE, 0,
'2026-03-01', '9999-12-31',
'2026-03-01', '9999-12-31',
'月度排名第一获得1000元并列都算'),
-- Top2排名奖金: 600元2026-03-01起
('TOP_RANK', 'TOP_2', 'Top2排名奖金',
NULL, 2, 600.00,
FALSE, 0,
'2026-03-01', '9999-12-31',
'2026-03-01', '9999-12-31',
'月度排名第二获得600元并列都算'),
-- Top3排名奖金: 400元2026-03-01起
('TOP_RANK', 'TOP_3', 'Top3排名奖金',
NULL, 3, 400.00,
FALSE, 0,
'2026-03-01', '9999-12-31',
'2026-03-01', '9999-12-31',
'月度排名第三获得400元并列都算');
@@ -217,14 +209,6 @@ INSERT INTO dws.cfg_bonus_rules (
-- - 将 dim_table.site_table_area_name 映射到财务报表区域分类
-- - 映射规则: 精确匹配 > 模糊匹配 > 默认兜底
-- - 数据来源: BD_manual_dim_table.md 中的 site_table_area_name 实际分布
-- 分类设计:
-- - BILLIARD: 台球散台A区/B区/C区/TV台
-- - BILLIARD_VIP: 台球VIP包厢
-- - SNOOKER: 斯诺克区
-- - MAHJONG: 麻将区
-- - KTV: K歌/KTV
-- - SPECIAL: 特殊(补时长等)
-- - OTHER: 其他
-- =============================================================================
TRUNCATE TABLE dws.cfg_area_category RESTART IDENTITY CASCADE;
@@ -232,63 +216,35 @@ INSERT INTO dws.cfg_area_category (
source_area_name, category_code, category_name,
match_type, match_priority, is_active, description
) VALUES
-- ============ 台球散台(精确匹配)============
('A区', 'BILLIARD', '台球散台',
'EXACT', 10, TRUE, '台球散台:A18台)- 中八/追分'),
('B区', 'BILLIARD', '台球散台',
'EXACT', 10, TRUE, '台球散台:B区15台)- 中八/追分'),
('C区', 'BILLIARD', '台球散台',
'EXACT', 10, TRUE, '台球散台C区6台- 中八/追分'),
('TV台', 'BILLIARD', '台球散台',
'EXACT', 10, TRUE, '台球散台TV台1台- 中八/追分'),
-- ============ 台球VIP包厢精确匹配============
('VIP包厢', 'BILLIARD_VIP', '台球VIP',
'EXACT', 10, TRUE, '台球VIPVIP包厢4台- V1-V4中八, V5斯诺克'),
-- ============ 斯诺克区(精确匹配)============
('斯诺克区', 'SNOOKER', '斯诺克',
'EXACT', 10, TRUE, '斯诺克斯诺克区4台'),
-- ============ 麻将区(精确匹配)============
('麻将房', 'MAHJONG', '麻将棋牌',
'EXACT', 10, TRUE, '麻将棋牌麻将房5台'),
('M7', 'MAHJONG', '麻将棋牌',
'EXACT', 10, TRUE, '麻将棋牌M72台'),
('M8', 'MAHJONG', '麻将棋牌',
'EXACT', 10, TRUE, '麻将棋牌M81台'),
('666', 'MAHJONG', '麻将棋牌',
'EXACT', 10, TRUE, '麻将棋牌6662台'),
('发财', 'MAHJONG', '麻将棋牌',
'EXACT', 10, TRUE, '麻将棋牌发财1台'),
-- ============ KTV/K包精确匹配============
('K包', 'KTV', 'K歌娱乐',
'EXACT', 10, TRUE, 'K歌娱乐K包4台'),
('k包活动区', 'KTV', 'K歌娱乐',
'EXACT', 10, TRUE, 'K歌娱乐k包活动区2台'),
('幸会158', 'KTV', 'K歌娱乐',
'EXACT', 10, TRUE, 'K歌娱乐幸会1582台'),
-- ============ 特殊区域(精确匹配)============
('补时长', 'SPECIAL', '补时长',
'EXACT', 10, TRUE, '特殊补时长7台- 用于时长补录'),
-- ============ 模糊匹配规则(优先级较低)============
('%VIP%', 'BILLIARD_VIP', '台球VIP',
'LIKE', 50, TRUE, '模糊匹配:包含"VIP"的区域'),
('%斯诺克%', 'SNOOKER', '斯诺克',
'LIKE', 50, TRUE, '模糊匹配:包含"斯诺克"的区域'),
('%麻将%', 'MAHJONG', '麻将棋牌',
'LIKE', 50, TRUE, '模糊匹配:包含"麻将"的区域'),
('%K包%', 'KTV', 'K歌娱乐',
'LIKE', 50, TRUE, '模糊匹配:包含"K包"的区域'),
('%KTV%', 'KTV', 'K歌娱乐',
'LIKE', 50, TRUE, '模糊匹配:包含"KTV"的区域'),
-- ============ 默认兜底(优先级最低)============
('DEFAULT', 'OTHER', '其他',
'DEFAULT', 999, TRUE, '兜底规则:无法匹配的区域归入其他');
-- 台球散台(精确匹配)
('A区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台A区18台- 中八/追分'),
('B区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:B15台)- 中八/追分'),
('C区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台C区6台- 中八/追分'),
('TV台', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:TV台1台- 中八/追分'),
-- 台球VIP包厢
('VIP包厢', 'BILLIARD_VIP', '台球VIP', 'EXACT', 10, TRUE, '台球VIPVIP包厢4台- V1-V4中八, V5斯诺克'),
-- 斯诺克区
('斯诺克区', 'SNOOKER', '斯诺克', 'EXACT', 10, TRUE, '斯诺克斯诺克区4台'),
-- 麻将区
('麻将房', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌麻将房5台'),
('M7', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌M72台'),
('M8', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌M81台'),
('666', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌6662台'),
('发财', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌发财1台'),
-- KTV/K包
('K包', 'KTV', 'K歌娱乐', 'EXACT', 10, TRUE, 'K歌娱乐K包4台'),
('k包活动区', 'KTV', 'K歌娱乐', 'EXACT', 10, TRUE, 'K歌娱乐k包活动区2台'),
('幸会158', 'KTV', 'K歌娱乐', 'EXACT', 10, TRUE, 'K歌娱乐幸会1582台'),
-- 特殊区域
('补时长', 'SPECIAL', '补时长', 'EXACT', 10, TRUE, '特殊补时长7台- 用于时长补录'),
-- 模糊匹配规则
('%VIP%', 'BILLIARD_VIP', '台球VIP', 'LIKE', 50, TRUE, '模糊匹配:包含"VIP"的区域'),
('%斯诺克%', 'SNOOKER', '斯诺克', 'LIKE', 50, TRUE, '模糊匹配:包含"斯诺克"的区域'),
('%麻将%', 'MAHJONG', '麻将棋牌', 'LIKE', 50, TRUE, '模糊匹配:包含"麻将"的区域'),
('%K包%', 'KTV', 'K歌娱乐', 'LIKE', 50, TRUE, '模糊匹配:包含"K包"的区域'),
('%KTV%', 'KTV', 'K歌娱乐', 'LIKE', 50, TRUE, '模糊匹配:包含"KTV"的区域'),
-- 默认兜底
('DEFAULT', 'OTHER', '其他', 'DEFAULT', 999, TRUE, '兜底规则:无法匹配的区域归入其他');
-- =============================================================================
@@ -302,63 +258,27 @@ INSERT INTO dws.cfg_area_category (
TRUNCATE TABLE dws.cfg_skill_type RESTART IDENTITY CASCADE;
INSERT INTO dws.cfg_skill_type (
skill_id, skill_name,
skill_id, skill_name,
course_type_code, course_type_name,
is_active, description
) VALUES
-- 基础课/陪打
(2791903611396869, '台球基础陪打',
'BASE', '基础课',
TRUE, '基础课:陪打服务,按助教等级计价'),
-- 附加课/超休
(2807440316432197, '台球超休服务',
'BONUS', '附加课',
TRUE, '附加课:超休/激励课固定190元/小时'),
-- 包厢课(如有)
(2807440316432198, '包厢服务',
'BASE', '基础课',
TRUE, '包厢服务归入基础课统计统一按138元/小时计价');
-- =============================================================================
-- 6. 优惠类型配置(用于财务优惠明细分析)
-- 说明: 定义各类优惠的代码和名称,便于后续分析
-- 6~8. 优惠类型/支出类型/平台类型 — 作为代码常量使用,不单独建表
-- =============================================================================
-- 此配置作为代码常量使用,不单独建表
-- GROUPBUY - 团购优惠
-- VIP - 会员折扣
-- GIFT_CARD - 赠送卡抵扣
-- MANUAL - 手动调整
-- ROUNDING - 抹零
-- BIG_CUSTOMER - 大客户优惠(待抽样分析确认)
-- OTHER - 其他优惠
-- =============================================================================
-- 7. 支出类型配置用于Excel导入
-- 说明: 定义各类支出的代码和名称
-- =============================================================================
-- 此配置作为代码常量使用,不单独建表
-- RENT - 房租
-- UTILITY - 水电费
-- PROPERTY - 物业费
-- SALARY - 工资
-- REIMBURSE - 报销
-- PLATFORM_FEE - 平台服务费
-- OTHER - 其他支出
-- =============================================================================
-- 8. 平台类型配置用于Excel导入
-- 说明: 定义各平台的代码和名称
-- =============================================================================
-- 此配置作为代码常量使用,不单独建表
-- MEITUAN - 美团
-- DOUYIN - 抖音
-- DIANPING - 大众点评
-- OTHER - 其他平台
-- 优惠类型: GROUPBUY/VIP/GIFT_CARD/MANUAL/ROUNDING/BIG_CUSTOMER/OTHER
-- 支出类型: RENT/UTILITY/PROPERTY/SALARY/REIMBURSE/PLATFORM_FEE/OTHER
-- 平台类型: MEITUAN/DOUYIN/DIANPING/OTHER
-- =============================================================================
@@ -377,7 +297,7 @@ BEGIN
SELECT COUNT(*) INTO v_bonus_count FROM dws.cfg_bonus_rules;
SELECT COUNT(*) INTO v_area_count FROM dws.cfg_area_category;
SELECT COUNT(*) INTO v_skill_count FROM dws.cfg_skill_type;
RAISE NOTICE '配置数据初始化完成:';
RAISE NOTICE ' - cfg_performance_tier: % 条', v_tier_count;
RAISE NOTICE ' - cfg_assistant_level_price: % 条', v_price_count;
@@ -386,4 +306,3 @@ BEGIN
RAISE NOTICE ' - cfg_skill_type: % 条', v_skill_count;
END;
$$;
*/

View File

@@ -136,6 +136,54 @@ ON CONFLICT (index_type, param_name, effective_from) DO UPDATE SET
updated_at = NOW();
-- =============================================================================
-- SPI消费力指数参数
-- 生效时间:北京时间 2026-02-23
-- =============================================================================
INSERT INTO dws.cfg_index_parameters
(index_type, param_name, param_value, description, effective_from)
VALUES
-- 窗口参数
('SPI', 'spend_window_short_days', 30.000000, '短期消费窗口(天)', DATE '2026-02-23'),
('SPI', 'spend_window_long_days', 90.000000, '长期消费窗口(天)', DATE '2026-02-23'),
('SPI', 'ewma_alpha_daily_spend', 0.300000, '日消费 EWMA 平滑系数', DATE '2026-02-23'),
-- 金额压缩基数(基于典型台球门店消费水平的初始默认值)
('SPI', 'amount_base_spend_30', 500.000000, '30天消费额压缩基数', DATE '2026-02-23'),
('SPI', 'amount_base_spend_90', 1500.000000, '90天消费额压缩基数', DATE '2026-02-23'),
('SPI', 'amount_base_ticket_90', 200.000000, '90天客单价压缩基数', DATE '2026-02-23'),
('SPI', 'amount_base_recharge_90', 1000.000000, '90天充值额压缩基数', DATE '2026-02-23'),
('SPI', 'amount_base_speed_abs', 100.000000, '绝对速度压缩基数', DATE '2026-02-23'),
('SPI', 'amount_base_ewma_90', 50.000000, '日消费EWMA压缩基数', DATE '2026-02-23'),
-- Level 子分权重
('SPI', 'w_level_spend_30', 0.300000, 'Level子分30天消费权重', DATE '2026-02-23'),
('SPI', 'w_level_spend_90', 0.350000, 'Level子分90天消费权重', DATE '2026-02-23'),
('SPI', 'w_level_ticket_90', 0.200000, 'Level子分90天客单权重', DATE '2026-02-23'),
('SPI', 'w_level_recharge_90', 0.150000, 'Level子分90天充值权重', DATE '2026-02-23'),
-- Speed 子分权重
('SPI', 'w_speed_abs', 0.500000, 'Speed子分绝对速度权重', DATE '2026-02-23'),
('SPI', 'w_speed_rel', 0.300000, 'Speed子分相对速度权重', DATE '2026-02-23'),
('SPI', 'w_speed_ewma', 0.200000, 'Speed子分EWMA速度权重', DATE '2026-02-23'),
-- 总分权重
('SPI', 'weight_level', 0.600000, 'SPI总分Level子分权重', DATE '2026-02-23'),
('SPI', 'weight_speed', 0.300000, 'SPI总分Speed子分权重', DATE '2026-02-23'),
('SPI', 'weight_stability', 0.100000, 'SPI总分Stability子分权重', DATE '2026-02-23'),
-- 稳定性参数
('SPI', 'stability_window_days', 90.000000, '稳定性计算窗口(天)', DATE '2026-02-23'),
('SPI', 'use_stability', 1.000000, '是否启用稳定性子分0=关闭,1=启用', DATE '2026-02-23'),
-- 映射与平滑
('SPI', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-02-23'),
('SPI', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-02-23'),
('SPI', 'compression_mode', 1.000000, '压缩模式0=none,1=log1p,2=asinh', DATE '2026-02-23'),
('SPI', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-02-23'),
('SPI', 'ewma_alpha', 0.200000, 'EWMA平滑系数', DATE '2026-02-23'),
-- 速度计算
('SPI', 'speed_epsilon', 0.000001, '速度计算防除零小量', DATE '2026-02-23')
ON CONFLICT (index_type, param_name, effective_from) DO UPDATE SET
param_value = EXCLUDED.param_value,
description = EXCLUDED.description,
updated_at = NOW();
-- =============================================================================
-- 验证
-- =============================================================================
@@ -147,6 +195,7 @@ DECLARE
ml_count INTEGER;
nci_count INTEGER;
wbi_count INTEGER;
spi_count INTEGER;
BEGIN
SELECT COUNT(*) INTO rs_count
FROM dws.cfg_index_parameters
@@ -172,12 +221,17 @@ BEGIN
FROM dws.cfg_index_parameters
WHERE index_type = 'WBI';
SELECT COUNT(*) INTO spi_count
FROM dws.cfg_index_parameters
WHERE index_type = 'SPI';
RAISE NOTICE 'RS 参数数量: %', rs_count;
RAISE NOTICE 'OS 参数数量: %', os_count;
RAISE NOTICE 'MS 参数数量: %', ms_count;
RAISE NOTICE 'ML 参数数量: %', ml_count;
RAISE NOTICE '新客转化参数数量: %', nci_count;
RAISE NOTICE '唤回指数参数数量: %', wbi_count;
RAISE NOTICE 'SPI 消费力指数参数数量: %', spi_count;
END $$;
SELECT

View File

@@ -28,8 +28,7 @@ task_codes AS (
'ODS_STORE_GOODS',
'ODS_STORE_GOODS_SALES',
'ODS_TABLE_FEE_DISCOUNT',
'ODS_TENANT_GOODS',
'ODS_SETTLEMENT_TICKET'
'ODS_TENANT_GOODS'
]) AS task_code
)
INSERT INTO meta.etl_task (task_code, store_id, enabled)

View File

@@ -40,6 +40,20 @@ task_codes AS (
'TICKET_DWD',
'TOPUPS',
'DWS_BUILD_ORDER_SUMMARY',
'DWS_ASSISTANT_DAILY',
'DWS_ASSISTANT_MONTHLY',
'DWS_ASSISTANT_CUSTOMER',
'DWS_ASSISTANT_SALARY',
'DWS_ASSISTANT_FINANCE',
'DWS_MEMBER_CONSUMPTION',
'DWS_MEMBER_VISIT',
'DWS_FINANCE_DAILY',
'DWS_FINANCE_RECHARGE',
'DWS_FINANCE_INCOME_STRUCTURE',
'DWS_FINANCE_DISCOUNT_DETAIL',
'DWS_GOODS_STOCK_DAILY',
'DWS_GOODS_STOCK_WEEKLY',
'DWS_GOODS_STOCK_MONTHLY',
'DWS_WINBACK_INDEX',
'DWS_NEWCONV_INDEX',
'DWS_RELATION_INDEX',