run_decision_test.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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_zero_spend=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" 待优化评估: {data.get('need_review_ads_total', 0)} 个")
  42. print(f" 正常运行: {summary.get('normal_ads', 0)} 个")
  43. # 从 tier_batches 提取待评估广告(方案 1:tier 分批)
  44. tier_batches = data.get('tier_batches', [])
  45. need_review_ads = []
  46. for batch in tier_batches:
  47. need_review_ads.extend(batch.get('ads', []))
  48. if need_review_ads:
  49. print(f"\n✅ 验证新字段(前3个样本):")
  50. for i, ad in enumerate(need_review_ads[:3]):
  51. print(f"\n 样本 {i+1}: 广告 {ad['ad_id']}")
  52. print(f" 人群包: {ad.get('audience_tier', 'N/A')}")
  53. print(f" ROI有效天数: {ad.get('roi_valid_days', 'N/A')} 天")
  54. print(f" 年龄分段: {ad.get('age_segment', 'N/A')} ({ad.get('age_protection_level', 'N/A')})")
  55. # 检查阈值字段(基于渠道P50)
  56. if 'pause_line_min' in ad:
  57. print(f" 关停线(渠道P50×0.70~0.75): {ad.get('pause_line_min', 'N/A')} ~ {ad.get('pause_line_max', 'N/A')}")
  58. print(f" 降价线(渠道P50×0.85~0.90): {ad.get('bid_down_line_min', 'N/A')} ~ {ad.get('bid_down_line_max', 'N/A')}")
  59. print(f" 提价线(渠道P50×1.05~1.10): {ad.get('bid_up_line_min', 'N/A')} ~ {ad.get('bid_up_line_max', 'N/A')}")
  60. # 检查操作限制
  61. if ad.get('age_segment') == 'cold_start':
  62. print(f" ⚠️ 冷启动期限制:")
  63. print(f" - 允许提价: {ad.get('allow_bid_up', 'N/A')}")
  64. print(f" - 允许降价: {ad.get('allow_bid_down', 'N/A')}")
  65. if ad.get('high_burn_alert'):
  66. print(f" 🔥 高燃烧预警: 昨日消耗 {ad.get('yesterday_cost', 'N/A')} 元")
  67. # 统计年龄分段分布
  68. age_segments = {}
  69. for ad in need_review_ads:
  70. seg = ad.get('age_segment', 'unknown')
  71. age_segments[seg] = age_segments.get(seg, 0) + 1
  72. if age_segments:
  73. print(f"\n📊 待评估广告的年龄分段分布:")
  74. for seg, count in sorted(age_segments.items()):
  75. print(f" {seg}: {count} 个")
  76. # 检查是否有冷启动期广告
  77. cold_start_ads = [ad for ad in need_review_ads if ad.get('age_segment') == 'cold_start']
  78. if cold_start_ads:
  79. print(f"\n⚠️ 发现 {len(cold_start_ads)} 个冷启动期广告(4-7天),这些广告:")
  80. print(f" - ✅ 可以提价")
  81. print(f" - ❌ 不允许降价/关停")
  82. print(f" 示例广告ID: {cold_start_ads[0]['ad_id']}")
  83. # 保存结果用于后续分析
  84. output_dir = Path(__file__).parent / "outputs" / "test_results"
  85. output_dir.mkdir(parents=True, exist_ok=True)
  86. output_file = output_dir / f"decision_test_{end_date}.json"
  87. with open(output_file, 'w', encoding='utf-8') as f:
  88. json.dump(data, f, ensure_ascii=False, indent=2)
  89. print(f"\n💾 详细结果已保存到: {output_file}")
  90. return True
  91. except Exception as e:
  92. print(f"❌ 决策引擎失败: {e}")
  93. import traceback
  94. traceback.print_exc()
  95. return False
  96. async def check_im_config():
  97. """检查IM审批配置"""
  98. print("\n" + "=" * 70)
  99. print(" 检查飞书审批配置")
  100. print("=" * 70)
  101. from examples.auto_put_ad_mini.config import (
  102. IM_ENABLED,
  103. FEISHU_APP_ID,
  104. FEISHU_APP_SECRET,
  105. FEISHU_OPERATOR_OPEN_ID,
  106. FEISHU_OPERATOR_CHAT_ID,
  107. EXECUTION_ENABLED
  108. )
  109. print(f"\n📋 当前配置:")
  110. print(f" IM_ENABLED (飞书审批开关): {IM_ENABLED}")
  111. print(f" EXECUTION_ENABLED (执行开关): {EXECUTION_ENABLED}")
  112. print(f" FEISHU_APP_ID: {FEISHU_APP_ID}")
  113. print(f" FEISHU_APP_SECRET: {'*' * 20}{FEISHU_APP_SECRET[-8:]}")
  114. print(f" FEISHU_OPERATOR_OPEN_ID: {FEISHU_OPERATOR_OPEN_ID}")
  115. print(f" FEISHU_OPERATOR_CHAT_ID: {FEISHU_OPERATOR_CHAT_ID}")
  116. # 检查配置问题
  117. issues = []
  118. if not IM_ENABLED:
  119. issues.append("⚠️ IM_ENABLED=False,飞书审批功能已关闭")
  120. if not EXECUTION_ENABLED:
  121. issues.append("ℹ️ EXECUTION_ENABLED=False,系统只验证不执行(正常的保护机制)")
  122. if not FEISHU_APP_ID or FEISHU_APP_ID == "your_app_id_here":
  123. issues.append("❌ FEISHU_APP_ID 未配置")
  124. if not FEISHU_APP_SECRET or FEISHU_APP_SECRET == "your_app_secret_here":
  125. issues.append("❌ FEISHU_APP_SECRET 未配置")
  126. if not FEISHU_OPERATOR_OPEN_ID:
  127. issues.append("❌ FEISHU_OPERATOR_OPEN_ID 未配置(接收审批的人员ID)")
  128. if issues:
  129. print(f"\n🔍 配置检查结果:")
  130. for issue in issues:
  131. print(f" {issue}")
  132. else:
  133. print(f"\n✅ 飞书审批配置正常")
  134. # 说明何时发送审批
  135. print(f"\n📖 飞书审批触发时机:")
  136. print(f" 1. 运行 execute_decisions() 工具时")
  137. print(f" 2. IM_ENABLED=True 且 EXECUTION_ENABLED=True")
  138. print(f" 3. 有待执行的决策(pause/bid_down/bid_up)")
  139. print(f"\n💡 当前状态分析:")
  140. if not IM_ENABLED:
  141. print(f" → 飞书审批已关闭,决策不会发送到飞书")
  142. print(f" → 建议:config.py 中设置 IM_ENABLED=True")
  143. elif not EXECUTION_ENABLED:
  144. print(f" → 执行开关关闭,系统处于【只验证不执行】模式")
  145. print(f" → 在此模式下,决策会被验证但不会真正调用API")
  146. print(f" → 飞书审批也不会发送(因为没有实际执行)")
  147. print(f" → 建议:如需测试审批流程,临时设置 EXECUTION_ENABLED=True")
  148. else:
  149. print(f" → 配置正常,执行决策时会发送飞书审批")
  150. return len(issues) == 0
  151. async def main():
  152. """主测试流程"""
  153. print("\n" + "🧪" * 35)
  154. print(" " * 15 + "决策引擎 + IM配置验证")
  155. print("🧪" * 35)
  156. # 测试1: 运行决策引擎
  157. decision_ok = await run_decision_test('20260415')
  158. # 测试2: 检查IM配置
  159. im_ok = await check_im_config()
  160. # 总结
  161. print("\n" + "=" * 70)
  162. print(" 测试总结")
  163. print("=" * 70)
  164. print(f" 决策引擎测试: {'✅ 通过' if decision_ok else '❌ 失败'}")
  165. print(f" IM配置检查: {'✅ 正常' if im_ok else '⚠️ 有问题'}")
  166. if decision_ok and im_ok:
  167. print(f"\n🎉 所有测试通过!")
  168. elif decision_ok:
  169. print(f"\n⚠️ 决策引擎工作正常,但IM配置需要调整")
  170. return 0 if decision_ok else 1
  171. if __name__ == "__main__":
  172. sys.exit(asyncio.run(main()))