""" 将 seed_dws_config.sql 中的种子数据写入 DWS 配置表(test_etl_feiqiu)。 包含: 1. 原始种子数据(cfg_performance_tier / cfg_assistant_level_price / cfg_bonus_rules / cfg_area_category / cfg_skill_type) 2. 新增 2025-01-01~2026-02-28 统一提成档位(基础课18元/小时,打赏课40%) 3. 新增 GUARANTEE 保底奖金规则(按等级区分) 执行目标库:TEST_DB_DSN 或 PG_DSN(.env 中配置) """ import os import sys from pathlib import Path from dotenv import load_dotenv import psycopg2 # 加载根 .env load_dotenv(Path(__file__).resolve().parents[2] / ".env") dsn = os.environ.get("PG_DSN") if not dsn: print("ERROR: PG_DSN 未在 .env 中配置,终止执行") sys.exit(1) # 确认连接的是测试库 if "test_etl_feiqiu" not in dsn: print(f"WARNING: DSN 指向 {dsn},不是 test_etl_feiqiu,请确认!") resp = input("继续执行?(y/N): ").strip().lower() if resp != "y": print("已取消") sys.exit(0) print(f"连接数据库: {dsn.split('@')[1] if '@' in dsn else dsn}") # ============================================================================ # SQL 语句定义 # ============================================================================ # 清空并重建配置数据 SQL_STATEMENTS = [] # --- 1. cfg_performance_tier --- SQL_STATEMENTS.append(""" TRUNCATE TABLE dws.cfg_performance_tier RESTART IDENTITY CASCADE; """) # 2025-01-01 ~ 2026-02-28: 统一提成档位(不分档,所有助教统一规则) # 基础课球房提成 18 元/小时,打赏课球房提成 40% SQL_STATEMENTS.append(""" 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 -- 2025-01-01 ~ 2026-02-28: 统一提成(不分档) ('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%,不分档位'), -- 旧方案(至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%,休假自由'), -- 新方案(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 --- SQL_STATEMENTS.append(""" TRUNCATE TABLE dws.cfg_assistant_level_price RESTART IDENTITY CASCADE; """) SQL_STATEMENTS.append(""" 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 --- SQL_STATEMENTS.append(""" TRUNCATE TABLE dws.cfg_bonus_rules RESTART IDENTITY CASCADE; """) SQL_STATEMENTS.append(""" 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) -- 按助教等级区分,需同时满足总课时和打赏课最低时数 -- level_code: 10=初级, 20=中级, 30=高级, 40=星级 ('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 --- SQL_STATEMENTS.append(""" TRUNCATE TABLE dws.cfg_area_category RESTART IDENTITY CASCADE; """) SQL_STATEMENTS.append(""" INSERT INTO dws.cfg_area_category ( source_area_name, category_code, category_name, match_type, match_priority, is_active, description ) VALUES -- 台球散台(精确匹配) ('A区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:A区(18台)- 中八/追分'), ('B区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:B区(15台)- 中八/追分'), ('C区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:C区(6台)- 中八/追分'), ('TV台', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:TV台(1台)- 中八/追分'), -- 台球VIP包厢 ('VIP包厢', 'BILLIARD_VIP', '台球VIP', 'EXACT', 10, TRUE, '台球VIP:VIP包厢(4台)- V1-V4中八, V5斯诺克'), -- 斯诺克区 ('斯诺克区', 'SNOOKER', '斯诺克', 'EXACT', 10, TRUE, '斯诺克:斯诺克区(4台)'), -- 麻将区 ('麻将房', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌:麻将房(5台)'), ('M7', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌:M7(2台)'), ('M8', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌:M8(1台)'), ('666', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌:666(2台)'), ('发财', '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歌娱乐:幸会158(2台)'), -- 特殊区域 ('补时长', 'SPECIAL', '补时长', 'EXACT', 10, TRUE, '特殊:补时长(7台)- 用于时长补录'), -- 模糊匹配 ('%VIP%', 'BILLIARD_VIP', '台球VIP', 'LIKE', 50, TRUE, '模糊匹配:包含"VIP"的区域'), ('%斯诺克%', 'SNOOKER', '斯诺克', 'LIKE', 50, TRUE, '模糊匹配:包含"斯诺克"的区域'), ('%麻将%', 'MAHJONG', '麻将棋牌', 'LIKE', 50, TRUE, '模糊匹配:包含"麻将"的区域'), ('%K包%', 'KTV', 'K歌娱乐', 'LIKE', 50, TRUE, '模糊匹配:包含"K包"的区域'), ('%KTV%', 'KTV', 'K歌娱乐', 'LIKE', 50, TRUE, '模糊匹配:包含"KTV"的区域'), -- 默认兜底 ('DEFAULT', 'OTHER', '其他', 'DEFAULT', 999, TRUE, '兜底规则:无法匹配的区域归入其他'); """) # --- 5. cfg_skill_type --- SQL_STATEMENTS.append(""" TRUNCATE TABLE dws.cfg_skill_type RESTART IDENTITY CASCADE; """) SQL_STATEMENTS.append(""" 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元/小时计价'); """) # --- 验证 SQL --- SQL_VERIFY = """ 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; $$; """ # ============================================================================ # 执行 # ============================================================================ def main(): conn = psycopg2.connect(dsn) conn.autocommit = False cur = conn.cursor() try: for i, sql in enumerate(SQL_STATEMENTS): cur.execute(sql) print(f" 步骤 {i+1}/{len(SQL_STATEMENTS)} 完成") # 验证 cur.execute(SQL_VERIFY) # 额外查询验证 checks = [ ("cfg_performance_tier", "SELECT tier_code, tier_name, effective_from, effective_to, base_deduction, bonus_deduction_ratio FROM dws.cfg_performance_tier ORDER BY effective_from, tier_level"), ("cfg_bonus_rules", "SELECT rule_type, rule_code, rule_name, threshold_hours, bonus_amount, effective_from, effective_to FROM dws.cfg_bonus_rules ORDER BY effective_from, rule_type, priority"), ("cfg_assistant_level_price", "SELECT level_code, level_name, base_course_price, bonus_course_price FROM dws.cfg_assistant_level_price ORDER BY level_code"), ("cfg_area_category", "SELECT COUNT(*) as cnt FROM dws.cfg_area_category"), ("cfg_skill_type", "SELECT skill_id, skill_name, course_type_code FROM dws.cfg_skill_type"), ] for table_name, sql in checks: cur.execute(sql) rows = cur.fetchall() cols = [desc[0] for desc in cur.description] print(f"\n=== {table_name} ===") print(f" 列: {cols}") for row in rows: print(f" {row}") conn.commit() print("\n✅ 所有配置数据已成功写入 dws schema") except Exception as e: conn.rollback() print(f"\n❌ 执行失败,已回滚: {e}") raise finally: cur.close() conn.close() if __name__ == "__main__": main()