# -*- coding: utf-8 -*- """ 创建指数算法相关表 """ import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from config.settings import AppConfig from database.connection import DatabaseConnection from database.operations import DatabaseOperations # 表DDL DDL_STATEMENTS = [ # 参数配置表 """ DROP TABLE IF EXISTS billiards_dws.cfg_index_parameters CASCADE; CREATE TABLE billiards_dws.cfg_index_parameters ( param_id SERIAL PRIMARY KEY, index_type VARCHAR(50) NOT NULL, param_name VARCHAR(100) NOT NULL, param_value NUMERIC(14,6) NOT NULL, description TEXT, effective_from DATE NOT NULL DEFAULT CURRENT_DATE, effective_to DATE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_cfg_index_parameters UNIQUE (index_type, param_name, effective_from) ); CREATE INDEX idx_cfg_index_params_type ON billiards_dws.cfg_index_parameters (index_type); """, # 召回指数表 """ DROP TABLE IF EXISTS billiards_dws.dws_member_recall_index CASCADE; CREATE TABLE billiards_dws.dws_member_recall_index ( recall_id BIGSERIAL PRIMARY KEY, site_id BIGINT NOT NULL, tenant_id BIGINT NOT NULL, member_id BIGINT NOT NULL, days_since_last_visit INTEGER, visit_interval_median NUMERIC(10,2), visit_interval_mad NUMERIC(10,2), days_since_first_visit INTEGER, days_since_last_recharge INTEGER, visits_last_14_days INTEGER NOT NULL DEFAULT 0, visits_last_60_days INTEGER NOT NULL DEFAULT 0, score_overdue NUMERIC(10,4), score_new_bonus NUMERIC(10,4), score_recharge_bonus NUMERIC(10,4), score_hot_drop NUMERIC(10,4), raw_score NUMERIC(14,6), display_score NUMERIC(4,2), calc_time TIMESTAMPTZ NOT NULL DEFAULT NOW(), calc_version INTEGER NOT NULL DEFAULT 1, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_dws_member_recall UNIQUE (site_id, member_id) ); CREATE INDEX idx_dws_recall_display ON billiards_dws.dws_member_recall_index (site_id, display_score DESC); """, # 亲密指数表 """ DROP TABLE IF EXISTS billiards_dws.dws_member_assistant_intimacy CASCADE; CREATE TABLE billiards_dws.dws_member_assistant_intimacy ( intimacy_id BIGSERIAL PRIMARY KEY, site_id BIGINT NOT NULL, tenant_id BIGINT NOT NULL, member_id BIGINT NOT NULL, assistant_id BIGINT NOT NULL, session_count INTEGER NOT NULL DEFAULT 0, total_duration_minutes INTEGER NOT NULL DEFAULT 0, basic_session_count INTEGER NOT NULL DEFAULT 0, incentive_session_count INTEGER NOT NULL DEFAULT 0, days_since_last_session INTEGER, attributed_recharge_count INTEGER NOT NULL DEFAULT 0, attributed_recharge_amount NUMERIC(14,2) NOT NULL DEFAULT 0, score_frequency NUMERIC(10,4), score_recency NUMERIC(10,4), score_recharge NUMERIC(10,4), score_duration NUMERIC(10,4), burst_multiplier NUMERIC(6,4), raw_score NUMERIC(14,6), display_score NUMERIC(4,2), calc_time TIMESTAMPTZ NOT NULL DEFAULT NOW(), calc_version INTEGER NOT NULL DEFAULT 1, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_dws_member_assistant_intimacy UNIQUE (site_id, member_id, assistant_id) ); CREATE INDEX idx_dws_intimacy_member ON billiards_dws.dws_member_assistant_intimacy (site_id, member_id, display_score DESC); CREATE INDEX idx_dws_intimacy_assistant ON billiards_dws.dws_member_assistant_intimacy (site_id, assistant_id, display_score DESC); """, # 分位点历史表 """ DROP TABLE IF EXISTS billiards_dws.dws_index_percentile_history CASCADE; CREATE TABLE billiards_dws.dws_index_percentile_history ( history_id BIGSERIAL PRIMARY KEY, site_id BIGINT NOT NULL, index_type VARCHAR(50) NOT NULL, calc_time TIMESTAMPTZ NOT NULL, percentile_5 NUMERIC(14,6), percentile_95 NUMERIC(14,6), percentile_5_smoothed NUMERIC(14,6), percentile_95_smoothed NUMERIC(14,6), record_count INTEGER, min_raw_score NUMERIC(14,6), max_raw_score NUMERIC(14,6), avg_raw_score NUMERIC(14,6), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uk_dws_index_percentile_history UNIQUE (site_id, index_type, calc_time) ); CREATE INDEX idx_dws_percentile_history ON billiards_dws.dws_index_percentile_history (site_id, index_type, calc_time DESC); """ ] # 初始化参数 SEED_PARAMS = """ INSERT INTO billiards_dws.cfg_index_parameters (index_type, param_name, param_value, description, effective_from) VALUES ('RECALL', 'lookback_days', 60, '回溯窗口(天)', CURRENT_DATE), ('RECALL', 'sigma_min', 2.0, '波动下限(天)', CURRENT_DATE), ('RECALL', 'halflife_new', 7, '新客户半衰期(天)', CURRENT_DATE), ('RECALL', 'halflife_recharge', 10, '刚充值半衰期(天)', CURRENT_DATE), ('RECALL', 'weight_overdue', 3.0, '超期紧急性权重', CURRENT_DATE), ('RECALL', 'weight_new', 1.0, '新客户权重', CURRENT_DATE), ('RECALL', 'weight_recharge', 1.0, '刚充值权重', CURRENT_DATE), ('RECALL', 'weight_hot', 1.0, '热度断档权重', CURRENT_DATE), ('RECALL', 'percentile_lower', 5, '下锚分位数', CURRENT_DATE), ('RECALL', 'percentile_upper', 95, '上锚分位数', CURRENT_DATE), ('RECALL', 'ewma_alpha', 0.2, 'EWMA平滑系数', CURRENT_DATE), ('INTIMACY', 'lookback_days', 60, '回溯窗口(天)', CURRENT_DATE), ('INTIMACY', 'session_merge_hours', 4, '会话合并间隔(小时)', CURRENT_DATE), ('INTIMACY', 'recharge_attribute_hours', 1, '充值归因窗口(小时)', CURRENT_DATE), ('INTIMACY', 'amount_base', 500, '金额压缩基准(元)', CURRENT_DATE), ('INTIMACY', 'incentive_weight', 1.5, '附加课权重倍数', CURRENT_DATE), ('INTIMACY', 'halflife_session', 14, '会话衰减半衰期(天)', CURRENT_DATE), ('INTIMACY', 'halflife_last', 10, '最近一次半衰期(天)', CURRENT_DATE), ('INTIMACY', 'halflife_recharge', 21, '充值衰减半衰期(天)', CURRENT_DATE), ('INTIMACY', 'halflife_short', 7, '短期激增检测半衰期(天)', CURRENT_DATE), ('INTIMACY', 'halflife_long', 30, '长期激增检测半衰期(天)', CURRENT_DATE), ('INTIMACY', 'weight_frequency', 2.0, '频次权重', CURRENT_DATE), ('INTIMACY', 'weight_recency', 1.5, '最近一次权重', CURRENT_DATE), ('INTIMACY', 'weight_recharge', 2.0, '归因充值权重', CURRENT_DATE), ('INTIMACY', 'weight_duration', 0.5, '时长权重', CURRENT_DATE), ('INTIMACY', 'burst_gamma', 0.6, '激增放大系数', CURRENT_DATE), ('INTIMACY', 'percentile_lower', 5, '下锚分位数', CURRENT_DATE), ('INTIMACY', 'percentile_upper', 95, '上锚分位数', CURRENT_DATE), ('INTIMACY', 'ewma_alpha', 0.2, 'EWMA平滑系数', CURRENT_DATE) ON CONFLICT (index_type, param_name, effective_from) DO NOTHING; """ def main(): print("创建指数算法相关表...") config = AppConfig.load() db_conn = DatabaseConnection(config.config["db"]["dsn"]) try: with db_conn.conn.cursor() as cur: # 创建表 for i, ddl in enumerate(DDL_STATEMENTS, 1): print(f" 执行DDL {i}/{len(DDL_STATEMENTS)}...") cur.execute(ddl) # 初始化参数 print(" 初始化算法参数...") cur.execute(SEED_PARAMS) db_conn.conn.commit() print("完成!") # 验证 cur.execute("SELECT COUNT(*) FROM billiards_dws.cfg_index_parameters") count = cur.fetchone()[0] print(f" 已插入 {count} 个参数配置") finally: db_conn.close() if __name__ == '__main__': main()