# -*- coding: utf-8 -*- """ 测试指数算法任务 """ import sys import os # 添加项目路径 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import logging from config.settings import AppConfig from database.connection import DatabaseConnection from database.operations import DatabaseOperations from tasks.dws.index import RecallIndexTask, IntimacyIndexTask # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger('test_index') def test_recall_index(): """测试召回指数任务""" logger.info("=" * 60) logger.info("测试客户召回指数任务 (DWS_RECALL_INDEX)") logger.info("=" * 60) # 加载配置 config = AppConfig.load() # 连接数据库 db_conn = DatabaseConnection(config.config["db"]["dsn"]) db = DatabaseOperations(db_conn) try: # 创建任务实例 task = RecallIndexTask(config, db, None, logger) # 执行任务 result = task.execute(None) logger.info("任务执行结果: %s", result) # 查询结果 if result.get('status') == 'success': sql = """ SELECT COUNT(*) as total_count, ROUND(AVG(display_score)::numeric, 2) as avg_score, ROUND(MIN(display_score)::numeric, 2) as min_score, ROUND(MAX(display_score)::numeric, 2) as max_score, ROUND(AVG(raw_score)::numeric, 4) as avg_raw_score, ROUND(AVG(score_overdue)::numeric, 4) as avg_overdue, ROUND(AVG(score_new_bonus)::numeric, 4) as avg_new_bonus, ROUND(AVG(score_recharge_bonus)::numeric, 4) as avg_recharge_bonus, ROUND(AVG(score_hot_drop)::numeric, 4) as avg_hot_drop FROM billiards_dws.dws_member_recall_index """ rows = db.query(sql) if rows: stats = dict(rows[0]) logger.info("-" * 40) logger.info("召回指数统计:") logger.info(" 总记录数: %s", stats['total_count']) logger.info(" Display Score: 平均=%.2f, 最小=%.2f, 最大=%.2f", stats['avg_score'] or 0, stats['min_score'] or 0, stats['max_score'] or 0) logger.info(" Raw Score 平均: %.4f", stats['avg_raw_score'] or 0) logger.info(" 分项得分平均:") logger.info(" - 超期紧急性: %.4f", stats['avg_overdue'] or 0) logger.info(" - 新客户加分: %.4f", stats['avg_new_bonus'] or 0) logger.info(" - 充值加分: %.4f", stats['avg_recharge_bonus'] or 0) logger.info(" - 热度断档: %.4f", stats['avg_hot_drop'] or 0) # 查询Top 5 logger.info("-" * 40) logger.info("召回优先级 Top 5:") top_sql = """ SELECT member_id, display_score, raw_score, days_since_last_visit, visit_interval_median FROM billiards_dws.dws_member_recall_index ORDER BY display_score DESC LIMIT 5 """ top_rows = db.query(top_sql) for i, row in enumerate(top_rows or [], 1): r = dict(row) logger.info(" %d. 会员%s: %.2f分 (Raw=%.4f, 最近到店=%s天前, 周期=%.1f天)", i, r['member_id'], r['display_score'] or 0, r['raw_score'] or 0, r['days_since_last_visit'], r['visit_interval_median'] or 0) return result finally: db_conn.close() def test_intimacy_index(): """测试亲密指数任务""" logger.info("") logger.info("=" * 60) logger.info("测试客户-助教亲密指数任务 (DWS_INTIMACY_INDEX)") logger.info("=" * 60) # 加载配置 config = AppConfig.load() # 连接数据库 db_conn = DatabaseConnection(config.config["db"]["dsn"]) db = DatabaseOperations(db_conn) try: # 创建任务实例 task = IntimacyIndexTask(config, db, None, logger) # 执行任务 result = task.execute(None) logger.info("任务执行结果: %s", result) # 查询结果 if result.get('status') == 'success': sql = """ SELECT COUNT(*) as total_count, COUNT(DISTINCT member_id) as unique_members, COUNT(DISTINCT assistant_id) as unique_assistants, ROUND(AVG(display_score)::numeric, 2) as avg_score, ROUND(MIN(display_score)::numeric, 2) as min_score, ROUND(MAX(display_score)::numeric, 2) as max_score, ROUND(AVG(raw_score)::numeric, 4) as avg_raw_score, ROUND(AVG(score_frequency)::numeric, 4) as avg_frequency, ROUND(AVG(score_recency)::numeric, 4) as avg_recency, ROUND(AVG(score_recharge)::numeric, 4) as avg_recharge, ROUND(AVG(burst_multiplier)::numeric, 4) as avg_burst FROM billiards_dws.dws_member_assistant_intimacy """ rows = db.query(sql) if rows: stats = dict(rows[0]) logger.info("-" * 40) logger.info("亲密指数统计:") logger.info(" 总记录数: %s (客户-助教对)", stats['total_count']) logger.info(" 唯一会员: %s, 唯一助教: %s", stats['unique_members'], stats['unique_assistants']) logger.info(" Display Score: 平均=%.2f, 最小=%.2f, 最大=%.2f", stats['avg_score'] or 0, stats['min_score'] or 0, stats['max_score'] or 0) logger.info(" Raw Score 平均: %.4f", stats['avg_raw_score'] or 0) logger.info(" 分项得分平均:") logger.info(" - 频次强度: %.4f", stats['avg_frequency'] or 0) logger.info(" - 最近温度: %.4f", stats['avg_recency'] or 0) logger.info(" - 充值强度: %.4f", stats['avg_recharge'] or 0) logger.info(" - 激增放大: %.4f", stats['avg_burst'] or 0) # 查询Top亲密关系 logger.info("-" * 40) logger.info("亲密度 Top 5 客户-助教对:") top_sql = """ SELECT member_id, assistant_id, display_score, raw_score, session_count, attributed_recharge_amount FROM billiards_dws.dws_member_assistant_intimacy ORDER BY display_score DESC LIMIT 5 """ top_rows = db.query(top_sql) for i, row in enumerate(top_rows or [], 1): r = dict(row) logger.info(" %d. 会员%s-助教%s: %.2f分 (会话%d次, 归因充值%.2f元)", i, r['member_id'], r['assistant_id'], r['display_score'] or 0, r['session_count'] or 0, r['attributed_recharge_amount'] or 0) return result finally: db_conn.close() if __name__ == '__main__': print("=" * 60) print("指数算法任务测试") print("=" * 60) print() # 先检查表是否存在 config = AppConfig.load() db_conn = DatabaseConnection(config.config["db"]["dsn"]) db = DatabaseOperations(db_conn) check_sql = """ SELECT table_name FROM information_schema.tables WHERE table_schema = 'billiards_dws' AND table_name IN ('dws_member_recall_index', 'dws_member_assistant_intimacy', 'cfg_index_parameters') """ tables = db.query(check_sql) existing_tables = [dict(r)['table_name'] for r in (tables or [])] if 'cfg_index_parameters' not in existing_tables: print("警告: cfg_index_parameters 表不存在,请先执行 schema_dws.sql") print("需要执行的表:") print(" - cfg_index_parameters") print(" - dws_member_recall_index") print(" - dws_member_assistant_intimacy") print(" - dws_index_percentile_history") db_conn.close() sys.exit(1) db_conn.close() # 测试召回指数 recall_result = test_recall_index() # 测试亲密指数 intimacy_result = test_intimacy_index() print() print("=" * 60) print("测试完成") print("=" * 60) print(f"召回指数: {recall_result.get('status', 'unknown')}") print(f"亲密指数: {intimacy_result.get('status', 'unknown')}")