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

@@ -1,10 +1,11 @@
# db/ — 数据库资产目录
## 当前状态2026-02-25 更新)
## 当前状态2026-03-15 更新)
完整 DDL 基线已迁移至 `docs/database/ddl/`(按 schema 分文件,从测试库自动导出)。
本目录保留运行时资产迁移脚本、种子数据、FDW 配置、建库脚本)。
2026-02-22 基线重置前的旧迁移已归档至 `_archived/`;之后的新迁移仍在 `migrations/` 中。
DDL 基线最近一次刷新2026-03-15合并了截至 2026-03-09 的全部迁移)。
## 目录结构
@@ -12,19 +13,14 @@
db/
├── etl_feiqiu/
│ ├── schemas/ — 已清空DDL 基线见 docs/database/ddl/etl_feiqiu__*.sql
│ ├── migrations/ — 基线重置后的增量迁移(重置前的已归档
│ ├── seeds/ — 种子数据(运行时需要
│ │ ├── seed_ods_tasks.sql
│ │ ├── seed_scheduler_tasks.sql
│ │ ├── seed_dws_config.sql
│ │ └── seed_index_parameters.sql
│ ├── migrations/ — 已清空(全部合并进 DDL 基线2026-03-15
│ ├── seeds/ — 已清空(合并进对应 DDL 文件末尾
│ └── scripts/
│ └── create_test_db.sql
├── zqyy_app/
│ ├── schemas/ — 已清空
│ ├── migrations/ — 基线重置后的增量迁移(含 auth 建表、FDW 配置、种子数据
│ ├── seeds/
│ │ └── admin_web_seed.sql
│ ├── migrations/ — 已清空(全部合并进 DDL 基线2026-03-15
│ ├── seeds/ — 已清空(合并进对应 DDL 文件末尾)
│ └── scripts/
│ └── create_test_db.sql
├── fdw/ — FDW 跨库映射配置(正式 + 测试 + 反向)
@@ -47,12 +43,14 @@ db/
- `docs/database/ddl/etl_feiqiu__app.sql`(仅视图)
- `docs/database/ddl/zqyy_app__public.sql`
- `docs/database/ddl/zqyy_app__auth.sql`
- `docs/database/ddl/zqyy_app__biz.sql`
- `docs/database/ddl/fdw.sql`(仅正向映射;反向映射见 `db/fdw/setup_fdw_reverse*.sql`
重新生成:`python scripts/ops/gen_consolidated_ddl.py`
## 迁移管理
新的迁移脚本放 `migrations/`,文件名格式 `YYYY-MM-DD__描述.sql`
每次迁移执行后,建议重新运行 DDL 生成脚本刷新基线:`python scripts/ops/gen_consolidated_ddl.py`
种子数据类脚本(纯 INSERT/DELETE`seeds/`,不放 `migrations/`
项目 1.0 尚未上线DDL 基线已统一到 `docs/database/ddl/`(含种子数据)
后续新增迁移脚本放 `migrations/`,文件名格式 `YYYY-MM-DD__描述.sql`
每次迁移执行后,重新运行 DDL 生成脚本刷新基线:`python scripts/ops/gen_consolidated_ddl.py`
种子数据已合并进对应 DDL 文件末尾,不再单独维护 `seeds/` 目录。

View File

@@ -1,67 +0,0 @@
-- =============================================================================
-- 迁移脚本:扩展助教日度业绩明细表 — 新增定档折算惩罚字段
-- 日期2025-02-24
-- 说明:在 dws.dws_assistant_daily_detail 中新增 penalty_minutes、penalty_reason、
-- is_exempt、per_hour_contribution 四个字段,支撑定档折算惩罚检测与计算。
-- 需求5.1, 5.2
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- 1. ALTER TABLE新增定档折算惩罚字段
-- ---------------------------------------------------------------------------
ALTER TABLE dws.dws_assistant_daily_detail
ADD COLUMN IF NOT EXISTS penalty_minutes NUMERIC(10,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS penalty_reason TEXT,
ADD COLUMN IF NOT EXISTS is_exempt BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS per_hour_contribution NUMERIC(14,2);
COMMENT ON COLUMN dws.dws_assistant_daily_detail.penalty_minutes
IS '定档折算惩罚分钟数,无惩罚时为 0';
COMMENT ON COLUMN dws.dws_assistant_daily_detail.penalty_reason
IS '惩罚原因描述,无惩罚时为 NULL';
COMMENT ON COLUMN dws.dws_assistant_daily_detail.is_exempt
IS '是否豁免惩罚TRUE 时跳过惩罚计算';
COMMENT ON COLUMN dws.dws_assistant_daily_detail.per_hour_contribution
IS '单人每小时贡献流水 = 台费每小时实收单价 / 本次基础课助教人数';
COMMIT;
-- =============================================================================
-- 回滚脚本(如需撤销)
-- =============================================================================
-- ALTER TABLE dws.dws_assistant_daily_detail
-- DROP COLUMN IF EXISTS penalty_minutes,
-- DROP COLUMN IF EXISTS penalty_reason,
-- DROP COLUMN IF EXISTS is_exempt,
-- DROP COLUMN IF EXISTS per_hour_contribution;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认新增字段存在
-- SELECT column_name, data_type, column_default
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_assistant_daily_detail'
-- AND column_name IN ('penalty_minutes', 'penalty_reason', 'is_exempt', 'per_hour_contribution')
-- ORDER BY column_name;
-- 预期4 行
-- 2. 确认字段类型正确
-- SELECT column_name, data_type, numeric_precision, numeric_scale
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_assistant_daily_detail'
-- AND column_name IN ('penalty_minutes', 'per_hour_contribution')
-- ORDER BY column_name;
-- 预期penalty_minutes → numeric(10,2)per_hour_contribution → numeric(14,2)
-- 3. 确认 is_exempt 默认值
-- SELECT column_name, column_default
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_assistant_daily_detail'
-- AND column_name = 'is_exempt';
-- 预期column_default = 'false'

View File

@@ -1,89 +0,0 @@
-- =============================================================================
-- 迁移脚本:扩展会员消费汇总表 — 新增充值窗口和次均消费字段
-- 日期2025-02-24
-- 说明:在 dws.dws_member_consumption_summary 中新增 30/60/90 天充值次数、
-- 充值金额以及次均消费额度字段,支撑小程序客户看板展示。
-- 需求3.1, 3.2
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- 1. ALTER TABLE新增充值窗口 + 次均消费字段
-- ---------------------------------------------------------------------------
ALTER TABLE dws.dws_member_consumption_summary
ADD COLUMN IF NOT EXISTS recharge_count_30d INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS recharge_count_60d INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS recharge_count_90d INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS recharge_amount_30d NUMERIC(14,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS recharge_amount_60d NUMERIC(14,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS recharge_amount_90d NUMERIC(14,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS avg_ticket_amount NUMERIC(14,2) DEFAULT 0;
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_count_30d
IS '近 30 天充值次数';
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_count_60d
IS '近 60 天充值次数';
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_count_90d
IS '近 90 天充值次数';
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_amount_30d
IS '近 30 天充值金额';
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_amount_60d
IS '近 60 天充值金额';
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_amount_90d
IS '近 90 天充值金额';
COMMENT ON COLUMN dws.dws_member_consumption_summary.avg_ticket_amount
IS '次均消费额度 = total_consume_amount / MAX(total_visit_count, 1)';
COMMIT;
-- =============================================================================
-- 回滚脚本(如需撤销)
-- =============================================================================
-- ALTER TABLE dws.dws_member_consumption_summary
-- DROP COLUMN IF EXISTS recharge_count_30d,
-- DROP COLUMN IF EXISTS recharge_count_60d,
-- DROP COLUMN IF EXISTS recharge_count_90d,
-- DROP COLUMN IF EXISTS recharge_amount_30d,
-- DROP COLUMN IF EXISTS recharge_amount_60d,
-- DROP COLUMN IF EXISTS recharge_amount_90d,
-- DROP COLUMN IF EXISTS avg_ticket_amount;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认新增字段存在
-- SELECT column_name, data_type, column_default
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_member_consumption_summary'
-- AND column_name IN (
-- 'recharge_count_30d', 'recharge_count_60d', 'recharge_count_90d',
-- 'recharge_amount_30d', 'recharge_amount_60d', 'recharge_amount_90d',
-- 'avg_ticket_amount'
-- )
-- ORDER BY column_name;
-- 预期7 行
-- 2. 确认字段类型正确
-- SELECT column_name, data_type, numeric_precision, numeric_scale
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_member_consumption_summary'
-- AND column_name LIKE 'recharge_amount%'
-- ORDER BY column_name;
-- 预期3 行data_type = numeric, precision = 14, scale = 2
-- 3. 确认注释已设置
-- SELECT c.column_name,
-- pgd.description
-- FROM information_schema.columns c
-- JOIN pg_catalog.pg_statio_all_tables st
-- ON c.table_schema = st.schemaname AND c.table_name = st.relname
-- JOIN pg_catalog.pg_description pgd
-- ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
-- WHERE c.table_schema = 'dws'
-- AND c.table_name = 'dws_member_consumption_summary'
-- AND c.column_name LIKE 'recharge%'
-- ORDER BY c.column_name;
-- 预期6 行description 非空

View File

@@ -1,89 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建助教订单流水四项统计表
-- 日期2025-02-24
-- 说明:新建 dws.dws_assistant_order_contribution 表,存储每名助教每日的
-- 订单总流水、订单净流水、时效贡献流水、时效净贡献四项统计数据。
-- 需求1.1, 1.2, 1.3, 1.4, 1.5
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- 1. 建表dws.dws_assistant_order_contribution
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dws.dws_assistant_order_contribution (
contribution_id BIGSERIAL PRIMARY KEY,
site_id INTEGER NOT NULL,
tenant_id INTEGER NOT NULL,
assistant_id BIGINT NOT NULL,
assistant_nickname VARCHAR(100),
stat_date DATE NOT NULL,
-- 四项统计
order_gross_revenue NUMERIC(14,2) DEFAULT 0, -- 订单总流水
order_net_revenue NUMERIC(14,2) DEFAULT 0, -- 订单净流水
time_weighted_revenue NUMERIC(14,2) DEFAULT 0, -- 时效贡献流水
time_weighted_net_revenue NUMERIC(14,2) DEFAULT 0, -- 时效净贡献
-- 辅助字段
order_count INTEGER DEFAULT 0, -- 参与订单数
total_service_seconds INTEGER DEFAULT 0, -- 总服务时长(秒)
-- 元数据
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE dws.dws_assistant_order_contribution
IS '助教订单流水四项统计表,粒度 (site_id, assistant_id, stat_date)';
COMMENT ON COLUMN dws.dws_assistant_order_contribution.order_gross_revenue
IS '订单总流水 = 台费 + 酒水食品 + 所有助教服务费';
COMMENT ON COLUMN dws.dws_assistant_order_contribution.order_net_revenue
IS '订单净流水 = 订单总流水 - 所有助教服务分成';
COMMENT ON COLUMN dws.dws_assistant_order_contribution.time_weighted_revenue
IS '时效贡献流水 = 台费按时长分摊 + 个人服务费 + 酒水食品按时长比例';
COMMENT ON COLUMN dws.dws_assistant_order_contribution.time_weighted_net_revenue
IS '时效净贡献 = 时效贡献流水 - 个人服务分成';
-- ---------------------------------------------------------------------------
-- 2. 唯一索引:确保 (site_id, assistant_id, stat_date) 唯一
-- ---------------------------------------------------------------------------
CREATE UNIQUE INDEX IF NOT EXISTS idx_aoc_site_assistant_date
ON dws.dws_assistant_order_contribution (site_id, assistant_id, stat_date);
-- ---------------------------------------------------------------------------
-- 3. 查询索引:按门店+日期查询
-- ---------------------------------------------------------------------------
CREATE INDEX IF NOT EXISTS idx_aoc_stat_date
ON dws.dws_assistant_order_contribution (site_id, stat_date);
COMMIT;
-- =============================================================================
-- 回滚脚本(如需撤销)
-- =============================================================================
-- 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;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认表存在
-- SELECT table_schema, table_name FROM information_schema.tables
-- WHERE table_schema = 'dws' AND table_name = 'dws_assistant_order_contribution';
-- 预期1 行
-- 2. 确认字段完整
-- SELECT column_name, data_type, column_default
-- FROM information_schema.columns
-- WHERE table_schema = 'dws' AND table_name = 'dws_assistant_order_contribution'
-- ORDER BY ordinal_position;
-- 预期16 行
-- 3. 确认索引存在
-- SELECT indexname FROM pg_indexes
-- WHERE schemaname = 'dws' AND tablename = 'dws_assistant_order_contribution'
-- ORDER BY indexname;
-- 预期3 行PK + idx_aoc_site_assistant_date + idx_aoc_stat_date

View File

@@ -1,34 +0,0 @@
-- =============================================================================
-- RLS 视图:助教订单流水统计 + 已有视图重建(包含新增字段)
-- 目标库test_etl_feiqiu测试/ etl_feiqiu生产
-- 前提dws.dws_assistant_order_contribution 表已创建;
-- dws.dws_member_consumption_summary 已新增充值窗口字段;
-- dws.dws_assistant_daily_detail 已新增惩罚字段
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 1. 新建:助教订单流水统计 RLS 视图
-- -----------------------------------------------------------------------------
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;
GRANT SELECT ON app.v_dws_assistant_order_contribution TO app_reader;
-- -----------------------------------------------------------------------------
-- 2. 重建已有视图(使用 SELECT * 以包含新增字段)
-- 原视图使用显式字段列表,新增字段不会自动暴露
-- -----------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_dws_member_consumption_summary AS
SELECT * FROM dws.dws_member_consumption_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_daily_detail AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE site_id = current_setting('app.current_site_id')::bigint;
-- =============================================================================
-- 回滚脚本
-- =============================================================================
-- DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution;
-- 对于已有视图的回滚,需要用旧的显式字段列表重建(参见 2026-02-24__p1_create_app_schema_rls_views.sql

View File

@@ -1,139 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建员工档案 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

@@ -1,70 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建 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,78 +0,0 @@
-- 迁移:新增 goodsStockWarningInfo 嵌套字段到 ODS 和 DWD
-- 日期2026-02-24
-- 关联:一致性检查报告发现 API 独有字段 goodsStockWarningInfo 未映射
-- 字段来源API store_goods_master -> goodsStockWarningInfo 嵌套对象
-- - sales_day: 销售天数(用于库存预警计算)
-- - warning_day_max: 预警天数上限
-- - warning_day_min: 预警天数下限
-- - site_goods_id / tenant_goods_id: 已有冗余字段,不重复收录
BEGIN;
-- =============================================================================
-- 1. ODS 层ods.store_goods_master 新增 3 列
-- =============================================================================
ALTER TABLE ods.store_goods_master
ADD COLUMN IF NOT EXISTS warning_sales_day NUMERIC(18,2),
ADD COLUMN IF NOT EXISTS warning_day_max INTEGER,
ADD COLUMN IF NOT EXISTS warning_day_min INTEGER;
COMMENT ON COLUMN ods.store_goods_master.warning_sales_day IS
'【说明】库存预警参考的日均销量。 【ODS来源】store_goods_master - goodsStockWarningInfo.sales_day。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.sales_day。';
COMMENT ON COLUMN ods.store_goods_master.warning_day_max IS
'【说明】库存预警天数上限。 【ODS来源】store_goods_master - goodsStockWarningInfo.warning_day_max。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.warning_day_max。';
COMMENT ON COLUMN ods.store_goods_master.warning_day_min IS
'【说明】库存预警天数下限。 【ODS来源】store_goods_master - goodsStockWarningInfo.warning_day_min。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.warning_day_min。';
-- =============================================================================
-- 2. DWD 层dwd.dim_store_goods_ex 新增 3 列
-- =============================================================================
ALTER TABLE dwd.dim_store_goods_ex
ADD COLUMN IF NOT EXISTS warning_sales_day NUMERIC(18,2),
ADD COLUMN IF NOT EXISTS warning_day_max INTEGER,
ADD COLUMN IF NOT EXISTS warning_day_min INTEGER;
COMMENT ON COLUMN dwd.dim_store_goods_ex.warning_sales_day IS
'【说明】库存预警参考的日均销量。 【ODS来源】store_goods_master - goodsStockWarningInfo.sales_day。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.sales_day。';
COMMENT ON COLUMN dwd.dim_store_goods_ex.warning_day_max IS
'【说明】库存预警天数上限。 【ODS来源】store_goods_master - goodsStockWarningInfo.warning_day_max。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.warning_day_max。';
COMMENT ON COLUMN dwd.dim_store_goods_ex.warning_day_min IS
'【说明】库存预警天数下限。 【ODS来源】store_goods_master - goodsStockWarningInfo.warning_day_min。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.warning_day_min。';
COMMIT;
-- =============================================================================
-- 回滚策略
-- =============================================================================
-- 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
-- =============================================================================
-- 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 col_description(
-- (SELECT oid FROM pg_class WHERE relname = 'store_goods_master' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'ods')),
-- (SELECT ordinal_position FROM information_schema.columns WHERE table_schema = 'ods' AND table_name = 'store_goods_master' AND column_name = 'warning_sales_day')
-- );
-- 预期:非空

View File

@@ -1,21 +0,0 @@
-- 迁移:清理 ODS_ASSISTANT_ABOLISH 残留元数据
-- 日期2026-02-24
-- 关联:联调报告发现 meta.etl_task 中仍有 ODS_ASSISTANT_ABOLISH 注册,
-- 导致调度器尝试执行已删除的任务并报 ValueError
-- 幂等DELETE WHERE 不存在时无影响
BEGIN;
-- 1. 清理 meta.etl_task 中的 ODS_ASSISTANT_ABOLISH 注册
DELETE FROM meta.etl_task WHERE task_code = 'ODS_ASSISTANT_ABOLISH';
-- 2. 清理 meta.etl_task 中的旧式 ASSISTANT_ABOLISH 注册seed_scheduler_tasks.sql 残留)
DELETE FROM meta.etl_task WHERE task_code = 'ASSISTANT_ABOLISH';
COMMIT;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- SELECT * FROM meta.etl_task WHERE task_code IN ('ODS_ASSISTANT_ABOLISH', 'ASSISTANT_ABOLISH');
-- 预期0 行

View File

@@ -1,196 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建 app Schema、RLS 视图层与 app_reader 角色
-- 日期2026-02-24
-- 目标库test_etl_feiqiu通过 PG_DSN 连接)
-- 说明:为 DWD/DWS 层共 35 张表创建带 site_id 行级过滤的 RLS 视图,
-- 供业务库通过 postgres_fdw 只读访问。
-- cfg_* 配置表无 site_id 列,视图直接 SELECT * 不加过滤。
-- dim_member / dim_member_card_account 使用 register_site_id 列过滤。
-- dim_staff_ex 无 site_id 列,视图直接 SELECT * 不加过滤。
-- 需求2.1, 2.2, 2.3, 2.4, 2.7, 2.8, 4.1, 4.3, 4.4, 4.5, 4.6
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 创建 app Schema
-- ---------------------------------------------------------------------------
CREATE SCHEMA IF NOT EXISTS app;
-- ---------------------------------------------------------------------------
-- 2. 创建 app_reader 只读角色(条件创建)
-- ---------------------------------------------------------------------------
DO $$ BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'app_reader') THEN
CREATE ROLE app_reader LOGIN;
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 3. DWD 层 RLS 视图11 张,全部含 site_id 过滤)
-- ---------------------------------------------------------------------------
-- dim_member 使用 register_site_id 而非 site_id
CREATE OR REPLACE VIEW app.v_dim_member AS
SELECT * FROM dwd.dim_member
WHERE register_site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dim_assistant AS
SELECT * FROM dwd.dim_assistant
WHERE site_id = current_setting('app.current_site_id')::bigint;
-- dim_member_card_account 使用 register_site_id 而非 site_id
CREATE OR REPLACE VIEW app.v_dim_member_card_account AS
SELECT * FROM dwd.dim_member_card_account
WHERE register_site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dim_table AS
SELECT * FROM dwd.dim_table
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dwd_settlement_head AS
SELECT * FROM dwd.dwd_settlement_head
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dwd_table_fee_log AS
SELECT * FROM dwd.dwd_table_fee_log
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dwd_assistant_service_log AS
SELECT * FROM dwd.dwd_assistant_service_log
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dwd_recharge_order AS
SELECT * FROM dwd.dwd_recharge_order
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dwd_store_goods_sale AS
SELECT * FROM dwd.dwd_store_goods_sale
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dim_staff AS
SELECT * FROM dwd.dim_staff
WHERE site_id = current_setting('app.current_site_id')::bigint;
-- dim_staff_ex 无 site_id 列,直接 SELECT * 不加过滤
CREATE OR REPLACE VIEW app.v_dim_staff_ex AS
SELECT * FROM dwd.dim_staff_ex;
-- ---------------------------------------------------------------------------
-- 4. DWS 层 RLS 视图 — 含 site_id 过滤20 张)
-- ---------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_dws_member_consumption_summary AS
SELECT * FROM dws.dws_member_consumption_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_visit_detail AS
SELECT * FROM dws.dws_member_visit_detail
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_winback_index AS
SELECT * FROM dws.dws_member_winback_index
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_newconv_index AS
SELECT * FROM dws.dws_member_newconv_index
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_recall_index AS
SELECT * FROM dws.dws_member_recall_index
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_assistant_relation_index AS
SELECT * FROM dws.dws_member_assistant_relation_index
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_assistant_intimacy AS
SELECT * FROM dws.dws_member_assistant_intimacy
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_daily_detail AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_monthly_summary AS
SELECT * FROM dws.dws_assistant_monthly_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_salary_calc AS
SELECT * FROM dws.dws_assistant_salary_calc
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_customer_stats AS
SELECT * FROM dws.dws_assistant_customer_stats
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_finance_analysis AS
SELECT * FROM dws.dws_assistant_finance_analysis
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_daily_summary AS
SELECT * FROM dws.dws_finance_daily_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_income_structure AS
SELECT * FROM dws.dws_finance_income_structure
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_recharge_summary AS
SELECT * FROM dws.dws_finance_recharge_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_discount_detail AS
SELECT * FROM dws.dws_finance_discount_detail
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_expense_summary AS
SELECT * FROM dws.dws_finance_expense_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_platform_settlement AS
SELECT * FROM dws.dws_platform_settlement
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_recharge_commission AS
SELECT * FROM dws.dws_assistant_recharge_commission
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_order_summary AS
SELECT * FROM dws.dws_order_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
-- ---------------------------------------------------------------------------
-- 5. DWS 层 cfg_* 配置表视图4 张,无 site_id直接 SELECT *
-- ---------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_cfg_performance_tier AS
SELECT * FROM dws.cfg_performance_tier;
CREATE OR REPLACE VIEW app.v_cfg_assistant_level_price AS
SELECT * FROM dws.cfg_assistant_level_price;
CREATE OR REPLACE VIEW app.v_cfg_bonus_rules AS
SELECT * FROM dws.cfg_bonus_rules;
CREATE OR REPLACE VIEW app.v_cfg_index_parameters AS
SELECT * FROM dws.cfg_index_parameters;
-- ---------------------------------------------------------------------------
-- 6. P2 预留(待 P2 完成后取消注释并创建视图)
-- ---------------------------------------------------------------------------
-- TODO [P2] dws.dws_member_spending_power_index → app.v_dws_member_spending_power_index
-- TODO [P2] dws.dws_assistant_order_contribution → app.v_dws_assistant_order_contribution
-- ---------------------------------------------------------------------------
-- 7. 授权app_reader 对 app Schema 的只读访问
-- ---------------------------------------------------------------------------
GRANT USAGE ON SCHEMA app TO app_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_reader;
-- 未来新建视图自动授权
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT ON TABLES TO app_reader;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- ALTER DEFAULT PRIVILEGES IN SCHEMA app REVOKE SELECT ON TABLES FROM app_reader;
-- REVOKE SELECT ON ALL TABLES IN SCHEMA app FROM app_reader;
-- REVOKE USAGE ON SCHEMA app FROM app_reader;
-- DROP SCHEMA IF EXISTS app CASCADE;
-- DROP ROLE IF EXISTS app_reader;

View File

@@ -1,34 +0,0 @@
-- 新增 dws.biz_date() 函数:将 timestamptz 按营业日分割点归属到对应日期
-- 原因:全系统统计时间口径从自然日切换为营业日(默认 08:00 分割),
-- 数据库层需要与 Python 侧 business_date() 等价的 SQL 函数,
-- 供物化视图和直接查询使用。
-- 影响:物化视图 mv_dws_finance_daily_summary_l1..l4、
-- mv_dws_assistant_daily_detail_l1..l4 的时间过滤条件将依赖此函数
-- 关联需求Requirements 9.2, 9.4
CREATE OR REPLACE FUNCTION dws.biz_date(
ts timestamptz,
cutoff_hour int DEFAULT 8
)
RETURNS date
LANGUAGE sql
IMMUTABLE
PARALLEL SAFE
AS $$
SELECT (ts - make_interval(hours => cutoff_hour))::date;
$$;
COMMENT ON FUNCTION dws.biz_date(timestamptz, int) IS
'营业日归属函数:将时间戳减去 cutoff_hour 小时后取日期。'
'默认 cutoff_hour=8即 08:00 前的时间戳归属前一天。'
'等价于 Python 侧 neozqyy_shared.datetime_utils.business_date()。';
-- 验证 SQL
-- SELECT dws.biz_date('2026-01-15 07:59:59+08'::timestamptz); -- 预期2026-01-14
-- SELECT dws.biz_date('2026-01-15 08:00:00+08'::timestamptz); -- 预期2026-01-15
-- SELECT dws.biz_date('2026-01-15 23:59:59+08'::timestamptz); -- 预期2026-01-15
-- SELECT dws.biz_date('2026-02-01 07:00:00+08'::timestamptz, 8); -- 预期2026-01-31月末边界
-- SELECT dws.biz_date(NOW()); -- 预期:当前营业日
-- 回滚:
-- DROP FUNCTION IF EXISTS dws.biz_date(timestamptz, int);

View File

@@ -1,22 +0,0 @@
-- 迁移dws_assistant_order_contribution.site_id integer → bigint
-- 原因site_id 为飞球雪花 ID值域超出 int32 上限
-- 依赖app.v_dws_assistant_order_contribution 视图需先 DROP 再重建
-- 日期2026-02-27
-- 已通过 scripts/ops/_fix_all_int_site_ids.py 执行
-- 1. DROP 依赖视图
DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution CASCADE;
-- 2. ALTER 列类型
ALTER TABLE dws.dws_assistant_order_contribution
ALTER COLUMN site_id TYPE bigint;
-- 3. 重建视图(定义需与原视图一致)
-- CREATE OR REPLACE VIEW app.v_dws_assistant_order_contribution AS
-- SELECT * FROM dws.dws_assistant_order_contribution;
-- 注意:实际视图定义已由脚本自动获取并重建
-- 回滚(如需):
-- DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution CASCADE;
-- ALTER TABLE dws.dws_assistant_order_contribution ALTER COLUMN site_id TYPE integer;
-- 然后重建视图

View File

@@ -1,14 +0,0 @@
-- 修复 dws_member_spending_power_index.site_id: integer → bigint
-- 原因site_id 值(如 2790685415443269超出 int32 范围max 2147483647
-- 导致 INSERT 时 NumericValueOutOfRange 错误
-- 影响DWS_SPENDING_POWER_INDEX 任务
-- 已在 test_etl_feiqiu 执行2026-02-27
ALTER TABLE dws.dws_member_spending_power_index
ALTER COLUMN site_id TYPE bigint;
-- 回滚(仅在确认无大值数据时):
-- ALTER TABLE dws.dws_member_spending_power_index ALTER COLUMN site_id TYPE integer;
-- 待处理dws.dws_assistant_order_contribution.site_id 也是 integer
-- 但有视图 app.v_dws_assistant_order_contribution 依赖,需先 DROP VIEW 再改再重建。

View File

@@ -1,138 +0,0 @@
-- 重建物化视图:时间过滤条件从自然日切换为营业日
-- 原因:全系统统计口径从自然日切换为营业日(默认 08:00 分割),
-- 物化视图的 WHERE 条件中 CURRENT_DATE 需替换为 dws.biz_date(NOW())
-- 使视图数据范围与 DWS 任务的营业日口径一致。
-- 前置依赖dws.biz_date() 函数2026-02-27__add_biz_date_function.sql
-- 影响8 个物化视图将按营业日口径过滤数据,刷新后生效
-- 关联需求Requirements 9.1, 9.3
--
-- 变更说明:
-- CURRENT_DATE → dws.biz_date(NOW())
-- date_trunc('month', CURRENT_DATE::timestamptz) → date_trunc('month', dws.biz_date(NOW())::timestamptz)
--
-- 注意:物化视图不支持 CREATE OR REPLACE必须 DROP + CREATE 重建。
-- 重建后数据为空WITH DATA 会立即填充),需执行 REFRESH MATERIALIZED VIEW 或等待定时刷新。
BEGIN;
-- ============================================================
-- 1. 助教日度明细物化视图mv_dws_assistant_daily_detail_l1..l4
-- ============================================================
-- L1昨日 + 今日(营业日口径)
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l1;
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l1 AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE stat_date >= (dws.biz_date(NOW()) - '1 day'::interval)
WITH DATA;
-- L2近 30 天(营业日口径)
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l2;
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l2 AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE stat_date >= (dws.biz_date(NOW()) - '30 days'::interval)
WITH DATA;
-- L3近 90 天(营业日口径)
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l3;
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l3 AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE stat_date >= (dws.biz_date(NOW()) - '90 days'::interval)
WITH DATA;
-- L4近 6 个月(不含当月,营业日口径)
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l4;
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l4 AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE stat_date >= (date_trunc('month', dws.biz_date(NOW())::timestamp with time zone) - '6 mons'::interval)
AND stat_date < date_trunc('month', dws.biz_date(NOW())::timestamp with time zone)
WITH DATA;
-- ============================================================
-- 2. 财务日度汇总物化视图mv_dws_finance_daily_summary_l1..l4
-- ============================================================
-- L1昨日 + 今日(营业日口径)
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l1;
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l1 AS
SELECT * FROM dws.dws_finance_daily_summary
WHERE stat_date >= (dws.biz_date(NOW()) - '1 day'::interval)
WITH DATA;
-- L2近 30 天(营业日口径)
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l2;
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l2 AS
SELECT * FROM dws.dws_finance_daily_summary
WHERE stat_date >= (dws.biz_date(NOW()) - '30 days'::interval)
WITH DATA;
-- L3近 90 天(营业日口径)
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l3;
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l3 AS
SELECT * FROM dws.dws_finance_daily_summary
WHERE stat_date >= (dws.biz_date(NOW()) - '90 days'::interval)
WITH DATA;
-- L4近 6 个月(不含当月,营业日口径)
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l4;
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l4 AS
SELECT * FROM dws.dws_finance_daily_summary
WHERE stat_date >= (date_trunc('month', dws.biz_date(NOW())::timestamp with time zone) - '6 mons'::interval)
AND stat_date < date_trunc('month', dws.biz_date(NOW())::timestamp with time zone)
WITH DATA;
-- ============================================================
-- 3. 重建物化视图索引
-- ============================================================
CREATE INDEX IF NOT EXISTS idx_mv_assistant_daily_l1
ON dws.mv_dws_assistant_daily_detail_l1 USING btree (site_id, stat_date, assistant_id);
CREATE INDEX IF NOT EXISTS idx_mv_assistant_daily_l2
ON dws.mv_dws_assistant_daily_detail_l2 USING btree (site_id, stat_date, assistant_id);
CREATE INDEX IF NOT EXISTS idx_mv_assistant_daily_l3
ON dws.mv_dws_assistant_daily_detail_l3 USING btree (site_id, stat_date, assistant_id);
CREATE INDEX IF NOT EXISTS idx_mv_assistant_daily_l4
ON dws.mv_dws_assistant_daily_detail_l4 USING btree (site_id, stat_date, assistant_id);
CREATE INDEX IF NOT EXISTS idx_mv_finance_daily_l1
ON dws.mv_dws_finance_daily_summary_l1 USING btree (site_id, stat_date);
CREATE INDEX IF NOT EXISTS idx_mv_finance_daily_l2
ON dws.mv_dws_finance_daily_summary_l2 USING btree (site_id, stat_date);
CREATE INDEX IF NOT EXISTS idx_mv_finance_daily_l3
ON dws.mv_dws_finance_daily_summary_l3 USING btree (site_id, stat_date);
CREATE INDEX IF NOT EXISTS idx_mv_finance_daily_l4
ON dws.mv_dws_finance_daily_summary_l4 USING btree (site_id, stat_date);
COMMIT;
-- 验证 SQL迁移后执行
-- 1. 确认函数存在
-- SELECT dws.biz_date(NOW());
--
-- 2. 确认 8 个物化视图已重建
-- SELECT schemaname, matviewname FROM pg_matviews
-- WHERE schemaname = 'dws' AND matviewname LIKE 'mv_dws_%'
-- ORDER BY matviewname;
--
-- 3. 确认索引已创建
-- SELECT indexname FROM pg_indexes
-- WHERE schemaname = 'dws' AND tablename LIKE 'mv_dws_%'
-- ORDER BY indexname;
--
-- 4. 确认视图定义中包含 biz_date
-- SELECT matviewname, definition FROM pg_matviews
-- WHERE schemaname = 'dws' AND matviewname LIKE 'mv_dws_%'
-- AND definition LIKE '%biz_date%';
-- 回滚(恢复为自然日口径):
-- BEGIN;
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l1;
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l2;
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l3;
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l4;
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l1;
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l2;
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l3;
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l4;
-- -- 然后用 scripts/migrate/migrate_finalize.py 中的原始定义重建
-- COMMIT;

View File

@@ -1,63 +0,0 @@
-- =============================================================================
-- 迁移dws_assistant_order_contribution.tenant_id INTEGER → BIGINT
-- 原因:飞球 tenant_id 值域(如 2790683160709957远超 int4 上限(~21 亿)
-- 依赖app.v_dws_assistant_order_contribution 视图需先 DROP 再重建
-- 日期2026-03-03
-- 目标库etl_feiqiu / test_etl_feiqiu
-- =============================================================================
BEGIN;
-- 1. DROP 依赖的 RLS 视图
DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution CASCADE;
-- 2. ALTER 列类型
ALTER TABLE dws.dws_assistant_order_contribution
ALTER COLUMN tenant_id TYPE bigint;
-- 3. 重建 RLS 视图
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;
GRANT SELECT ON app.v_dws_assistant_order_contribution TO app_reader;
COMMIT;
-- =============================================================================
-- 回滚脚本
-- =============================================================================
-- BEGIN;
-- DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution CASCADE;
-- ALTER TABLE dws.dws_assistant_order_contribution ALTER COLUMN tenant_id TYPE integer;
-- 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;
-- GRANT SELECT ON app.v_dws_assistant_order_contribution TO app_reader;
-- COMMIT;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认列类型已变更
-- SELECT column_name, data_type, udt_name
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_assistant_order_contribution'
-- AND column_name = 'tenant_id';
-- 预期data_type = 'bigint', udt_name = 'int8'
-- 2. 确认视图已重建
-- SELECT table_schema, table_name
-- FROM information_schema.views
-- WHERE table_schema = 'app'
-- AND table_name = 'v_dws_assistant_order_contribution';
-- 预期1 行
-- 3. 确认视图中 tenant_id 类型正确
-- SELECT column_name, data_type
-- FROM information_schema.columns
-- WHERE table_schema = 'app'
-- AND table_name = 'v_dws_assistant_order_contribution'
-- AND column_name = 'tenant_id';
-- 预期data_type = 'bigint'

View File

@@ -1,27 +0,0 @@
-- =============================================================================
-- 迁移脚本dim_groupbuy_package_ex 新增团购详情字段
-- 日期2026-03-05
-- 说明从团购详情接口QueryPackageCouponInfo提取的 4 个 JSONB 字段,
-- 补充可用台区、助教服务、关联门店等维度信息
-- 需求:需求 4 验收标准 1
-- =============================================================================
ALTER TABLE dwd.dim_groupbuy_package_ex
ADD COLUMN IF NOT EXISTS table_area_ids JSONB,
ADD COLUMN IF NOT EXISTS table_area_names JSONB,
ADD COLUMN IF NOT EXISTS assistant_services JSONB,
ADD COLUMN IF NOT EXISTS groupon_site_infos JSONB;
COMMENT ON COLUMN dwd.dim_groupbuy_package_ex.table_area_ids IS '可用台区 ID 列表(来自详情接口 tableAreaId';
COMMENT ON COLUMN dwd.dim_groupbuy_package_ex.table_area_names IS '可用台区名称列表(来自详情接口 tableAreaNameList';
COMMENT ON COLUMN dwd.dim_groupbuy_package_ex.assistant_services IS '助教服务关联(来自详情接口 packageCouponAssistants';
COMMENT ON COLUMN dwd.dim_groupbuy_package_ex.groupon_site_infos IS '关联门店信息(来自详情接口 grouponSiteInfos';
-- =============================================================================
-- 回滚脚本(如需撤销)
-- 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;
-- =============================================================================

View File

@@ -1,102 +0,0 @@
-- =============================================================================
-- 迁移cfg_area_category 新增字段 + VIP 包厢拆分 + 显示名称更新
-- 日期2026-03-07
-- 说明:
-- 1. 新增 source_table_name、display_name、short_name 三个字段
-- 2. 删除 BILLIARD_VIP 类型VIP包厢默认归 BILLIARDV5 单独归 SNOOKER
-- 3. 更新四大项目类型的 display_name 和 short_name
-- 4. 更新模糊匹配规则(%VIP% 改为 BILLIARD
-- 回滚:见文件末尾 ROLLBACK 部分
-- =============================================================================
BEGIN;
-- 1. 新增字段
ALTER TABLE dws.cfg_area_category
ADD COLUMN IF NOT EXISTS source_table_name VARCHAR(100) DEFAULT NULL,
ADD COLUMN IF NOT EXISTS display_name VARCHAR(50) DEFAULT NULL,
ADD COLUMN IF NOT EXISTS short_name VARCHAR(20) DEFAULT NULL;
-- 1.1 唯一约束从 (source_area_name) 改为 (source_area_name, source_table_name)
-- 支持同一台区下按台桌名称细分映射
ALTER TABLE dws.cfg_area_category DROP CONSTRAINT IF EXISTS uk_cfg_area_category;
CREATE UNIQUE INDEX uk_cfg_area_category
ON dws.cfg_area_category (source_area_name, COALESCE(source_table_name, ''));
-- 2. 为现有记录填充 display_name 和 short_name
UPDATE dws.cfg_area_category SET display_name = '🎱 中式/追分', short_name = '🎱'
WHERE category_code = 'BILLIARD';
UPDATE dws.cfg_area_category SET display_name = '斯诺克', short_name = ''
WHERE category_code = 'SNOOKER';
UPDATE dws.cfg_area_category SET display_name = '🀄 麻将/棋牌', short_name = '🀄'
WHERE category_code = 'MAHJONG';
UPDATE dws.cfg_area_category SET display_name = '🎤 团建/K歌', short_name = '🎤'
WHERE category_code = 'KTV';
UPDATE dws.cfg_area_category SET display_name = '补时长', short_name = ''
WHERE category_code = 'SPECIAL';
UPDATE dws.cfg_area_category SET display_name = '其他', short_name = ''
WHERE category_code = 'OTHER';
-- 3. VIP包厢拆分将原 BILLIARD_VIP 精确匹配改为 BILLIARD
UPDATE dws.cfg_area_category
SET category_code = 'BILLIARD',
category_name = '🎱 中式/追分',
display_name = '🎱 中式/追分',
short_name = '🎱',
description = '台球VIP包厢V1-V4中八→ 归入中式/追分',
updated_at = NOW()
WHERE source_area_name = 'VIP包厢' AND match_type = 'EXACT';
-- 4. 新增 V5 → SNOOKER 的台桌级映射(优先级高于区域级)
INSERT INTO dws.cfg_area_category (
source_area_name, source_table_name, category_code, category_name,
display_name, short_name,
match_type, match_priority, is_active, description
) VALUES (
'VIP包厢', 'V5', 'SNOOKER', '斯诺克',
'斯诺克', '',
'EXACT', 5, TRUE,
'VIP包厢V5台→斯诺克台桌级精确匹配优先级高于区域级'
);
-- 5. 模糊匹配 %VIP% 改为 BILLIARD原为 BILLIARD_VIP
UPDATE dws.cfg_area_category
SET category_code = 'BILLIARD',
category_name = '🎱 中式/追分',
display_name = '🎱 中式/追分',
short_name = '🎱',
description = '模糊匹配:包含"VIP"的区域→归入中式/追分',
updated_at = NOW()
WHERE source_area_name = '%VIP%' AND match_type = 'LIKE';
-- 6. 更新所有分类的 category_name 为新显示名
UPDATE dws.cfg_area_category SET category_name = '🎱 中式/追分', updated_at = NOW()
WHERE category_code = 'BILLIARD' AND category_name != '🎱 中式/追分';
UPDATE dws.cfg_area_category SET category_name = '斯诺克', updated_at = NOW()
WHERE category_code = 'SNOOKER' AND category_name != '斯诺克';
UPDATE dws.cfg_area_category SET category_name = '🀄 麻将/棋牌', updated_at = NOW()
WHERE category_code = 'MAHJONG' AND category_name != '🀄 麻将/棋牌';
UPDATE dws.cfg_area_category SET category_name = '🎤 团建/K歌', updated_at = NOW()
WHERE category_code = 'KTV' AND category_name != '🎤 团建/K歌';
COMMIT;
-- =============================================================================
-- ROLLBACK如需回滚手动执行以下语句
-- =============================================================================
-- BEGIN;
-- DELETE FROM dws.cfg_area_category WHERE source_table_name = 'V5';
-- UPDATE dws.cfg_area_category SET category_code = 'BILLIARD_VIP', category_name = '台球VIP',
-- description = '台球VIPVIP包厢4台- V1-V4中八, V5斯诺克'
-- WHERE source_area_name = 'VIP包厢' AND match_type = 'EXACT';
-- UPDATE dws.cfg_area_category SET category_code = 'BILLIARD_VIP', category_name = '台球VIP'
-- WHERE source_area_name = '%VIP%' AND match_type = 'LIKE';
-- UPDATE dws.cfg_area_category SET category_name = '台球散台' WHERE category_code = 'BILLIARD';
-- UPDATE dws.cfg_area_category SET category_name = '斯诺克' WHERE category_code = 'SNOOKER';
-- UPDATE dws.cfg_area_category SET category_name = '麻将棋牌' WHERE category_code = 'MAHJONG';
-- UPDATE dws.cfg_area_category SET category_name = 'K歌娱乐' WHERE category_code = 'KTV';
-- ALTER TABLE dws.cfg_area_category DROP COLUMN IF EXISTS source_table_name;
-- ALTER TABLE dws.cfg_area_category DROP COLUMN IF EXISTS display_name;
-- ALTER TABLE dws.cfg_area_category DROP COLUMN IF EXISTS short_name;
-- COMMIT;

View File

@@ -1,95 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建项目标签表
-- 日期2026-03-07
-- 说明:新建 dws_assistant_project_tag 和 dws_member_project_tag 两张表,
-- 存储助教和客户按项目类型的时长占比标签
-- 依赖cfg_area_category2026-03-07 已更新,含 display_name/short_name
-- =============================================================================
-- 1. 助教项目标签表
CREATE TABLE IF NOT EXISTS dws.dws_assistant_project_tag (
id BIGSERIAL NOT NULL,
site_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
assistant_id BIGINT NOT NULL,
time_window VARCHAR(40) NOT NULL, -- TimeWindow 枚举值
category_code VARCHAR(30) NOT NULL, -- BILLIARD/SNOOKER/MAHJONG/KTV
category_name VARCHAR(50) NOT NULL, -- 显示名称(如 🎱 中式/追分)
short_name VARCHAR(10) NOT NULL, -- 简写(如 🎱)
duration_seconds BIGINT NOT NULL DEFAULT 0, -- 该项目总工作时长(秒)
total_seconds BIGINT NOT NULL DEFAULT 0, -- 所有四大项目总时长(秒)
percentage NUMERIC(5,4) NOT NULL DEFAULT 0, -- 占比0~1
is_tagged BOOLEAN NOT NULL DEFAULT FALSE, -- 占比≥25% 则为 TRUE
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT pk_dws_assistant_project_tag PRIMARY KEY (id),
CONSTRAINT uk_dws_assistant_project_tag
UNIQUE (site_id, assistant_id, time_window, category_code)
);
COMMENT ON TABLE dws.dws_assistant_project_tag IS '助教项目标签按时间窗口计算各项目时长占比≥25%分配标签';
COMMENT ON COLUMN dws.dws_assistant_project_tag.time_window IS '时间窗口THIS_MONTH/THIS_QUARTER/LAST_MONTH/LAST_3_MONTHS_EXCL_CURRENT/LAST_QUARTER/LAST_6_MONTHS';
COMMENT ON COLUMN dws.dws_assistant_project_tag.is_tagged IS '占比≥0.25时为TRUE表示该助教拥有此项目标签';
-- 2. 客户项目标签表
CREATE TABLE IF NOT EXISTS dws.dws_member_project_tag (
id BIGSERIAL NOT NULL,
site_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
time_window VARCHAR(40) NOT NULL, -- TimeWindow 枚举值
category_code VARCHAR(30) NOT NULL, -- BILLIARD/SNOOKER/MAHJONG/KTV
category_name VARCHAR(50) NOT NULL, -- 显示名称
short_name VARCHAR(10) NOT NULL, -- 简写
duration_seconds BIGINT NOT NULL DEFAULT 0, -- 该项目总计费时长(秒)
total_seconds BIGINT NOT NULL DEFAULT 0, -- 所有四大项目总时长(秒)
percentage NUMERIC(5,4) NOT NULL DEFAULT 0, -- 占比0~1
is_tagged BOOLEAN NOT NULL DEFAULT FALSE,
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT pk_dws_member_project_tag PRIMARY KEY (id),
CONSTRAINT uk_dws_member_project_tag
UNIQUE (site_id, member_id, time_window, category_code)
);
COMMENT ON TABLE dws.dws_member_project_tag IS '客户项目标签按时间窗口计算各项目消费时长占比≥25%分配标签';
COMMENT ON COLUMN dws.dws_member_project_tag.time_window IS '时间窗口LAST_30_DAYS/LAST_60_DAYS';
COMMENT ON COLUMN dws.dws_member_project_tag.is_tagged IS '占比≥0.25时为TRUE表示该客户拥有此项目标签';
-- 3. 索引(加速看板查询:按 site_id + time_window 筛选 is_tagged=TRUE 的标签)
CREATE INDEX IF NOT EXISTS idx_apt_site_window_tagged
ON dws.dws_assistant_project_tag (site_id, time_window)
WHERE is_tagged = TRUE;
CREATE INDEX IF NOT EXISTS idx_mpt_site_window_tagged
ON dws.dws_member_project_tag (site_id, time_window)
WHERE is_tagged = TRUE;
-- =============================================================================
-- 验证
-- =============================================================================
DO $$
DECLARE
v_apt_exists BOOLEAN;
v_mpt_exists BOOLEAN;
BEGIN
SELECT EXISTS(SELECT 1 FROM information_schema.tables
WHERE table_schema = 'dws' AND table_name = 'dws_assistant_project_tag')
INTO v_apt_exists;
SELECT EXISTS(SELECT 1 FROM information_schema.tables
WHERE table_schema = 'dws' AND table_name = 'dws_member_project_tag')
INTO v_mpt_exists;
IF NOT v_apt_exists THEN
RAISE EXCEPTION 'dws_assistant_project_tag 创建失败';
END IF;
IF NOT v_mpt_exists THEN
RAISE EXCEPTION 'dws_member_project_tag 创建失败';
END IF;
RAISE NOTICE '✅ 两张项目标签表创建成功';
END;
$$;

View File

@@ -1,67 +0,0 @@
-- 迁移:统一 DWS 层储值卡/充值卡支付字段命名
-- 日期2026-03-07
-- 关联:账务构成排查发现 cash_card_pay/cash_card_consume 语义歧义
-- cash_card_pay 取 balance_amount含赠送卡cash_card_consume 取 recharge_card_amount仅现金卡
-- 幂等RENAME + ADD 均为幂等操作(已存在则跳过)
BEGIN;
-- =============================================================================
-- 1. dws_member_visit_detailcash_card_pay → balance_pay + 新增 recharge_card_pay
-- =============================================================================
-- 1a. 重命名 cash_card_pay → balance_pay储值卡总额 = recharge + gift
ALTER TABLE dws.dws_member_visit_detail
RENAME COLUMN cash_card_pay TO balance_pay;
-- 1b. 新增 recharge_card_pay现金充值卡支付balance_pay 的子项)
ALTER TABLE dws.dws_member_visit_detail
ADD COLUMN IF NOT EXISTS recharge_card_pay NUMERIC(12,2) NOT NULL DEFAULT 0;
-- 1c. 回填 recharge_card_pay = balance_pay - gift_card_pay
UPDATE dws.dws_member_visit_detail
SET recharge_card_pay = balance_pay - gift_card_pay
WHERE recharge_card_pay = 0 AND balance_pay > 0;
-- =============================================================================
-- 2. dws_finance_daily_summarycash_card_consume → recharge_card_consume
-- =============================================================================
ALTER TABLE dws.dws_finance_daily_summary
RENAME COLUMN cash_card_consume TO recharge_card_consume;
COMMIT;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认 dws_member_visit_detail 字段存在
-- SELECT column_name FROM information_schema.columns
-- WHERE table_schema = 'dws' AND table_name = 'dws_member_visit_detail'
-- AND column_name IN ('balance_pay', 'recharge_card_pay', 'gift_card_pay')
-- ORDER BY column_name;
-- 预期3 行balance_pay, gift_card_pay, recharge_card_pay
-- 2. 确认 cash_card_pay 已不存在
-- SELECT column_name FROM information_schema.columns
-- WHERE table_schema = 'dws' AND table_name = 'dws_member_visit_detail'
-- AND column_name = 'cash_card_pay';
-- 预期0 行
-- 3. 确认 dws_finance_daily_summary 字段重命名
-- SELECT column_name FROM information_schema.columns
-- WHERE table_schema = 'dws' AND table_name = 'dws_finance_daily_summary'
-- AND column_name IN ('recharge_card_consume', 'gift_card_consume', 'card_consume_total');
-- 预期3 行
-- 4. 确认 cash_card_consume 已不存在
-- SELECT column_name FROM information_schema.columns
-- WHERE table_schema = 'dws' AND table_name = 'dws_finance_daily_summary'
-- AND column_name = 'cash_card_consume';
-- 预期0 行
-- 5. 验证 recharge_card_pay 回填正确balance_pay = recharge_card_pay + gift_card_pay
-- SELECT COUNT(*) AS mismatch_count
-- FROM dws.dws_member_visit_detail
-- WHERE ABS(balance_pay - recharge_card_pay - gift_card_pay) > 0.01;
-- 预期0

View File

@@ -1,58 +0,0 @@
-- 迁移DWS 层 ratio/margin/multiplier 字段精度扩展
-- 原因numeric(5,4) 只能存 ±0.9999,当比率 ≥ 1 或 < -1 时溢出P1 已爆 gross_margin
-- 日期2026-03-01
-- 注意:需先 DROP 依赖视图ALTER 后重建
BEGIN;
-- 1. DROP 依赖视图app schema 的 RLS 视图)
DROP VIEW IF EXISTS app.v_cfg_performance_tier CASCADE;
DROP VIEW IF EXISTS app.v_dws_assistant_finance_analysis CASCADE;
DROP VIEW IF EXISTS app.v_dws_assistant_recharge_commission CASCADE;
DROP VIEW IF EXISTS app.v_dws_assistant_salary_calc CASCADE;
DROP VIEW IF EXISTS app.v_dws_finance_discount_detail CASCADE;
DROP VIEW IF EXISTS app.v_dws_finance_income_structure CASCADE;
DROP VIEW IF EXISTS app.v_dws_member_assistant_intimacy CASCADE;
-- 2. ALTER 列类型numeric(5,4)/numeric(6,4) → numeric(7,4)
ALTER TABLE dws.cfg_performance_tier
ALTER COLUMN bonus_deduction_ratio TYPE numeric(7,4);
ALTER TABLE dws.dws_assistant_finance_analysis
ALTER COLUMN gross_margin TYPE numeric(7,4);
ALTER TABLE dws.dws_assistant_recharge_commission
ALTER COLUMN commission_ratio TYPE numeric(7,4);
ALTER TABLE dws.dws_assistant_salary_calc
ALTER COLUMN bonus_deduction_ratio TYPE numeric(7,4);
ALTER TABLE dws.dws_finance_discount_detail
ALTER COLUMN discount_ratio TYPE numeric(7,4);
ALTER TABLE dws.dws_finance_income_structure
ALTER COLUMN income_ratio TYPE numeric(7,4);
ALTER TABLE dws.dws_member_assistant_intimacy
ALTER COLUMN burst_multiplier TYPE numeric(7,4);
-- 3. 重建视图(使用 pg_get_viewdef 保存的原始定义)
-- 注意:实际执行时应从 _p1_migrate_with_views.py 自动保存/重建
-- 以下为手动重建模板,视图定义为 SELECT * FROM dws.xxx WHERE site_id = ...
CREATE OR REPLACE VIEW app.v_cfg_performance_tier AS
SELECT * FROM dws.cfg_performance_tier
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_finance_analysis AS
SELECT * FROM dws.dws_assistant_finance_analysis
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_recharge_commission AS
SELECT * FROM dws.dws_assistant_recharge_commission
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_salary_calc AS
SELECT * FROM dws.dws_assistant_salary_calc
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_discount_detail AS
SELECT * FROM dws.dws_finance_discount_detail
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_income_structure AS
SELECT * FROM dws.dws_finance_income_structure
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_assistant_intimacy AS
SELECT * FROM dws.dws_member_assistant_intimacy
WHERE site_id = current_setting('app.current_site_id')::bigint;
COMMIT;

View File

@@ -1,28 +0,0 @@
-- 回滚DWS 层 ratio/margin/multiplier 字段精度还原
-- 注意:如果已有数据超出原精度范围,回滚会失败,需先清理数据
-- 日期2026-03-01
BEGIN;
ALTER TABLE dws.cfg_performance_tier
ALTER COLUMN bonus_deduction_ratio TYPE numeric(5,4);
ALTER TABLE dws.dws_assistant_finance_analysis
ALTER COLUMN gross_margin TYPE numeric(5,4);
ALTER TABLE dws.dws_assistant_recharge_commission
ALTER COLUMN commission_ratio TYPE numeric(5,4);
ALTER TABLE dws.dws_assistant_salary_calc
ALTER COLUMN bonus_deduction_ratio TYPE numeric(5,4);
ALTER TABLE dws.dws_finance_discount_detail
ALTER COLUMN discount_ratio TYPE numeric(5,4);
ALTER TABLE dws.dws_finance_income_structure
ALTER COLUMN income_ratio TYPE numeric(5,4);
ALTER TABLE dws.dws_member_spending_power_index
ALTER COLUMN burst_multiplier TYPE numeric(6,4);
COMMIT;

View File

@@ -1,22 +0,0 @@
-- 迁移ods.goods_stock_summary 加 siteid 列
-- 原因API 返回记录不含 siteId但 DWD 层需要 site_id 做门店隔离
-- ODS 入库时从 app.store_id 注入
-- 日期2026-03-01
BEGIN;
ALTER TABLE ods.goods_stock_summary
ADD COLUMN IF NOT EXISTS siteid bigint;
-- 回填已有数据(单门店部署,所有记录属于同一 store_id
-- 实际 store_id 值需从 .env 的 STORE_ID 获取,此处用子查询从 goods_stock_movements 推断
UPDATE ods.goods_stock_summary s
SET siteid = (
SELECT DISTINCT m.siteid
FROM ods.goods_stock_movements m
WHERE m.siteid IS NOT NULL
LIMIT 1
)
WHERE s.siteid IS NULL;
COMMIT;

View File

@@ -1,334 +0,0 @@
-- =============================================================================
-- DWS 配置表初始数据
-- 版本: 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
-- 描述: 初始化配置表数据,包含绩效档位、等级定价、奖金规则、区域分类、技能映射
-- =============================================================================
-- =============================================================================
-- 1. cfg_performance_tier - 绩效档位配置(含历史口径)
-- 数据来源DWS 数据库处理需求.md
-- 三段时间线:
-- 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,
base_deduction, bonus_deduction_ratio, vacation_days, vacation_unlimited,
is_new_hire_tier, effective_from, effective_to, description
) VALUES
-- 旧方案至2024-12-31
('T0', '0档-淘汰压力', 0,
0, 100,
28.00, 0.50, 3, FALSE,
FALSE, '2000-01-01', '2024-12-31',
'旧方案H<100专业课抽成28元/小时打赏课抽成50%休假3天'),
('T1', '1档-及格档', 1,
100, 130,
18.00, 0.40, 4, FALSE,
FALSE, '2000-01-01', '2024-12-31',
'旧方案100≤H<130专业课抽成18元/小时打赏课抽成40%休假4天'),
('T2', '2档-良好档', 2,
130, 160,
15.00, 0.38, 4, FALSE,
FALSE, '2000-01-01', '2024-12-31',
'旧方案130≤H<160专业课抽成15元/小时打赏课抽成38%休假4天'),
('T3', '3档-优秀档', 3,
160, 190,
13.00, 0.35, 5, FALSE,
FALSE, '2000-01-01', '2024-12-31',
'旧方案160≤H<190专业课抽成13元/小时打赏课抽成35%休假5天'),
('T4', '4档-卓越加速档', 4,
190, 220,
10.00, 0.33, 6, FALSE,
FALSE, '2000-01-01', '2024-12-31',
'旧方案190≤H<220专业课抽成10元/小时打赏课抽成33%休假6天'),
('T5', '5档-冠军加速档', 5,
220, NULL,
8.00, 0.30, 0, TRUE,
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起
('T0', '0档-淘汰压力', 0,
0, 120,
28.00, 0.50, 3, FALSE,
FALSE, '2026-03-01', '9999-12-31',
'新方案H<120专业课抽成28元/小时打赏课抽成50%休假3天'),
('T1', '1档-及格档', 1,
120, 150,
18.00, 0.40, 4, FALSE,
FALSE, '2026-03-01', '9999-12-31',
'新方案120≤H<150专业课抽成18元/小时打赏课抽成40%休假4天'),
('T2', '2档-良好档', 2,
150, 180,
13.00, 0.35, 5, FALSE,
FALSE, '2026-03-01', '9999-12-31',
'新方案150≤H<180专业课抽成13元/小时打赏课抽成35%休假5天'),
('T3', '3档-优秀档', 3,
180, 210,
10.00, 0.30, 6, FALSE,
FALSE, '2026-03-01', '9999-12-31',
'新方案180≤H<210专业课抽成10元/小时打赏课抽成30%休假6天'),
('T4', '4档-销冠竞争', 4,
210, NULL,
8.00, 0.25, 0, TRUE,
FALSE, '2026-03-01', '9999-12-31',
'新方案H≥210专业课抽成8元/小时打赏课抽成25%,休假自由');
-- =============================================================================
-- 2. cfg_assistant_level_price - 助教等级定价
-- 说明:
-- - level_code 来自 dim_assistant.assistant_level
-- - 8=助教管理, 10=初级, 20=中级, 30=高级, 40=星级
-- - 价格为客户支付价格(对外价格),助教收入=客户支付-档位抽成
-- - 包厢课基础课统一138元/小时(不随等级变化)
-- =============================================================================
TRUNCATE TABLE dws.cfg_assistant_level_price RESTART IDENTITY CASCADE;
INSERT INTO dws.cfg_assistant_level_price (
level_code, level_name,
base_course_price, bonus_course_price,
effective_from, effective_to, description
) VALUES
(10, '初级',
98.00, 190.00,
'2000-01-01', '9999-12-31',
'初级助教基础课98元/时附加课190元/时(客户支付价格)'),
(20, '中级',
108.00, 190.00,
'2000-01-01', '9999-12-31',
'中级助教基础课108元/时附加课190元/时(客户支付价格)'),
(30, '高级',
118.00, 190.00,
'2000-01-01', '9999-12-31',
'高级助教基础课118元/时附加课190元/时(客户支付价格)'),
(40, '星级',
138.00, 190.00,
'2000-01-01', '9999-12-31',
'星级助教基础课138元/时附加课190元/时(客户支付价格)'),
(8, '助教管理',
98.00, 190.00,
'2000-01-01', '9999-12-31',
'助教管理:不参与客户服务计费,默认按初级价格');
-- =============================================================================
-- 3. cfg_bonus_rules - 奖金规则配置
-- 说明:
-- - 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;
INSERT INTO dws.cfg_bonus_rules (
rule_type, rule_code, rule_name,
threshold_hours, rank_position, bonus_amount,
is_cumulative, priority,
effective_from, effective_to, description
) VALUES
-- 冲刺奖金历史口径至2024-12-31
('SPRINT', 'SPRINT_190', '冲刺奖金190',
190.00, NULL, 300.00,
FALSE, 1,
'2000-01-01', '2024-12-31',
'历史口径业绩≥190小时获得300元冲刺奖金不累计'),
('SPRINT', 'SPRINT_220', '冲刺奖金220',
220.00, NULL, 800.00,
FALSE, 2,
'2000-01-01', '2024-12-31',
'历史口径业绩≥220小时获得800元冲刺奖金覆盖190档'),
-- 保底奖金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',
'月度排名第一获得1000元并列都算'),
('TOP_RANK', 'TOP_2', 'Top2排名奖金',
NULL, 2, 600.00,
FALSE, 0,
'2026-03-01', '9999-12-31',
'月度排名第二获得600元并列都算'),
('TOP_RANK', 'TOP_3', 'Top3排名奖金',
NULL, 3, 400.00,
FALSE, 0,
'2026-03-01', '9999-12-31',
'月度排名第三获得400元并列都算');
-- =============================================================================
-- 4. cfg_area_category - 台区分类映射
-- 说明:
-- - 将 dim_table.site_table_area_name 映射到项目分类
-- - 新增 source_table_name 支持台桌级细分(如 VIP包厢 V5 → SNOOKER
-- - 映射规则: 台桌精确 > 区域精确 > 模糊匹配 > 默认兜底
-- - 数据来源: BD_manual_dim_table.md 中的 site_table_area_name 实际分布
-- =============================================================================
TRUNCATE TABLE dws.cfg_area_category RESTART IDENTITY CASCADE;
INSERT INTO dws.cfg_area_category (
source_area_name, source_table_name, category_code, category_name,
display_name, short_name,
match_type, match_priority, is_active, description
) VALUES
-- VIP包厢台桌级映射优先级最高
('VIP包厢', 'V5', 'SNOOKER', '斯诺克', '斯诺克', '', 'EXACT', 5, TRUE,
'VIP包厢V5台→斯诺克台桌级精确匹配优先级高于区域级'),
-- 台球散台(精确匹配)
('A区', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
'台球散台A区18台- 中八/追分'),
('B区', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
'台球散台B区15台- 中八/追分'),
('C区', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
'台球散台C区6台- 中八/追分'),
('TV台', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
'台球散台TV台1台- 中八/追分'),
-- VIP包厢区域级V1-V4 归入中式/追分)
('VIP包厢', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
'台球VIP包厢V1-V4中八→ 归入中式/追分'),
-- 斯诺克区
('斯诺克区', NULL, 'SNOOKER', '斯诺克', '斯诺克', '', 'EXACT', 10, TRUE,
'斯诺克斯诺克区4台'),
-- 麻将区
('麻将房', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE,
'麻将棋牌麻将房5台'),
('M7', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE,
'麻将棋牌M72台'),
('M8', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE,
'麻将棋牌M81台'),
('666', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE,
'麻将棋牌6662台'),
('发财', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE,
'麻将棋牌发财1台'),
-- KTV/K包
('K包', NULL, 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE,
'K歌娱乐K包4台'),
('k包活动区', NULL, 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE,
'K歌娱乐k包活动区2台'),
('幸会158', NULL, 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE,
'K歌娱乐幸会1582台'),
-- 特殊区域
('补时长', NULL, 'SPECIAL', '补时长', '补时长', '', 'EXACT', 10, TRUE,
'特殊补时长7台- 用于时长补录'),
-- 模糊匹配规则
('%VIP%', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'LIKE', 50, TRUE,
'模糊匹配:包含"VIP"的区域→归入中式/追分'),
('%斯诺克%', NULL, 'SNOOKER', '斯诺克', '斯诺克', '', 'LIKE', 50, TRUE,
'模糊匹配:包含"斯诺克"的区域'),
('%麻将%', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'LIKE', 50, TRUE,
'模糊匹配:包含"麻将"的区域'),
('%K包%', NULL, 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'LIKE', 50, TRUE,
'模糊匹配:包含"K包"的区域'),
('%KTV%', NULL, 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'LIKE', 50, TRUE,
'模糊匹配:包含"KTV"的区域'),
-- 默认兜底
('DEFAULT', NULL, 'OTHER', '其他', '其他', '', 'DEFAULT', 999, TRUE,
'兜底规则:无法匹配的区域归入其他');
-- =============================================================================
-- 5. cfg_skill_type - 技能→课程类型映射
-- 说明:
-- - 将 skill_id 映射到课程类型
-- - 基础课/陪打: skill_id = 2791903611396869
-- - 附加课/超休: skill_id = 2807440316432197
-- - 避免依赖 skill_name 文本匹配
-- =============================================================================
TRUNCATE TABLE dws.cfg_skill_type RESTART IDENTITY CASCADE;
INSERT INTO dws.cfg_skill_type (
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~8. 优惠类型/支出类型/平台类型 — 作为代码常量使用,不单独建表
-- =============================================================================
-- 优惠类型: GROUPBUY/VIP/GIFT_CARD/MANUAL/ROUNDING/BIG_CUSTOMER/OTHER
-- 支出类型: RENT/UTILITY/PROPERTY/SALARY/REIMBURSE/PLATFORM_FEE/OTHER
-- 平台类型: MEITUAN/DOUYIN/DIANPING/OTHER
-- =============================================================================
-- 验证数据插入
-- =============================================================================
DO $$
DECLARE
v_tier_count INTEGER;
v_price_count INTEGER;
v_bonus_count INTEGER;
v_area_count INTEGER;
v_skill_count INTEGER;
BEGIN
SELECT COUNT(*) INTO v_tier_count FROM dws.cfg_performance_tier;
SELECT COUNT(*) INTO v_price_count FROM dws.cfg_assistant_level_price;
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;
RAISE NOTICE ' - cfg_bonus_rules: % 条', v_bonus_count;
RAISE NOTICE ' - cfg_area_category: % 条', v_area_count;
RAISE NOTICE ' - cfg_skill_type: % 条', v_skill_count;
END;
$$;

View File

@@ -1,244 +0,0 @@
-- =============================================================================
-- 指数算法参数初始化脚本
-- 版本: v3.0
-- 创建日期: 2026-02-13
-- 描述: 仅保留 RS / OS / MS / ML / NCI / WBI 指数参数(已移除 RECALL / INTIMACY
-- AI_CHANGELOG [2026-02-13] 移除 RECALL/INTIMACY 参数及 ML 废弃参数source_mode/recharge_attribute_hours
-- =============================================================================
-- 清理旧版指数参数
DELETE FROM dws.cfg_index_parameters WHERE index_type IN ('RECALL', 'INTIMACY');
-- 清理 ML 已废弃参数
DELETE FROM dws.cfg_index_parameters WHERE index_type = 'ML' AND param_name IN ('source_mode', 'recharge_attribute_hours');
INSERT INTO dws.cfg_index_parameters
(index_type, param_name, param_value, description, effective_from)
VALUES
('NCI', 'active_new_penalty', 0.200000, 'active-new suppression multiplier', DATE '2026-02-06'),
('NCI', 'active_new_recency_days', 7.000000, 'active-new recency window (days)', DATE '2026-02-06'),
('NCI', 'active_new_visit_threshold_14d', 2.000000, 'active-new threshold in 14d visits', DATE '2026-02-06'),
('NCI', 'amount_base_M0', 300.000000, 'spend log base M0', DATE '2026-02-06'),
('NCI', 'balance_base_B0', 500.000000, 'balance log base B0', DATE '2026-02-06'),
('NCI', 'compression_mode', 0.000000, 'compression mode', DATE '2026-02-06'),
('NCI', 'enable_stop_high_balance_exception', 0.000000, 'enable high-balance STOP exception', DATE '2026-02-06'),
('NCI', 'ewma_alpha', 0.200000, 'EWMA alpha', DATE '2026-02-06'),
('NCI', 'h_recharge', 7.000000, 'recharge decay half-life (days)', DATE '2026-02-06'),
('NCI', 'high_balance_threshold', 1000.000000, 'high-balance threshold', DATE '2026-02-06'),
('NCI', 'lookback_days_recency', 60.000000, 'recency lookback window (days)', DATE '2026-02-06'),
('NCI', 'new_days_threshold', 30.000000, 'new member days threshold', DATE '2026-02-06'),
('NCI', 'new_recharge_max_visits', 10.000000, 'max visits for new-recharge grouping', DATE '2026-02-06'),
('NCI', 'new_visit_threshold', 2.000000, 'new member visit threshold', DATE '2026-02-06'),
('NCI', 'no_touch_days_new', 3.000000, 'no-touch threshold (days)', DATE '2026-02-06'),
('NCI', 'percentile_lower', 5.000000, 'lower percentile', DATE '2026-02-06'),
('NCI', 'percentile_upper', 95.000000, 'upper percentile', DATE '2026-02-06'),
('NCI', 'recharge_recent_days', 14.000000, 'recent recharge window (days)', DATE '2026-02-06'),
('NCI', 'salvage_end', 60.000000, 'salvage decay end day', DATE '2026-02-06'),
('NCI', 'salvage_start', 30.000000, 'salvage decay start day', DATE '2026-02-06'),
('NCI', 't2_target_days', 7.000000, 'second-visit target window (days)', DATE '2026-02-06'),
('NCI', 'use_smoothing', 1.000000, 'enable smoothing', DATE '2026-02-06'),
('NCI', 'value_w_bal', 0.800000, 'value weight for balance', DATE '2026-02-06'),
('NCI', 'value_w_spend', 1.000000, 'value weight for spend', DATE '2026-02-06'),
('NCI', 'visit_lookback_days', 180.000000, 'visit history lookback (days)', DATE '2026-02-06'),
('NCI', 'w_need', 1.600000, 'need weight', DATE '2026-02-06'),
('NCI', 'w_re', 0.800000, 'recharge pressure weight', DATE '2026-02-06'),
('NCI', 'w_value', 1.000000, 'value weight', DATE '2026-02-06'),
('NCI', 'w_welcome', 1.000000, 'welcome-stage weight', DATE '2026-02-06'),
('NCI', 'welcome_window_days', 3.000000, 'welcome outreach window for first touch (days)', DATE '2026-02-06'),
('WBI', 'amount_base_M0', 300.000000, 'spend log base M0', DATE '2026-02-06'),
('WBI', 'balance_base_B0', 500.000000, 'balance log base B0', DATE '2026-02-06'),
('WBI', 'compression_mode', 0.000000, 'compression mode', DATE '2026-02-06'),
('WBI', 'enable_stop_high_balance_exception', 0.000000, 'enable high-balance STOP exception', DATE '2026-02-06'),
('WBI', 'ewma_alpha', 0.200000, 'EWMA alpha', DATE '2026-02-06'),
('WBI', 'h_recharge', 7.000000, 'recharge decay half-life (days)', DATE '2026-02-06'),
('WBI', 'high_balance_threshold', 1000.000000, 'high-balance threshold', DATE '2026-02-06'),
('WBI', 'lookback_days_recency', 60.000000, 'recency lookback window (days)', DATE '2026-02-06'),
('WBI', 'new_days_threshold', 30.000000, 'new member days threshold', DATE '2026-02-06'),
('WBI', 'new_recharge_max_visits', 10.000000, 'max visits for new-recharge grouping', DATE '2026-02-06'),
('WBI', 'new_visit_threshold', 2.000000, 'new member visit threshold', DATE '2026-02-06'),
('WBI', 'overdue_alpha', 2.000000, 'overdue fallback alpha', DATE '2026-02-06'),
('WBI', 'overdue_weight_blend_min_samples', 8.000000, 'minimum samples to fully trust weighted overdue CDF', DATE '2026-02-07'),
('WBI', 'overdue_weight_halflife_days', 30.000000, 'overdue weighted-CDF interval half-life (days)', DATE '2026-02-07'),
('WBI', 'percentile_lower', 5.000000, 'lower percentile', DATE '2026-02-06'),
('WBI', 'percentile_upper', 95.000000, 'upper percentile', DATE '2026-02-06'),
('WBI', 'recency_gate_days', 14.000000, 'recency suppression gate center (days)', DATE '2026-02-06'),
('WBI', 'recency_gate_slope_days', 3.000000, 'recency suppression slope (days)', DATE '2026-02-06'),
('WBI', 'recency_hard_floor_days', 14.000000, 'hard floor for winback recency (days)', DATE '2026-02-06'),
('WBI', 'recharge_recent_days', 14.000000, 'recent recharge window (days)', DATE '2026-02-06'),
('WBI', 'use_smoothing', 1.000000, 'enable smoothing', DATE '2026-02-06'),
('WBI', 'value_w_bal', 1.000000, 'value weight for balance', DATE '2026-02-06'),
('WBI', 'value_w_spend', 1.000000, 'value weight for spend', DATE '2026-02-06'),
('WBI', 'visit_lookback_days', 180.000000, 'visit history lookback (days)', DATE '2026-02-06'),
('WBI', 'w_drop', 1.000000, 'drop weight', DATE '2026-02-06'),
('WBI', 'w_over', 2.000000, 'overdue weight', DATE '2026-02-06'),
('WBI', 'w_re', 0.400000, 'recharge pressure weight', DATE '2026-02-06'),
('WBI', 'w_value', 1.200000, 'value weight', DATE '2026-02-06')
ON CONFLICT (index_type, param_name, effective_from) DO UPDATE SET
param_value = EXCLUDED.param_value,
description = EXCLUDED.description,
updated_at = NOW();
-- =============================================================================
-- 关系指数RS/OS/MS/ML参数
-- 生效时间:北京时间 2026-01-01按数据库日期管理
-- =============================================================================
INSERT INTO dws.cfg_index_parameters
(index_type, param_name, param_value, description, effective_from)
VALUES
-- RS关系强度
('RS', 'lookback_days', 60.000000, '服务行为回溯窗口(天)', DATE '2026-01-01'),
('RS', 'session_merge_hours', 4.000000, '会话合并阈值(小时)', DATE '2026-01-01'),
('RS', 'incentive_weight', 1.500000, '激励课权重', DATE '2026-01-01'),
('RS', 'halflife_session', 14.000000, '会话半衰期(天)', DATE '2026-01-01'),
('RS', 'halflife_last', 10.000000, '最近一次服务半衰期(天)', DATE '2026-01-01'),
('RS', 'weight_f', 1.000000, '频次项权重', DATE '2026-01-01'),
('RS', 'weight_d', 0.700000, '时长项权重', DATE '2026-01-01'),
('RS', 'gate_alpha', 0.600000, '最近服务门控指数', DATE '2026-01-01'),
('RS', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-01-01'),
('RS', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-01-01'),
('RS', 'compression_mode', 1.000000, '压缩模式0=none,1=log1p,2=asinh', DATE '2026-01-01'),
('RS', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-01-01'),
('RS', 'ewma_alpha', 0.200000, 'EWMA平滑系数', DATE '2026-01-01'),
-- OS归属份额
('OS', 'min_rs_raw_for_ownership', 0.050000, '参与归属计算的最小RS_raw', DATE '2026-01-01'),
('OS', 'min_total_rs_raw', 0.100000, '形成稳定归属的最小sum_rs', DATE '2026-01-01'),
('OS', 'ownership_main_threshold', 0.600000, '主责阈值', DATE '2026-01-01'),
('OS', 'ownership_comanage_threshold', 0.350000, '共管阈值', DATE '2026-01-01'),
('OS', 'ownership_gap_threshold', 0.150000, '主责与次席份额差阈值', DATE '2026-01-01'),
('OS', 'eps', 0.000001, '数值稳定项', DATE '2026-01-01'),
-- MS升温动量
('MS', 'lookback_days', 60.000000, '服务行为回溯窗口(天)', DATE '2026-01-01'),
('MS', 'session_merge_hours', 4.000000, '会话合并阈值(小时)', DATE '2026-01-01'),
('MS', 'incentive_weight', 1.500000, '激励课权重', DATE '2026-01-01'),
('MS', 'halflife_short', 7.000000, '短期半衰期(天)', DATE '2026-01-01'),
('MS', 'halflife_long', 30.000000, '长期半衰期(天)', DATE '2026-01-01'),
('MS', 'eps', 0.000001, '数值稳定项', DATE '2026-01-01'),
('MS', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-01-01'),
('MS', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-01-01'),
('MS', 'compression_mode', 1.000000, '压缩模式0=none,1=log1p,2=asinh', DATE '2026-01-01'),
('MS', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-01-01'),
('MS', 'ewma_alpha', 0.200000, 'EWMA平滑系数', DATE '2026-01-01'),
-- ML付费关联
('ML', 'lookback_days', 60.000000, '充值行为回溯窗口(天)', DATE '2026-01-01'),
('ML', 'amount_base', 500.000000, '金额压缩基准', DATE '2026-01-01'),
('ML', 'halflife_recharge', 21.000000, '充值半衰期(天)', DATE '2026-01-01'),
('ML', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-01-01'),
('ML', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-01-01'),
('ML', 'compression_mode', 1.000000, '压缩模式0=none,1=log1p,2=asinh', DATE '2026-01-01'),
('ML', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-01-01'),
('ML', 'ewma_alpha', 0.200000, 'EWMA平滑系数', 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();
-- =============================================================================
-- 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();
-- =============================================================================
-- 验证
-- =============================================================================
DO $$
DECLARE
rs_count INTEGER;
os_count INTEGER;
ms_count INTEGER;
ml_count INTEGER;
nci_count INTEGER;
wbi_count INTEGER;
spi_count INTEGER;
BEGIN
SELECT COUNT(*) INTO rs_count
FROM dws.cfg_index_parameters
WHERE index_type = 'RS';
SELECT COUNT(*) INTO os_count
FROM dws.cfg_index_parameters
WHERE index_type = 'OS';
SELECT COUNT(*) INTO ms_count
FROM dws.cfg_index_parameters
WHERE index_type = 'MS';
SELECT COUNT(*) INTO ml_count
FROM dws.cfg_index_parameters
WHERE index_type = 'ML';
SELECT COUNT(*) INTO nci_count
FROM dws.cfg_index_parameters
WHERE index_type = 'NCI';
SELECT COUNT(*) INTO wbi_count
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
index_type,
param_name,
param_value,
description,
effective_from
FROM dws.cfg_index_parameters
ORDER BY index_type, param_name, effective_from;

View File

@@ -1,38 +0,0 @@
-- CHANGE 2026-02-15 | 修复 schema 引用etl_admin → meta对齐新库 etl_feiqiu 六层架构)
-- 将新的 ODS 任务注册到 meta.etl_task按需替换 store_id
WITH target_store AS (
SELECT 2790685415443269::bigint AS store_id -- TODO: 替换为实际 store_id
),
task_codes AS (
SELECT unnest(ARRAY[
-- Must match tasks/ods_tasks.py (ENABLED_ODS_CODES)
'ODS_ASSISTANT_ACCOUNT',
'ODS_ASSISTANT_LEDGER',
-- CHANGE [2026-02-24] intent: 移除 ODS_ASSISTANT_ABOLISH全链路已清理表已删除
'ODS_SETTLEMENT_RECORDS',
'ODS_TABLE_USE',
'ODS_PAYMENT',
'ODS_REFUND',
'ODS_PLATFORM_COUPON',
'ODS_MEMBER',
'ODS_MEMBER_CARD',
'ODS_MEMBER_BALANCE',
'ODS_RECHARGE_SETTLE',
'ODS_GROUP_PACKAGE',
'ODS_GROUP_BUY_REDEMPTION',
'ODS_INVENTORY_STOCK',
'ODS_INVENTORY_CHANGE',
'ODS_TABLES',
'ODS_GOODS_CATEGORY',
'ODS_STORE_GOODS',
'ODS_STORE_GOODS_SALES',
'ODS_TABLE_FEE_DISCOUNT',
'ODS_TENANT_GOODS'
]) AS task_code
)
INSERT INTO meta.etl_task (task_code, store_id, enabled)
SELECT t.task_code, s.store_id, TRUE
FROM task_codes t CROSS JOIN target_store s
ON CONFLICT (task_code, store_id) DO UPDATE
SET enabled = EXCLUDED.enabled;

View File

@@ -1,68 +0,0 @@
-- CHANGE 2026-02-15 | 修复 schema 引用etl_admin → meta对齐新库 etl_feiqiu 六层架构)
-- Seed scheduler-compatible tasks into meta.etl_task.
--
-- Notes:
-- - These task_code values must match orchestration/task_registry.py.
-- - ODS_* tasks are intentionally excluded here because they don't follow the
-- BaseTask(cursor_data) scheduler interface in this repo version.
--
-- Usage (example):
-- psql "%PG_DSN%" -f etl_billiards/database/seed_scheduler_tasks.sql
--
WITH target_store AS (
SELECT 2790685415443269::bigint AS store_id -- TODO: replace with your store_id
),
task_codes AS (
SELECT unnest(ARRAY[
-- CHANGE [2026-02-24] intent: 移除 ASSISTANT_ABOLISH全链路已清理
'ASSISTANTS',
'COUPON_USAGE',
'CHECK_CUTOFF',
'DWD_LOAD_FROM_ODS',
'DWD_QUALITY_CHECK',
'INIT_DWD_SCHEMA',
'INIT_DWS_SCHEMA',
'INIT_ODS_SCHEMA',
'INVENTORY_CHANGE',
'LEDGER',
'MANUAL_INGEST',
'MEMBERS',
'MEMBERS_DWD',
'ODS_JSON_ARCHIVE',
'ORDERS',
'PACKAGES_DEF',
'PAYMENTS',
'PAYMENTS_DWD',
'PRODUCTS',
'REFUNDS',
'TABLE_DISCOUNT',
'TABLES',
'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',
'DWS_ML_MANUAL_IMPORT'
]) AS task_code
)
INSERT INTO meta.etl_task (task_code, store_id, enabled)
SELECT t.task_code, s.store_id, TRUE
FROM task_codes t CROSS JOIN target_store s
ON CONFLICT (task_code, store_id) DO UPDATE
SET enabled = EXCLUDED.enabled,
updated_at = now();

View File

@@ -1,53 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建 auth/biz Schema 与 app_user 权限配置
-- 日期2026-02-24
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在业务库中创建 auth用户认证和 biz业务数据两个 Schema
-- 并授予 app_user 角色完整的 CRUD 权限(含未来新表自动授权)。
-- 不操作 public Schema保留其中现有系统管理表不受影响。
-- 前提app_user 角色已由 DBA 预创建
-- 需求1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 4.2, 4.3, 4.4, 4.5, 4.6
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 创建 auth Schema用户认证、权限、映射
-- ---------------------------------------------------------------------------
CREATE SCHEMA IF NOT EXISTS auth;
-- ---------------------------------------------------------------------------
-- 2. 创建 biz Schema业务数据
-- ---------------------------------------------------------------------------
CREATE SCHEMA IF NOT EXISTS biz;
-- ---------------------------------------------------------------------------
-- 3. 授予 app_user 对 auth Schema 的 USAGE + CRUD 权限
-- ---------------------------------------------------------------------------
GRANT USAGE ON SCHEMA auth TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA auth TO app_user;
-- ---------------------------------------------------------------------------
-- 4. 授予 app_user 对 biz Schema 的 USAGE + CRUD 权限
-- ---------------------------------------------------------------------------
GRANT USAGE ON SCHEMA biz TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA biz TO app_user;
-- ---------------------------------------------------------------------------
-- 5. 设置 ALTER DEFAULT PRIVILEGES未来新表自动授权
-- ---------------------------------------------------------------------------
ALTER DEFAULT PRIVILEGES IN SCHEMA auth
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA biz
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- ALTER DEFAULT PRIVILEGES IN SCHEMA biz REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM app_user;
-- ALTER DEFAULT PRIVILEGES IN SCHEMA auth REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM app_user;
-- REVOKE SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA biz FROM app_user;
-- REVOKE USAGE ON SCHEMA biz FROM app_user;
-- REVOKE SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA auth FROM app_user;
-- REVOKE USAGE ON SCHEMA auth FROM app_user;
-- DROP SCHEMA IF EXISTS biz CASCADE;
-- DROP SCHEMA IF EXISTS auth CASCADE;

View File

@@ -1,71 +0,0 @@
-- =============================================================================
-- 迁移脚本:配置 FDW 跨库映射ETL 库 → 业务库)
-- 日期2026-02-24
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:通过 postgres_fdw 将 ETL 库 app Schema 的 RLS 视图映射为业务库
-- fdw_etl Schema 的只读外部表,使后端无需直连 ETL 库即可读取汇总/维度数据。
-- 前提:
-- 1. ETL 库已部署 app Schema 及 RLS 视图2026-02-24__p1_create_app_schema_rls_views.sql
-- 2. ETL 库已创建 app_reader 只读角色
-- 3. 业务库已创建 app_user 角色
-- 需求3.1, 3.2, 3.3, 3.4, 3.7, 4.2, 4.3, 4.4, 4.5, 4.6
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 安装 postgres_fdw 扩展
-- ---------------------------------------------------------------------------
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
-- ---------------------------------------------------------------------------
-- 2. 创建外部服务器(指向 ETL 库)
-- host / dbname / port 使用占位符 '***',部署时按环境替换
-- 服务器名使用通用名(不含环境前缀),通过连接参数区分环境
-- ---------------------------------------------------------------------------
CREATE SERVER IF NOT EXISTS etl_feiqiu_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host '***', dbname '***', port '***');
-- ---------------------------------------------------------------------------
-- 3. 创建用户映射(只读角色)
-- app_user = test_zqyy_app 侧的应用连接角色
-- app_reader = ETL 库侧的只读角色
-- 密码占位符 '***',部署时替换为真实凭据
-- ---------------------------------------------------------------------------
CREATE USER MAPPING IF NOT EXISTS FOR app_user
SERVER etl_feiqiu_server
OPTIONS (user 'app_reader', password '***');
-- ---------------------------------------------------------------------------
-- 4. 创建 fdw_etl Schema幂等处理先 DROP 再重建)
-- IMPORT FOREIGN SCHEMA 不是幂等的(外部表已存在会报错),
-- 因此采用 DROP CASCADE + 重建的方式确保可重复执行。
-- ---------------------------------------------------------------------------
DROP SCHEMA IF EXISTS fdw_etl CASCADE;
CREATE SCHEMA fdw_etl;
-- ---------------------------------------------------------------------------
-- 5. 批量导入 ETL 库 app Schema 的所有外部表到 fdw_etl
-- ---------------------------------------------------------------------------
IMPORT FOREIGN SCHEMA app
FROM SERVER etl_feiqiu_server
INTO fdw_etl;
-- ---------------------------------------------------------------------------
-- 6. 授权:允许 app_user 访问 fdw_etl Schema 及其外部表
-- ---------------------------------------------------------------------------
GRANT USAGE ON SCHEMA fdw_etl TO app_user;
GRANT SELECT ON ALL TABLES IN SCHEMA fdw_etl TO app_user;
-- 未来新导入的外部表自动授权
ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_etl GRANT SELECT ON TABLES TO app_user;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_etl REVOKE SELECT ON TABLES FROM app_user;
-- REVOKE SELECT ON ALL TABLES IN SCHEMA fdw_etl FROM app_user;
-- REVOKE USAGE ON SCHEMA fdw_etl FROM app_user;
-- DROP SCHEMA IF EXISTS fdw_etl CASCADE;
-- DROP USER MAPPING IF EXISTS FOR app_user SERVER etl_feiqiu_server;
-- DROP SERVER IF EXISTS etl_feiqiu_server CASCADE;
-- DROP EXTENSION IF EXISTS postgres_fdw;

View File

@@ -1,280 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建 auth Schema 认证业务表
-- 日期2026-02-25
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在 auth Schema 下创建用户认证系统所需的 8 张业务表:
-- users、user_applications、site_code_mapping、roles、permissions、
-- role_permissions、user_site_roles、user_assistant_binding
-- 包含字段定义、约束、索引、外键。使用 IF NOT EXISTS 幂等语法。
-- 前提auth Schema 已由 P1 迁移脚本创建
-- 2026-02-24__p1_create_auth_biz_schemas.sql
-- 需求1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10
-- =============================================================================
-- 确保 auth Schema 存在(幂等)
CREATE SCHEMA IF NOT EXISTS auth;
-- ---------------------------------------------------------------------------
-- 1. users — 微信用户主表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.users (
id SERIAL PRIMARY KEY,
wx_openid VARCHAR(128) NOT NULL,
wx_union_id VARCHAR(128),
wx_avatar_url VARCHAR(512),
nickname VARCHAR(50),
phone VARCHAR(20),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- wx_openid 唯一约束(幂等:先检查再添加)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_users_wx_openid' AND conrelid = 'auth.users'::regclass
) THEN
ALTER TABLE auth.users ADD CONSTRAINT uq_users_wx_openid UNIQUE (wx_openid);
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_users_wx_openid ON auth.users (wx_openid);
-- ---------------------------------------------------------------------------
-- 2. site_code_mapping — 球房ID与门店映射表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.site_code_mapping (
id SERIAL PRIMARY KEY,
site_code VARCHAR(10) NOT NULL,
site_id BIGINT NOT NULL,
site_name VARCHAR(100),
tenant_id INT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- site_code 唯一约束格式2字母+3数字如 AB123
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_site_code_mapping_site_code' AND conrelid = 'auth.site_code_mapping'::regclass
) THEN
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_code UNIQUE (site_code);
END IF;
END $$;
-- site_id 唯一约束
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_site_code_mapping_site_id' AND conrelid = 'auth.site_code_mapping'::regclass
) THEN
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_id UNIQUE (site_id);
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_site_code_mapping_site_code ON auth.site_code_mapping (site_code);
-- ---------------------------------------------------------------------------
-- 3. roles — 角色定义表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.roles (
id SERIAL PRIMARY KEY,
code VARCHAR(50) NOT NULL,
name VARCHAR(100),
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_roles_code' AND conrelid = 'auth.roles'::regclass
) THEN
ALTER TABLE auth.roles ADD CONSTRAINT uq_roles_code UNIQUE (code);
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 4. permissions — 权限定义表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.permissions (
id SERIAL PRIMARY KEY,
code VARCHAR(50) NOT NULL,
name VARCHAR(100),
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_permissions_code' AND conrelid = 'auth.permissions'::regclass
) THEN
ALTER TABLE auth.permissions ADD CONSTRAINT uq_permissions_code UNIQUE (code);
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 5. role_permissions — 角色-权限关联表(联合主键)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.role_permissions (
role_id INT NOT NULL,
permission_id INT NOT NULL,
PRIMARY KEY (role_id, permission_id)
);
-- 外键(幂等添加)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_role_permissions_role_id' AND conrelid = 'auth.role_permissions'::regclass
) THEN
ALTER TABLE auth.role_permissions
ADD CONSTRAINT fk_role_permissions_role_id
FOREIGN KEY (role_id) REFERENCES auth.roles (id) ON DELETE CASCADE;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_role_permissions_permission_id' AND conrelid = 'auth.role_permissions'::regclass
) THEN
ALTER TABLE auth.role_permissions
ADD CONSTRAINT fk_role_permissions_permission_id
FOREIGN KEY (permission_id) REFERENCES auth.permissions (id) ON DELETE CASCADE;
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 6. user_applications — 用户入驻申请表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.user_applications (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
site_code VARCHAR(10) NOT NULL,
site_id BIGINT,
applied_role_text VARCHAR(100) NOT NULL,
employee_number VARCHAR(50),
phone VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
reviewer_id INT,
review_note TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
reviewed_at TIMESTAMPTZ
);
-- 外键
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_applications_user_id' AND conrelid = 'auth.user_applications'::regclass
) THEN
ALTER TABLE auth.user_applications
ADD CONSTRAINT fk_user_applications_user_id
FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE;
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_user_applications_user_id ON auth.user_applications (user_id);
CREATE INDEX IF NOT EXISTS ix_user_applications_status ON auth.user_applications (status);
-- ---------------------------------------------------------------------------
-- 7. user_site_roles — 用户-门店-角色关联表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.user_site_roles (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
site_id BIGINT NOT NULL,
role_id INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 外键
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_site_roles_user_id' AND conrelid = 'auth.user_site_roles'::regclass
) THEN
ALTER TABLE auth.user_site_roles
ADD CONSTRAINT fk_user_site_roles_user_id
FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_site_roles_role_id' AND conrelid = 'auth.user_site_roles'::regclass
) THEN
ALTER TABLE auth.user_site_roles
ADD CONSTRAINT fk_user_site_roles_role_id
FOREIGN KEY (role_id) REFERENCES auth.roles (id) ON DELETE CASCADE;
END IF;
END $$;
-- 唯一约束:同一用户在同一门店下不能重复分配同一角色
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_user_site_roles_user_site_role' AND conrelid = 'auth.user_site_roles'::regclass
) THEN
ALTER TABLE auth.user_site_roles
ADD CONSTRAINT uq_user_site_roles_user_site_role UNIQUE (user_id, site_id, role_id);
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_user_site_roles_user_site ON auth.user_site_roles (user_id, site_id);
-- ---------------------------------------------------------------------------
-- 8. user_assistant_binding — 用户-人员绑定表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.user_assistant_binding (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
site_id BIGINT NOT NULL,
assistant_id BIGINT,
staff_id BIGINT,
binding_type VARCHAR(20) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 外键
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_assistant_binding_user_id' AND conrelid = 'auth.user_assistant_binding'::regclass
) THEN
ALTER TABLE auth.user_assistant_binding
ADD CONSTRAINT fk_user_assistant_binding_user_id
FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE;
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 授予 app_user 对新表的 SEQUENCE 使用权限SERIAL 字段需要)
-- ---------------------------------------------------------------------------
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA auth TO app_user;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- DROP TABLE IF EXISTS auth.user_assistant_binding CASCADE;
-- DROP TABLE IF EXISTS auth.user_site_roles CASCADE;
-- DROP TABLE IF EXISTS auth.user_applications CASCADE;
-- DROP TABLE IF EXISTS auth.role_permissions CASCADE;
-- DROP TABLE IF EXISTS auth.permissions CASCADE;
-- DROP TABLE IF EXISTS auth.roles CASCADE;
-- DROP TABLE IF EXISTS auth.site_code_mapping CASCADE;
-- DROP TABLE IF EXISTS auth.users CASCADE;

View File

@@ -1,75 +0,0 @@
-- =============================================================================
-- 种子数据脚本:预置权限列表、默认角色、角色-权限映射
-- 日期2026-02-25
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在 auth Schema 的 permissions、roles、role_permissions 表中插入种子数据。
-- 使用 ON CONFLICT DO NOTHING 幂等语法,重复执行不会产生重复数据。
-- 前提auth.roles、auth.permissions、auth.role_permissions 表已由
-- 2026-02-25__p3_create_auth_tables.sql 创建
-- 需求2.1, 2.2, 2.3, 2.4
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 插入固定权限5 条)
-- ---------------------------------------------------------------------------
INSERT INTO auth.permissions (code, name, description) VALUES
('view_tasks', '查看任务', '允许查看任务列表和任务详情'),
('view_board', '查看看板', '允许查看数据看板概览'),
('view_board_finance', '查看财务看板', '允许查看财务相关的数据看板'),
('view_board_customer', '查看客户看板', '允许查看客户相关的数据看板'),
('view_board_coach', '查看助教看板', '允许查看助教相关的数据看板')
ON CONFLICT (code) DO NOTHING;
-- ---------------------------------------------------------------------------
-- 2. 插入默认角色4 条)
-- ---------------------------------------------------------------------------
INSERT INTO auth.roles (code, name, description) VALUES
('coach', '助教', '球房助教,可查看任务和助教看板'),
('staff', '员工', '球房员工,可查看任务和数据看板'),
('site_admin', '店铺管理员', '单店管理员,可查看所有看板'),
('tenant_admin', '租户管理员', '租户级管理员,拥有全部权限')
ON CONFLICT (code) DO NOTHING;
-- ---------------------------------------------------------------------------
-- 3. 插入角色-权限映射
-- coach: view_tasks, view_board_coach
-- staff: view_tasks, view_board
-- site_admin: view_tasks, view_board, view_board_finance, view_board_customer, view_board_coach
-- tenant_admin: 全部 5 个权限
-- ---------------------------------------------------------------------------
INSERT INTO auth.role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM auth.roles r
CROSS JOIN auth.permissions p
WHERE (r.code, p.code) IN (
-- coach: 2 个权限
('coach', 'view_tasks'),
('coach', 'view_board_coach'),
-- staff: 2 个权限
('staff', 'view_tasks'),
('staff', 'view_board'),
-- site_admin: 5 个权限
('site_admin', 'view_tasks'),
('site_admin', 'view_board'),
('site_admin', 'view_board_finance'),
('site_admin', 'view_board_customer'),
('site_admin', 'view_board_coach'),
-- tenant_admin: 5 个权限
('tenant_admin', 'view_tasks'),
('tenant_admin', 'view_board'),
('tenant_admin', 'view_board_finance'),
('tenant_admin', 'view_board_customer'),
('tenant_admin', 'view_board_coach')
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- DELETE FROM auth.role_permissions
-- WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin'))
-- AND permission_id IN (SELECT id FROM auth.permissions WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach'));
--
-- DELETE FROM auth.roles WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin');
--
-- DELETE FROM auth.permissions WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach');

View File

@@ -1,99 +0,0 @@
-- =============================================================================
-- 迁移member_birthday_manual → member_retention_clue维客线索重构
-- 日期2026-02-26
-- 目标库zqyy_app / test_zqyy_app
-- 说明:
-- 1. 删除 member_birthday_manual 表(生日不再单独记录,归入维客线索"客户基础信息"大类)
-- 2. 新建 member_retention_clue 表(维客线索:大类 + 摘要 + 详情)
-- 影响:
-- - 后端 API/api/member-birthday 废弃,替换为 /api/retention-clue
-- - ETL DWS移除 FDW 读取 member_birthday_manual 的逻辑
-- - FDWfdw_app.member_birthday_manual 外部表需在 ETL 库侧同步删除
-- 幂等DROP IF EXISTS / CREATE IF NOT EXISTS可重复执行
-- =============================================================================
BEGIN;
-- ============================================================
-- 1. 删除旧表 member_birthday_manual
-- ============================================================
DROP TABLE IF EXISTS member_birthday_manual CASCADE;
-- ============================================================
-- 2. 创建 member_retention_clue 表
-- ============================================================
CREATE TABLE IF NOT EXISTS member_retention_clue (
id BIGSERIAL PRIMARY KEY,
member_id BIGINT NOT NULL,
category VARCHAR(20) NOT NULL
CONSTRAINT chk_retention_clue_category CHECK (
category IN ('客户基础信息', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
),
summary VARCHAR(200) NOT NULL,
detail TEXT,
recorded_by_assistant_id BIGINT,
recorded_by_name VARCHAR(50),
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
site_id BIGINT NOT NULL
);
COMMENT ON TABLE member_retention_clue
IS '维客线索:助教为会员记录的销售/维护线索(大类 + 摘要 + 详情)';
COMMENT ON COLUMN member_retention_clue.category
IS '线索大类枚举:客户基础信息、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈';
COMMENT ON COLUMN member_retention_clue.summary
IS '摘要:重点信息';
COMMENT ON COLUMN member_retention_clue.detail
IS '详情:分析及扩展说明,可为空';
-- ============================================================
-- 3. 创建索引
-- ============================================================
CREATE INDEX IF NOT EXISTS idx_retention_clue_member
ON member_retention_clue (member_id);
CREATE INDEX IF NOT EXISTS idx_retention_clue_site
ON member_retention_clue (site_id);
CREATE INDEX IF NOT EXISTS idx_retention_clue_category
ON member_retention_clue (member_id, category);
COMMIT;
-- ============================================================
-- 回滚(需手动执行,不在事务内)
-- ============================================================
-- BEGIN;
-- DROP TABLE IF EXISTS member_retention_clue CASCADE;
-- -- 如需恢复旧表,执行 2026-02-22__C2_member_birthday_manual.sql
-- COMMIT;
-- ============================================================
-- 验证 SQL
-- ============================================================
-- 1. 确认旧表已删除
-- SELECT table_name FROM information_schema.tables
-- WHERE table_schema = 'public' AND table_name = 'member_birthday_manual';
-- 预期0 行
-- 2. 确认新表存在
-- SELECT table_name FROM information_schema.tables
-- WHERE table_schema = 'public' AND table_name = 'member_retention_clue';
-- 预期1 行
-- 3. 确认列结构完整9 列)
-- SELECT column_name, data_type, is_nullable
-- FROM information_schema.columns
-- WHERE table_schema = 'public' AND table_name = 'member_retention_clue'
-- ORDER BY ordinal_position;
-- 预期id, member_id, category, summary, detail,
-- recorded_by_assistant_id, recorded_by_name, recorded_at, site_id
-- 4. 确认 CHECK 约束存在
-- SELECT conname, consrc FROM pg_constraint
-- WHERE conrelid = 'member_retention_clue'::regclass AND contype = 'c';
-- 预期1 行chk_retention_clue_category
-- 5. 确认索引存在
-- SELECT indexname FROM pg_indexes
-- WHERE tablename = 'member_retention_clue';
-- 预期3 行idx_retention_clue_member, idx_retention_clue_site, idx_retention_clue_category
-- + 1 行主键索引

View File

@@ -1,21 +0,0 @@
-- 迁移:为 auth.users.status 的 CHECK 约束添加 'new' 值
-- 原因:新用户注册后初始状态为 'new'(尚未提交申请),提交申请后变为 'pending'
-- 影响:后端 wx_login 新用户创建、dev_login mock 登录
-- 回滚:见文件末尾
-- 1. 删除旧约束
ALTER TABLE auth.users DROP CONSTRAINT IF EXISTS users_status_check;
-- 2. 添加包含 'new' 的新约束
ALTER TABLE auth.users ADD CONSTRAINT users_status_check
CHECK (status IN ('new', 'pending', 'approved', 'rejected', 'disabled'));
-- 3. 修改默认值:新用户初始状态为 'new'(尚未提交申请)
ALTER TABLE auth.users ALTER COLUMN status SET DEFAULT 'new';
-- ── 回滚 SQL ──
-- ALTER TABLE auth.users ALTER COLUMN status SET DEFAULT 'pending';
-- ALTER TABLE auth.users DROP CONSTRAINT IF EXISTS users_status_check;
-- ALTER TABLE auth.users ADD CONSTRAINT users_status_check
-- CHECK (status IN ('pending', 'approved', 'rejected', 'disabled'));
-- UPDATE auth.users SET status = 'pending' WHERE status = 'new';

View File

@@ -1,52 +0,0 @@
-- =============================================================================
-- 迁移member_retention_clue 新增 source 字段
-- 日期2026-02-27
-- 目标库zqyy_app / test_zqyy_app
-- 说明:
-- 区分维客线索来源manual助教手动/ ai_consumption应用3/ ai_note应用6
-- 影响:
-- - 后端 APIPOST /api/retention-clue 已引用 source 列,本次补齐 DDL
-- - 已有数据自动填充 DEFAULT 'manual'
-- 幂等IF NOT EXISTS 检查列是否存在,可重复执行
-- =============================================================================
BEGIN;
-- 仅在列不存在时添加PostgreSQL 11+ 支持 IF NOT EXISTS
ALTER TABLE member_retention_clue
ADD COLUMN IF NOT EXISTS source VARCHAR(20) NOT NULL DEFAULT 'manual';
COMMENT ON COLUMN member_retention_clue.source
IS '线索来源manual助教手动/ ai_consumption应用3 消费分析)/ ai_note应用6 备注分析)';
COMMIT;
-- ============================================================
-- 回滚(需手动执行)
-- ============================================================
-- BEGIN;
-- ALTER TABLE member_retention_clue DROP COLUMN IF EXISTS source;
-- COMMIT;
-- ============================================================
-- 验证 SQL
-- ============================================================
-- 1. 确认 source 列存在
-- SELECT column_name, data_type, column_default, is_nullable
-- FROM information_schema.columns
-- WHERE table_schema = 'public'
-- AND table_name = 'member_retention_clue'
-- AND column_name = 'source';
-- 预期1 行varchar, 'manual'::character varying, NO
-- 2. 确认已有数据的 source 值
-- SELECT source, COUNT(*) FROM member_retention_clue GROUP BY source;
-- 预期:全部为 'manual'(或空表)
-- 3. 确认列注释
-- SELECT col_description(
-- (SELECT oid FROM pg_class WHERE relname = 'member_retention_clue'),
-- (SELECT ordinal_position FROM information_schema.columns
-- WHERE table_name = 'member_retention_clue' AND column_name = 'source')
-- );
-- 预期:包含 'manual' / 'ai_consumption' / 'ai_note'

View File

@@ -1,107 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建 biz Schema 业务表coach_tasks / coach_task_history / notes / trigger_jobs
-- 日期2026-02-27
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在 biz Schema 下创建助教任务系统和备注系统所需的 4 张业务表,
-- 包含字段定义、约束、CHECK 约束、外键、部分唯一索引和查询索引。
-- 前提biz Schema 已由 2026-02-24__p1_create_auth_biz_schemas.sql 创建
-- 需求1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9
-- =============================================================================
-- 确保 biz Schema 存在(幂等)
CREATE SCHEMA IF NOT EXISTS biz;
-- ---------------------------------------------------------------------------
-- 1. biz.coach_tasks — 助教任务表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.coach_tasks (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
assistant_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
task_type VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'active',
priority_score NUMERIC(5,2),
expires_at TIMESTAMPTZ,
is_pinned BOOLEAN DEFAULT FALSE,
abandon_reason TEXT,
completed_at TIMESTAMPTZ,
completed_task_type VARCHAR(50),
parent_task_id BIGINT REFERENCES biz.coach_tasks(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 部分唯一索引:同一 (site_id, assistant_id, member_id, task_type) 下 active 任务最多一条
CREATE UNIQUE INDEX IF NOT EXISTS idx_coach_tasks_site_assistant_member_type
ON biz.coach_tasks (site_id, assistant_id, member_id, task_type)
WHERE status = 'active';
-- 助教任务列表查询索引
CREATE INDEX IF NOT EXISTS idx_coach_tasks_assistant_status
ON biz.coach_tasks (site_id, assistant_id, status);
-- ---------------------------------------------------------------------------
-- 2. biz.coach_task_history — 任务变更历史表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.coach_task_history (
id BIGSERIAL PRIMARY KEY,
task_id BIGINT NOT NULL REFERENCES biz.coach_tasks(id),
action VARCHAR(50) NOT NULL,
old_status VARCHAR(20),
new_status VARCHAR(20),
old_task_type VARCHAR(50),
new_task_type VARCHAR(50),
detail JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ---------------------------------------------------------------------------
-- 3. biz.notes — 统一备注表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.notes (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
user_id INTEGER NOT NULL,
target_type VARCHAR(50) NOT NULL,
target_id BIGINT NOT NULL,
type VARCHAR(20) NOT NULL DEFAULT 'normal',
content TEXT NOT NULL,
rating_service_willingness SMALLINT CHECK (rating_service_willingness BETWEEN 1 AND 5),
rating_revisit_likelihood SMALLINT CHECK (rating_revisit_likelihood BETWEEN 1 AND 5),
task_id BIGINT REFERENCES biz.coach_tasks(id),
ai_score SMALLINT,
ai_analysis TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 按目标查询备注索引
CREATE INDEX IF NOT EXISTS idx_notes_target
ON biz.notes (site_id, target_type, target_id);
-- ---------------------------------------------------------------------------
-- 4. biz.trigger_jobs — 触发器配置表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.trigger_jobs (
id SERIAL PRIMARY KEY,
job_type VARCHAR(100) NOT NULL,
job_name VARCHAR(100) NOT NULL UNIQUE,
trigger_condition VARCHAR(20) NOT NULL,
trigger_config JSONB NOT NULL,
last_run_at TIMESTAMPTZ,
next_run_at TIMESTAMPTZ,
status VARCHAR(20) NOT NULL DEFAULT 'enabled',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- DROP INDEX IF EXISTS biz.idx_notes_target;
-- DROP INDEX IF EXISTS biz.idx_coach_tasks_assistant_status;
-- DROP INDEX IF EXISTS biz.idx_coach_tasks_site_assistant_member_type;
-- DROP TABLE IF EXISTS biz.trigger_jobs;
-- DROP TABLE IF EXISTS biz.notes;
-- DROP TABLE IF EXISTS biz.coach_task_history;
-- DROP TABLE IF EXISTS biz.coach_tasks;

View File

@@ -1,43 +0,0 @@
-- =============================================================================
-- 迁移脚本:预置触发器配置种子数据
-- 日期2026-02-27
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在 biz.trigger_jobs 表中插入 4 条核心触发器配置,
-- 驱动任务生成、有效期检查、召回完成检测、备注回溯重分类。
-- 前提biz.trigger_jobs 表已由 2026-02-27__p4_create_biz_tables.sql 创建
-- 需求2.1, 2.2, 2.3, 2.4, 2.5
-- =============================================================================
INSERT INTO biz.trigger_jobs (job_type, job_name, trigger_condition, trigger_config, next_run_at)
VALUES
-- 1. task_generator每日凌晨 4:00 运行,基于 WBI/NCI/RS 指数为助教生成任务
('task_generator', 'task_generator', 'cron',
'{"cron_expression": "0 4 * * *"}',
(CURRENT_DATE + 1) + INTERVAL '4 hours'),
-- 2. task_expiry_check每小时运行检查 expires_at 到期的任务并标记 inactive
('task_expiry_check', 'task_expiry_check', 'interval',
'{"interval_seconds": 3600}',
NOW() + INTERVAL '1 hour'),
-- 3. recall_completion_checkETL 数据更新后触发,检测助教服务记录匹配活跃任务
('recall_completion_check', 'recall_completion_check', 'event',
'{"event_name": "etl_data_updated"}',
NULL),
-- 4. note_reclassify_backfill召回完成后触发回溯重分类普通备注为回访备注
('note_reclassify_backfill', 'note_reclassify_backfill', 'event',
'{"event_name": "recall_completed"}',
NULL)
ON CONFLICT (job_name) DO NOTHING;
-- =============================================================================
-- 回滚脚本
-- =============================================================================
-- DELETE FROM biz.trigger_jobs
-- WHERE job_name IN (
-- 'task_generator',
-- 'task_expiry_check',
-- 'recall_completion_check',
-- 'note_reclassify_backfill'
-- );

View File

@@ -1,50 +0,0 @@
-- =============================================================================
-- 迁移auth.site_code_mapping.tenant_id INTEGER → BIGINT
-- 原因:飞球 tenant_id 值域(如 2790683160709957远超 int4 上限(~21 亿)
-- 日期2026-03-03
-- 目标库zqyy_app / test_zqyy_app
-- 注意:该列无外键、无索引依赖,可直接 ALTER
-- =============================================================================
BEGIN;
-- 1. ALTER 列类型
ALTER TABLE auth.site_code_mapping
ALTER COLUMN tenant_id TYPE bigint;
-- 2. 刷新 FDW 外部表ETL 库 tenant_id 变更后需重新导入)
-- 注意:此步骤需在 ETL 库迁移完成后执行
-- 如果 FDW 外部表类型不匹配,需要重新 IMPORT FOREIGN SCHEMA
-- DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_assistant_order_contribution;
-- IMPORT FOREIGN SCHEMA app
-- LIMIT TO (v_dws_assistant_order_contribution)
-- FROM SERVER test_etl_feiqiu_server
-- INTO fdw_etl;
COMMIT;
-- =============================================================================
-- 回滚脚本
-- =============================================================================
-- BEGIN;
-- ALTER TABLE auth.site_code_mapping ALTER COLUMN tenant_id TYPE integer;
-- COMMIT;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认列类型已变更
-- SELECT column_name, data_type, udt_name
-- FROM information_schema.columns
-- WHERE table_schema = 'auth'
-- AND table_name = 'site_code_mapping'
-- AND column_name = 'tenant_id';
-- 预期data_type = 'bigint', udt_name = 'int8'
-- 2. 确认现有数据完整
-- SELECT count(*) FROM auth.site_code_mapping;
-- 预期:行数与迁移前一致
-- 3. 确认可插入大值 tenant_id
-- SELECT 2790683160709957::bigint;
-- 预期:返回 2790683160709957不报溢出错误

View File

@@ -1,11 +0,0 @@
-- 2026-03-07 | 多实例任务隔离task_queue 增加 enqueued_by 列
-- 背景:发现有另一台机器(宿主机 D 盘部署)的后端也在消费同一个 task_queue
-- 导致任务被错误实例执行(路径不匹配 → UTF-8 解码失败)。
-- 通过 enqueued_by 列记录入队实例的 hostnameprocess_loop 只消费自己入队的任务。
ALTER TABLE task_queue ADD COLUMN IF NOT EXISTS enqueued_by VARCHAR(255) DEFAULT NULL;
COMMENT ON COLUMN task_queue.enqueued_by IS '入队实例的 hostnameplatform.node()),用于多实例任务隔离';
-- 回滚:
-- ALTER TABLE task_queue DROP COLUMN IF EXISTS enqueued_by;

View File

@@ -1,82 +0,0 @@
-- =============================================================================
-- 迁移member_retention_clue.category CHECK 约束枚举对齐
-- 日期2026-03-08
-- 目标库zqyy_app / test_zqyy_app
-- 说明:
-- 将 category CHECK 约束中 '客户基础信息' 改为 '客户基础'
-- 与 AI 应用 Prompt 枚举值统一P5 spec 2026-03-08 评审决定)。
-- '促销偏好' 保持不变(原 Prompt 中的 '促销接受' 已在 spec 侧对齐为 '促销偏好')。
-- 统一后 6 个枚举值:客户基础、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈
-- 影响:
-- - 已有数据中 category='客户基础信息' 的行需更新为 '客户基础'
-- - 后端 APIPOST /api/retention-clue 的 category 校验需同步
-- - AI 应用 3/6/8 的 Prompt 枚举已在 P5 spec 中统一
-- 幂等:先检查约束是否存在再操作,可重复执行
-- =============================================================================
BEGIN;
-- ============================================================
-- 1. 更新已有数据(先于约束变更)
-- ============================================================
UPDATE member_retention_clue
SET category = '客户基础'
WHERE category = '客户基础信息';
-- ============================================================
-- 2. 删除旧 CHECK 约束
-- ============================================================
ALTER TABLE member_retention_clue
DROP CONSTRAINT IF EXISTS chk_retention_clue_category;
-- ============================================================
-- 3. 创建新 CHECK 约束('客户基础信息' → '客户基础'
-- ============================================================
ALTER TABLE member_retention_clue
ADD CONSTRAINT chk_retention_clue_category CHECK (
category IN ('客户基础', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
);
-- ============================================================
-- 4. 更新列注释
-- ============================================================
COMMENT ON COLUMN member_retention_clue.category
IS '线索大类枚举:客户基础、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈';
COMMIT;
-- ============================================================
-- 回滚(需手动执行)
-- ============================================================
-- BEGIN;
-- UPDATE member_retention_clue SET category = '客户基础信息' WHERE category = '客户基础';
-- ALTER TABLE member_retention_clue DROP CONSTRAINT IF EXISTS chk_retention_clue_category;
-- ALTER TABLE member_retention_clue ADD CONSTRAINT chk_retention_clue_category CHECK (
-- category IN ('客户基础信息', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
-- );
-- COMMENT ON COLUMN member_retention_clue.category
-- IS '线索大类枚举:客户基础信息、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈';
-- COMMIT;
-- ============================================================
-- 验证 SQL
-- ============================================================
-- 1. 确认无 '客户基础信息' 残留
-- SELECT COUNT(*) FROM member_retention_clue WHERE category = '客户基础信息';
-- 预期0
-- 2. 确认 CHECK 约束内容
-- SELECT conname, pg_get_constraintdef(oid) FROM pg_constraint
-- WHERE conrelid = 'member_retention_clue'::regclass AND contype = 'c';
-- 预期chk_retention_clue_category包含 '客户基础'(非 '客户基础信息'
-- 3. 测试新枚举值可写入
-- INSERT INTO member_retention_clue (member_id, category, summary, site_id)
-- VALUES (0, '客户基础', '测试', 0);
-- 预期:成功
-- DELETE FROM member_retention_clue WHERE member_id = 0 AND summary = '测试';
-- 4. 测试旧枚举值被拒绝
-- INSERT INTO member_retention_clue (member_id, category, summary, site_id)
-- VALUES (0, '客户基础信息', '测试', 0);
-- 预期CHECK 约束违反错误

View File

@@ -1,65 +0,0 @@
-- ============================================================
-- P5 AI 集成层:创建 AI 对话、消息、缓存三张表
-- 需求: 1.1, 1.2, 1.3, 1.4, 1.5
-- ============================================================
-- 1. biz.ai_conversations —— AI 对话记录
CREATE TABLE IF NOT EXISTS biz.ai_conversations (
id BIGSERIAL PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
nickname VARCHAR(100) NOT NULL DEFAULT '',
app_id VARCHAR(30) NOT NULL,
site_id BIGINT NOT NULL,
source_page VARCHAR(100),
source_context JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE biz.ai_conversations IS 'AI 对话记录:每次 AI 调用(用户主动或系统自动)创建一条';
COMMENT ON COLUMN biz.ai_conversations.app_id IS '应用标识app1_chat / app2_finance / app3_clue / app4_analysis / app5_tactics / app6_note / app7_customer / app8_consolidation';
COMMENT ON COLUMN biz.ai_conversations.user_id IS '用户 ID系统自动调用时为 system';
CREATE INDEX IF NOT EXISTS idx_ai_conv_user_site ON biz.ai_conversations (user_id, site_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ai_conv_app_site ON biz.ai_conversations (app_id, site_id, created_at DESC);
-- 2. biz.ai_messages —— AI 消息记录
CREATE TABLE IF NOT EXISTS biz.ai_messages (
id BIGSERIAL PRIMARY KEY,
conversation_id BIGINT NOT NULL REFERENCES biz.ai_conversations(id) ON DELETE CASCADE,
role VARCHAR(10) NOT NULL
CONSTRAINT chk_ai_msg_role CHECK (role IN ('user', 'assistant', 'system')),
content TEXT NOT NULL,
tokens_used INTEGER,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE biz.ai_messages IS 'AI 消息记录:对话中的每条消息(输入/输出/系统)';
CREATE INDEX IF NOT EXISTS idx_ai_msg_conv ON biz.ai_messages (conversation_id, created_at);
-- 3. biz.ai_cache —— AI 应用缓存
CREATE TABLE IF NOT EXISTS biz.ai_cache (
id BIGSERIAL PRIMARY KEY,
cache_type VARCHAR(30) NOT NULL
CONSTRAINT chk_ai_cache_type CHECK (
cache_type IN (
'app2_finance', 'app3_clue', 'app4_analysis',
'app5_tactics', 'app6_note_analysis',
'app7_customer_analysis', 'app8_clue_consolidated'
)
),
site_id BIGINT NOT NULL,
target_id VARCHAR(100) NOT NULL,
result_json JSONB NOT NULL,
score INTEGER,
triggered_by VARCHAR(100),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ
);
COMMENT ON TABLE biz.ai_cache IS 'AI 应用缓存:各应用的结构化输出结果';
COMMENT ON COLUMN biz.ai_cache.target_id IS '目标 IDApp2=时间维度编码 / App3,6,7,8=member_id / App4,5={assistant_id}_{member_id}';
COMMENT ON COLUMN biz.ai_cache.score IS '评分:仅应用 6 使用1-10 分)';
CREATE INDEX IF NOT EXISTS idx_ai_cache_lookup ON biz.ai_cache (cache_type, site_id, target_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ai_cache_cleanup ON biz.ai_cache (cache_type, site_id, target_id, created_at);

View File

@@ -1,13 +0,0 @@
-- 给 task_execution_log 添加 schedule_id 字段,关联调度任务
-- 用于查询某个调度任务的执行历史
ALTER TABLE task_execution_log
ADD COLUMN IF NOT EXISTS schedule_id UUID REFERENCES scheduled_tasks(id) ON DELETE SET NULL;
-- 按 schedule_id 查询执行历史的部分索引
CREATE INDEX IF NOT EXISTS idx_execution_log_schedule_id
ON task_execution_log(schedule_id)
WHERE schedule_id IS NOT NULL;
-- 给 task_queue 添加 schedule_id 字段,追溯调度来源
ALTER TABLE task_queue
ADD COLUMN IF NOT EXISTS schedule_id UUID REFERENCES scheduled_tasks(id) ON DELETE SET NULL;

View File

@@ -1,29 +0,0 @@
-- 触发器INSERT task_execution_log 时自动从 task_queue 回填 schedule_id
-- 背景:队列处理循环中 schedule_id 可能未通过代码传递,用触发器兜底
CREATE OR REPLACE FUNCTION fn_backfill_schedule_id()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.schedule_id IS NULL AND NEW.queue_id IS NOT NULL THEN
SELECT schedule_id INTO NEW.schedule_id
FROM task_queue
WHERE id = NEW.queue_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_backfill_schedule_id ON task_execution_log;
CREATE TRIGGER trg_backfill_schedule_id
BEFORE INSERT ON task_execution_log
FOR EACH ROW
EXECUTE FUNCTION fn_backfill_schedule_id();
-- 回填历史数据
UPDATE task_execution_log el
SET schedule_id = tq.schedule_id
FROM task_queue tq
WHERE el.queue_id = tq.id
AND tq.schedule_id IS NOT NULL
AND el.schedule_id IS NULL;

View File

@@ -1,18 +0,0 @@
-- =============================================================================
-- 种子数据Web 管理后台默认管理员账号
-- 数据库zqyy_app
-- 关联需求Requirements 1.1(用户认证)
-- =============================================================================
-- 默认密码admin123bcrypt hashcost=12
-- 生产环境部署后务必立即修改密码
-- =============================================================================
INSERT INTO admin_users (username, password_hash, display_name, site_id, is_active)
VALUES (
'admin',
'$2b$12$2MTWlJKL0HTgHIkv5Rmpie2pQ9PkeJu0iciLbzPEpPcA94ZakIQzq',
'默认管理员',
1,
TRUE
)
ON CONFLICT (username) DO NOTHING;