run_decision_test.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. #!/usr/bin/env python3
  2. """
  3. 运行决策引擎并验证新策略是否生效
  4. """
  5. import asyncio
  6. import sys
  7. import json
  8. from pathlib import Path
  9. # 添加项目根目录到路径
  10. PROJECT_ROOT = Path(__file__).parent.parent.parent
  11. sys.path.insert(0, str(PROJECT_ROOT))
  12. from examples.auto_put_ad_mini.tools.ad_decision import get_ads_for_review, apply_decisions
  13. # 模拟ToolContext
  14. class SimpleContext:
  15. """简单的上下文模拟"""
  16. def __init__(self):
  17. self.config = {}
  18. async def run_decision_test(end_date='20260415'):
  19. """运行决策测试"""
  20. ctx = SimpleContext()
  21. print("=" * 70)
  22. print(" 决策引擎测试 - 验证新策略")
  23. print("=" * 70)
  24. # Step 1: 获取需要评估的广告
  25. print(f"\n📊 步骤 1/2: 获取需要评估的广告 (end_date={end_date})")
  26. try:
  27. result = await get_ads_for_review(
  28. ctx,
  29. metrics_csv="",
  30. end_date=end_date,
  31. roi_review_factor=0.8,
  32. min_spend_for_class_a=10.0
  33. )
  34. print(f"✅ {result.title}")
  35. # 解析结果
  36. data = json.loads(result.output)
  37. summary = data.get('summary', {})
  38. print(f"\n📊 广告分类统计:")
  39. print(f" 总广告数: {summary.get('total', 0)}")
  40. print(f" 零消耗待关停: {summary.get('zero_spend_ads', 0)} 个")
  41. print(f" 待优化评估: {summary.get('need_review_ads', 0)} 个")
  42. print(f" 正常运行: {summary.get('normal_ads', 0)} 个")
  43. # 检查待评估广告是否包含新字段
  44. need_review_ads = data.get('need_review_ads', [])
  45. if need_review_ads:
  46. print(f"\n✅ 验证新字段(前3个样本):")
  47. for i, ad in enumerate(need_review_ads[:3]):
  48. print(f"\n 样本 {i+1}: 广告 {ad['ad_id']}")
  49. print(f" 人群包: {ad.get('audience_tier', 'N/A')}")
  50. print(f" ROI有效天数: {ad.get('roi_valid_days', 'N/A')} 天")
  51. print(f" 年龄分段: {ad.get('age_segment', 'N/A')} ({ad.get('age_protection_level', 'N/A')})")
  52. # 检查同类对比字段
  53. if 'tier_roi_p50' in ad:
  54. print(f" 同类中位数: {ad['tier_roi_p50']}")
  55. print(f" 关停线: {ad.get('pause_line_min', 'N/A')} ~ {ad.get('pause_line_max', 'N/A')}")
  56. print(f" 降价线: {ad.get('bid_down_line_min', 'N/A')} ~ {ad.get('bid_down_line_max', 'N/A')}")
  57. print(f" 提价线: {ad.get('bid_up_line_min', 'N/A')} ~ {ad.get('bid_up_line_max', 'N/A')}")
  58. # 检查操作限制
  59. if ad.get('age_segment') == 'cold_start':
  60. print(f" ⚠️ 冷启动期限制:")
  61. print(f" - 允许提价: {ad.get('allow_bid_up', 'N/A')}")
  62. print(f" - 允许降价: {ad.get('allow_bid_down', 'N/A')}")
  63. if ad.get('high_burn_alert'):
  64. print(f" 🔥 高燃烧预警: 昨日消耗 {ad.get('yesterday_cost', 'N/A')} 元")
  65. # 统计年龄分段分布
  66. age_segments = {}
  67. for ad in need_review_ads:
  68. seg = ad.get('age_segment', 'unknown')
  69. age_segments[seg] = age_segments.get(seg, 0) + 1
  70. if age_segments:
  71. print(f"\n📊 待评估广告的年龄分段分布:")
  72. for seg, count in sorted(age_segments.items()):
  73. print(f" {seg}: {count} 个")
  74. # 检查是否有冷启动期广告
  75. cold_start_ads = [ad for ad in need_review_ads if ad.get('age_segment') == 'cold_start']
  76. if cold_start_ads:
  77. print(f"\n⚠️ 发现 {len(cold_start_ads)} 个冷启动期广告(4-7天),这些广告:")
  78. print(f" - ✅ 可以提价")
  79. print(f" - ❌ 不允许降价/关停")
  80. print(f" 示例广告ID: {cold_start_ads[0]['ad_id']}")
  81. # 保存结果用于后续分析
  82. output_dir = Path(__file__).parent / "outputs" / "test_results"
  83. output_dir.mkdir(parents=True, exist_ok=True)
  84. output_file = output_dir / f"decision_test_{end_date}.json"
  85. with open(output_file, 'w', encoding='utf-8') as f:
  86. json.dump(data, f, ensure_ascii=False, indent=2)
  87. print(f"\n💾 详细结果已保存到: {output_file}")
  88. return True
  89. except Exception as e:
  90. print(f"❌ 决策引擎失败: {e}")
  91. import traceback
  92. traceback.print_exc()
  93. return False
  94. async def check_im_config():
  95. """检查IM审批配置"""
  96. print("\n" + "=" * 70)
  97. print(" 检查飞书审批配置")
  98. print("=" * 70)
  99. from examples.auto_put_ad_mini.config import (
  100. IM_ENABLED,
  101. FEISHU_APP_ID,
  102. FEISHU_APP_SECRET,
  103. FEISHU_OPERATOR_OPEN_ID,
  104. FEISHU_OPERATOR_CHAT_ID,
  105. EXECUTION_ENABLED
  106. )
  107. print(f"\n📋 当前配置:")
  108. print(f" IM_ENABLED (飞书审批开关): {IM_ENABLED}")
  109. print(f" EXECUTION_ENABLED (执行开关): {EXECUTION_ENABLED}")
  110. print(f" FEISHU_APP_ID: {FEISHU_APP_ID}")
  111. print(f" FEISHU_APP_SECRET: {'*' * 20}{FEISHU_APP_SECRET[-8:]}")
  112. print(f" FEISHU_OPERATOR_OPEN_ID: {FEISHU_OPERATOR_OPEN_ID}")
  113. print(f" FEISHU_OPERATOR_CHAT_ID: {FEISHU_OPERATOR_CHAT_ID}")
  114. # 检查配置问题
  115. issues = []
  116. if not IM_ENABLED:
  117. issues.append("⚠️ IM_ENABLED=False,飞书审批功能已关闭")
  118. if not EXECUTION_ENABLED:
  119. issues.append("ℹ️ EXECUTION_ENABLED=False,系统只验证不执行(正常的保护机制)")
  120. if not FEISHU_APP_ID or FEISHU_APP_ID == "your_app_id_here":
  121. issues.append("❌ FEISHU_APP_ID 未配置")
  122. if not FEISHU_APP_SECRET or FEISHU_APP_SECRET == "your_app_secret_here":
  123. issues.append("❌ FEISHU_APP_SECRET 未配置")
  124. if not FEISHU_OPERATOR_OPEN_ID:
  125. issues.append("❌ FEISHU_OPERATOR_OPEN_ID 未配置(接收审批的人员ID)")
  126. if issues:
  127. print(f"\n🔍 配置检查结果:")
  128. for issue in issues:
  129. print(f" {issue}")
  130. else:
  131. print(f"\n✅ 飞书审批配置正常")
  132. # 说明何时发送审批
  133. print(f"\n📖 飞书审批触发时机:")
  134. print(f" 1. 运行 execute_decisions() 工具时")
  135. print(f" 2. IM_ENABLED=True 且 EXECUTION_ENABLED=True")
  136. print(f" 3. 有待执行的决策(pause/bid_down/bid_up)")
  137. print(f"\n💡 当前状态分析:")
  138. if not IM_ENABLED:
  139. print(f" → 飞书审批已关闭,决策不会发送到飞书")
  140. print(f" → 建议:config.py 中设置 IM_ENABLED=True")
  141. elif not EXECUTION_ENABLED:
  142. print(f" → 执行开关关闭,系统处于【只验证不执行】模式")
  143. print(f" → 在此模式下,决策会被验证但不会真正调用API")
  144. print(f" → 飞书审批也不会发送(因为没有实际执行)")
  145. print(f" → 建议:如需测试审批流程,临时设置 EXECUTION_ENABLED=True")
  146. else:
  147. print(f" → 配置正常,执行决策时会发送飞书审批")
  148. return len(issues) == 0
  149. async def main():
  150. """主测试流程"""
  151. print("\n" + "🧪" * 35)
  152. print(" " * 15 + "决策引擎 + IM配置验证")
  153. print("🧪" * 35)
  154. # 测试1: 运行决策引擎
  155. decision_ok = await run_decision_test('20260415')
  156. # 测试2: 检查IM配置
  157. im_ok = await check_im_config()
  158. # 总结
  159. print("\n" + "=" * 70)
  160. print(" 测试总结")
  161. print("=" * 70)
  162. print(f" 决策引擎测试: {'✅ 通过' if decision_ok else '❌ 失败'}")
  163. print(f" IM配置检查: {'✅ 正常' if im_ok else '⚠️ 有问题'}")
  164. if decision_ok and im_ok:
  165. print(f"\n🎉 所有测试通过!")
  166. elif decision_ok:
  167. print(f"\n⚠️ 决策引擎工作正常,但IM配置需要调整")
  168. return 0 if decision_ok else 1
  169. if __name__ == "__main__":
  170. sys.exit(asyncio.run(main()))