微信小程序页面迁移校验之前 P5任务处理之前

This commit is contained in:
Neo
2026-03-09 01:19:21 +08:00
parent 263bf96035
commit 6e20987d2f
1112 changed files with 153824 additions and 219694 deletions

View File

@@ -0,0 +1,34 @@
-- 新增 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

@@ -0,0 +1,22 @@
-- 迁移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

@@ -0,0 +1,14 @@
-- 修复 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

@@ -0,0 +1,138 @@
-- 重建物化视图:时间过滤条件从自然日切换为营业日
-- 原因:全系统统计口径从自然日切换为营业日(默认 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

@@ -0,0 +1,63 @@
-- =============================================================================
-- 迁移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

@@ -0,0 +1,27 @@
-- =============================================================================
-- 迁移脚本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

@@ -0,0 +1,102 @@
-- =============================================================================
-- 迁移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

@@ -0,0 +1,95 @@
-- =============================================================================
-- 迁移脚本:创建项目标签表
-- 日期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

@@ -0,0 +1,67 @@
-- 迁移:统一 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

@@ -0,0 +1,58 @@
-- 迁移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

@@ -0,0 +1,28 @@
-- 回滚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

@@ -0,0 +1,22 @@
-- 迁移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

@@ -0,0 +1,94 @@
-- ============================================================================
-- 表ods.group_buy_package_details
-- 来源QueryPackageCouponInfo 详情接口
-- 说明:团购套餐详情 ODS 层,存储每个 couponId 的详情原始数据
-- ============================================================================
CREATE TABLE IF NOT EXISTS ods.group_buy_package_details (
-- 主键
coupon_id BIGINT NOT NULL,
-- 结构化业务字段(来自 data.groupPurchasePackage
package_name TEXT,
duration INTEGER,
start_time TIMESTAMPTZ,
end_time TIMESTAMPTZ,
add_start_clock TEXT,
add_end_clock TEXT,
is_enabled INTEGER,
is_delete INTEGER,
site_id BIGINT,
tenant_id BIGINT,
create_time TIMESTAMPTZ,
creator_name TEXT,
-- JSONB 数组字段
table_area_ids JSONB,
table_area_names JSONB,
assistant_services JSONB,
groupon_site_infos JSONB,
package_services JSONB,
coupon_details_list JSONB,
-- ETL 元数据
content_hash TEXT,
payload JSONB,
fetched_at TIMESTAMPTZ DEFAULT now(),
-- 约束
CONSTRAINT pk_group_buy_package_details PRIMARY KEY (coupon_id)
);
-- 表注释
COMMENT ON TABLE ods.group_buy_package_details
IS '团购套餐详情 ODSQueryPackageCouponInfo 原始数据';
-- 主键 / 结构化字段注释
COMMENT ON COLUMN ods.group_buy_package_details.coupon_id
IS '团购套餐 ID= groupPurchasePackage.id主键';
COMMENT ON COLUMN ods.group_buy_package_details.package_name
IS '团购套餐名称';
COMMENT ON COLUMN ods.group_buy_package_details.duration
IS '台费计时时长(秒)';
COMMENT ON COLUMN ods.group_buy_package_details.start_time
IS '可用日期开始';
COMMENT ON COLUMN ods.group_buy_package_details.end_time
IS '可用日期结束';
COMMENT ON COLUMN ods.group_buy_package_details.add_start_clock
IS '可用时段开始(如 "00:00:00"';
COMMENT ON COLUMN ods.group_buy_package_details.add_end_clock
IS '可用时段结束(如 "1.00:00:00"';
COMMENT ON COLUMN ods.group_buy_package_details.is_enabled
IS '是否启用1=启用, 0=禁用)';
COMMENT ON COLUMN ods.group_buy_package_details.is_delete
IS '是否已删除1=已删除, 0=正常)';
COMMENT ON COLUMN ods.group_buy_package_details.site_id
IS '店铺 ID';
COMMENT ON COLUMN ods.group_buy_package_details.tenant_id
IS '租户 ID';
COMMENT ON COLUMN ods.group_buy_package_details.create_time
IS '创建时间';
COMMENT ON COLUMN ods.group_buy_package_details.creator_name
IS '创建人';
-- JSONB 数组字段注释
COMMENT ON COLUMN ods.group_buy_package_details.table_area_ids
IS '可用台区 ID 列表(来自 groupPurchasePackage.tableAreaId';
COMMENT ON COLUMN ods.group_buy_package_details.table_area_names
IS '可用台区名称列表(来自 groupPurchasePackage.tableAreaNameList';
COMMENT ON COLUMN ods.group_buy_package_details.assistant_services
IS '助教服务关联数组(来自 packageCouponAssistants含 skillId/assistantLevel/assistantDuration';
COMMENT ON COLUMN ods.group_buy_package_details.groupon_site_infos
IS '关联门店信息数组(来自 grouponSiteInfos含 siteId/siteName';
COMMENT ON COLUMN ods.group_buy_package_details.package_services
IS '套餐服务数组(来自 packagePackageService待调研可能为空';
COMMENT ON COLUMN ods.group_buy_package_details.coupon_details_list
IS '券明细数组(来自 packageCouponDetailsList待调研可能为空';
-- ETL 元数据字段注释
COMMENT ON COLUMN ods.group_buy_package_details.content_hash
IS '业务字段内容哈希,用于变更检测';
COMMENT ON COLUMN ods.group_buy_package_details.payload
IS '详情接口完整原始 JSON 响应';
COMMENT ON COLUMN ods.group_buy_package_details.fetched_at
IS 'ETL 拉取时间戳';

View File

@@ -206,45 +206,71 @@ INSERT INTO dws.cfg_bonus_rules (
-- =============================================================================
-- 4. cfg_area_category - 台区分类映射
-- 说明:
-- - 将 dim_table.site_table_area_name 映射到财务报表区域分类
-- - 映射规则: 精确匹配 > 模糊匹配 > 默认兜底
-- - 将 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, category_code, category_name,
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区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台A区18台- 中八/追分'),
('B区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台B15台)- 中八/追分'),
('C', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台C区6台- 中八/追分'),
('TV台', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:TV台1台- 中八/追分'),
-- 台球VIP包厢
('VIP包厢', 'BILLIARD_VIP', '台球VIP', 'EXACT', 10, TRUE, '台球VIPVIP包厢4台- V1-V4中八, V5斯诺克'),
('A区', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
'台球散台A18台)- 中八/追分'),
('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中八→ 归入中式/追分'),
-- 斯诺克区
('斯诺克区', 'SNOOKER', '斯诺克', 'EXACT', 10, TRUE, '斯诺克斯诺克区4台'),
('斯诺克区', NULL, 'SNOOKER', '斯诺克', '斯诺克', '', 'EXACT', 10, TRUE,
'斯诺克斯诺克区4台'),
-- 麻将区
('麻将房', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌麻将房5台'),
('M7', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌M72台)'),
('M8', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌M81台'),
('666', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌:6662台'),
('发财', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌发财1台'),
('麻将房', 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包', 'KTV', 'K歌娱乐', 'EXACT', 10, TRUE, 'K歌娱乐K包4台'),
('k包活动区', 'KTV', 'K歌娱乐', 'EXACT', 10, TRUE, 'K歌娱乐k包活动区2台)'),
('幸会158', 'KTV', 'K歌娱乐', 'EXACT', 10, TRUE, 'K歌娱乐幸会1582台'),
('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台'),
-- 特殊区域
('补时长', 'SPECIAL', '补时长', 'EXACT', 10, TRUE, '特殊补时长7台- 用于时长补录'),
('补时长', NULL, 'SPECIAL', '补时长', '补时长', '', 'EXACT', 10, TRUE,
'特殊补时长7台- 用于时长补录'),
-- 模糊匹配规则
('%VIP%', 'BILLIARD_VIP', '台球VIP', 'LIKE', 50, TRUE, '模糊匹配:包含"VIP"的区域'),
('%斯诺克%', 'SNOOKER', '斯诺克', 'LIKE', 50, TRUE, '模糊匹配:包含"斯诺克"的区域'),
('%麻将%', 'MAHJONG', '麻将棋牌', 'LIKE', 50, TRUE, '模糊匹配:包含"麻将"的区域'),
('%K包%', 'KTV', 'K歌娱乐', 'LIKE', 50, TRUE, '模糊匹配:包含"K包"的区域'),
('%KTV%', 'KTV', 'K歌娱乐', 'LIKE', 50, TRUE, '模糊匹配:包含"KTV"的区域'),
('%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', 'OTHER', '其他', 'DEFAULT', 999, TRUE, '兜底规则:无法匹配的区域归入其他');
('DEFAULT', NULL, 'OTHER', '其他', '其他', '', 'DEFAULT', 999, TRUE,
'兜底规则:无法匹配的区域归入其他');
-- =============================================================================