"""Debug creative pattern selection for one video. This script is read-only except the video feature cache may be populated when ODPS lookup is enabled. It does not generate images, upload OSS files, or create Tencent ads. """ from __future__ import annotations import argparse import json import sys from pathlib import Path from dotenv import load_dotenv _HERE = Path(__file__).parent load_dotenv(_HERE / ".env") sys.path.insert(0, str(_HERE)) from tools.material_strategy_learning import select_creative_patterns # noqa: E402 from tools.video_feature_query import ( # noqa: E402 fetch_video_element_features, read_cached_video_element_features, ) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="调试单个视频的创意 pattern 选择") parser.add_argument("--video-id", type=int, required=True) parser.add_argument("--crowd-package", default="", help="保留展示字段,pattern选择不按人群包区分") parser.add_argument("--placement", default="") parser.add_argument("--top-k", type=int, default=3) parser.add_argument( "--include-draft", action="store_true", default=True, help="包含 DRAFT pattern,用于上线前调试评估", ) parser.add_argument( "--approved-only", action="store_true", help="只看 APPROVED/enabled=1 的生产可用 pattern", ) parser.add_argument( "--cached-only", action="store_true", help="只读本地视频特征缓存,不查 ODPS", ) parser.add_argument( "--no-model", action="store_true", help="关闭模型选择,只看非语义稳定兜底排序", ) parser.add_argument( "--model", default=None, help="覆盖 OPENROUTER_TEXT_MODEL,例如 google/gemini-2.5-flash", ) return parser.parse_args() def _feature_to_dict(feature) -> dict: return { "video_id": feature.video_id, "dt": feature.dt, "element_dimension": feature.element_dimension, "point_type": feature.point_type, "standard_element": feature.standard_element, "contribution_score": feature.contribution_score, } def main() -> int: args = parse_args() if args.cached_only: feature_map = read_cached_video_element_features([args.video_id]) else: feature_map = fetch_video_element_features([args.video_id]) features = feature_map.get(args.video_id) or [] selections = select_creative_patterns( video_features=features, crowd_package=args.crowd_package, placement=args.placement, include_draft=not args.approved_only and args.include_draft, top_k=args.top_k, use_model=not args.no_model, model=args.model, ) payload = { "video_id": args.video_id, "crowd_package": args.crowd_package, "placement": args.placement, "selection_mode": "model_select" if not args.no_model else "stable_fallback", "feature_count": len(features), "features": [_feature_to_dict(feature) for feature in features], "selected_patterns": [ { "pattern_version": item.pattern.pattern_version, "pattern_key": item.pattern.pattern_key, "pattern_name": item.pattern.pattern_name, "status": item.pattern.status, "enabled": item.pattern.enabled, "score": item.score, "reasons": item.reasons, "penalties": item.penalties, "matched_features": item.matched_features, "title_hook_rule": item.pattern.title_hook_rule, "visual_rule": item.pattern.visual_rule, "relevance_rule": item.pattern.relevance_rule, "compliance_rule": item.pattern.compliance_rule, "positive_examples": item.pattern.positive_examples or [], "negative_examples": item.pattern.negative_examples or [], } for item in selections ], } print(json.dumps(payload, ensure_ascii=False, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())