debug_select_creative_patterns.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. """Debug creative pattern selection for one video.
  2. This script is read-only except the video feature cache may be populated when
  3. ODPS lookup is enabled. It does not generate images, upload OSS files, or create
  4. Tencent ads.
  5. """
  6. from __future__ import annotations
  7. import argparse
  8. import json
  9. import sys
  10. from pathlib import Path
  11. from dotenv import load_dotenv
  12. _HERE = Path(__file__).parent
  13. load_dotenv(_HERE / ".env")
  14. sys.path.insert(0, str(_HERE))
  15. from tools.material_strategy_learning import select_creative_patterns # noqa: E402
  16. from tools.video_feature_query import ( # noqa: E402
  17. fetch_video_element_features,
  18. read_cached_video_element_features,
  19. )
  20. def parse_args() -> argparse.Namespace:
  21. parser = argparse.ArgumentParser(description="调试单个视频的创意 pattern 选择")
  22. parser.add_argument("--video-id", type=int, required=True)
  23. parser.add_argument("--crowd-package", default="", help="保留展示字段,pattern选择不按人群包区分")
  24. parser.add_argument("--placement", default="")
  25. parser.add_argument("--top-k", type=int, default=3)
  26. parser.add_argument(
  27. "--include-draft",
  28. action="store_true",
  29. default=True,
  30. help="包含 DRAFT pattern,用于上线前调试评估",
  31. )
  32. parser.add_argument(
  33. "--approved-only",
  34. action="store_true",
  35. help="只看 APPROVED/enabled=1 的生产可用 pattern",
  36. )
  37. parser.add_argument(
  38. "--cached-only",
  39. action="store_true",
  40. help="只读本地视频特征缓存,不查 ODPS",
  41. )
  42. parser.add_argument(
  43. "--no-model",
  44. action="store_true",
  45. help="关闭模型选择,只看非语义稳定兜底排序",
  46. )
  47. parser.add_argument(
  48. "--model",
  49. default=None,
  50. help="覆盖 OPENROUTER_TEXT_MODEL,例如 google/gemini-2.5-flash",
  51. )
  52. return parser.parse_args()
  53. def _feature_to_dict(feature) -> dict:
  54. return {
  55. "video_id": feature.video_id,
  56. "dt": feature.dt,
  57. "element_dimension": feature.element_dimension,
  58. "point_type": feature.point_type,
  59. "standard_element": feature.standard_element,
  60. "contribution_score": feature.contribution_score,
  61. }
  62. def main() -> int:
  63. args = parse_args()
  64. if args.cached_only:
  65. feature_map = read_cached_video_element_features([args.video_id])
  66. else:
  67. feature_map = fetch_video_element_features([args.video_id])
  68. features = feature_map.get(args.video_id) or []
  69. selections = select_creative_patterns(
  70. video_features=features,
  71. crowd_package=args.crowd_package,
  72. placement=args.placement,
  73. include_draft=not args.approved_only and args.include_draft,
  74. top_k=args.top_k,
  75. use_model=not args.no_model,
  76. model=args.model,
  77. )
  78. payload = {
  79. "video_id": args.video_id,
  80. "crowd_package": args.crowd_package,
  81. "placement": args.placement,
  82. "selection_mode": "model_select" if not args.no_model else "stable_fallback",
  83. "feature_count": len(features),
  84. "features": [_feature_to_dict(feature) for feature in features],
  85. "selected_patterns": [
  86. {
  87. "pattern_version": item.pattern.pattern_version,
  88. "pattern_key": item.pattern.pattern_key,
  89. "pattern_name": item.pattern.pattern_name,
  90. "status": item.pattern.status,
  91. "enabled": item.pattern.enabled,
  92. "score": item.score,
  93. "reasons": item.reasons,
  94. "penalties": item.penalties,
  95. "matched_features": item.matched_features,
  96. "title_hook_rule": item.pattern.title_hook_rule,
  97. "visual_rule": item.pattern.visual_rule,
  98. "relevance_rule": item.pattern.relevance_rule,
  99. "compliance_rule": item.pattern.compliance_rule,
  100. "positive_examples": item.pattern.positive_examples or [],
  101. "negative_examples": item.pattern.negative_examples or [],
  102. }
  103. for item in selections
  104. ],
  105. }
  106. print(json.dumps(payload, ensure_ascii=False, indent=2))
  107. return 0
  108. if __name__ == "__main__":
  109. raise SystemExit(main())