Просмотр исходного кода

fix(auto_put_ad_mini): 添加metrics字段合并逻辑,修复决策CSV缺失关键字段问题

- 在apply_decisions中添加merge逻辑,合并metrics CSV的17个关键字段
- 修复年龄保护逻辑,完全阻断早期成长期(4-7天)的非提价候选
- 移除cost_7d_avg的删除操作,保留所有字段到最终报告
- 解决决策CSV只有9列而飞书表格无法显示ad_name等关键信息的问题

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
刘立冬 3 недель назад
Родитель
Сommit
3d98240841
1 измененных файлов с 35 добавлено и 21 удалено
  1. 35 21
      examples/auto_put_ad_mini/tools/ad_decision.py

+ 35 - 21
examples/auto_put_ad_mini/tools/ad_decision.py

@@ -993,32 +993,25 @@ async def get_ads_for_review(
                     )
                     age_protected_skip = True
 
-                # 早期成长期(4-7天):仅允许提价和扩量评估,清除其他候选标志
+                # 早期成长期(4-7天):仅允许提价和扩量评估
+                # ⚠️ 关键修复:完全阻断非提价/扩量候选,无论何种候选标志
                 elif ad_age <= EARLY_GROWTH_DAYS:
-                    # 先判断是否需要评估
-                    need_review = roi_low or decay_signal or bid_up_candidate or bid_down_candidate or scale_up_candidate
-
-                    if need_review:
-                        # 只允许提价候选或扩量候选进入评估
-                        if not (bid_up_candidate or scale_up_candidate):
+                    # 只有提价候选或扩量候选才允许进入LLM评估
+                    # 其他所有候选标志(roi_low, decay_signal, bid_down_candidate)都被排除
+                    if not (bid_up_candidate or scale_up_candidate):
+                        # 检查是否有任何候选标志(即使不是提价/扩量)
+                        has_any_candidate = roi_low or decay_signal or bid_down_candidate
+                        if has_any_candidate:
+                            # 有候选标志但不是提价/扩量 → 直接排除
                             normal_ads_count += 1
                             logger.debug(
                                 f"广告 {row['ad_id']} 处于早期成长期({ad_age}天,4-{EARLY_GROWTH_DAYS}天),"
-                                f"年龄保护规则仅允许提价/扩量评估,其他候选已排除"
+                                f"年龄保护规则仅允许提价/扩量评估,其他候选已排除"
                                 f"(roi_low={roi_low}, decay={decay_signal}, bid_down={bid_down_candidate})"
                             )
                             age_protected_skip = True
-                        else:
-                            # 允许进入评估,但需要清除不允许的候选标志
-                            # 防止LLM基于这些信号给出降价/关停决策
-                            logger.debug(
-                                f"广告 {row['ad_id']} 处于早期成长期({ad_age}天,4-{EARLY_GROWTH_DAYS}天),"
-                                f"仅允许提价/扩量评估,清除降价/关停候选标志"
-                            )
-                            roi_low = False
-                            decay_signal = False
-                            bid_down_candidate = False
-                    # 如果不需要评估,正常计入 normal_ads_count(在后面统一处理)
+                        # else: 无任何候选标志,正常计入normal_ads_count
+                    # else: 是提价或扩量候选,允许进入评估
 
             # 年龄保护排除的广告,直接跳过
             if age_protected_skip:
@@ -1335,6 +1328,28 @@ async def apply_decisions(
 
         df_out = pd.DataFrame(all_decisions)
 
+        # ===== 关键修复:合并 metrics CSV 中的字段 =====
+        # 从 metrics CSV 补充 ad_name, ad_age_days, cost_7d_avg, 动态ROI 等字段
+        try:
+            df_metrics_full = pd.read_csv(metrics_csv)
+            # 选择需要合并的列(OUTPUT_COLUMNS中定义的所有列)
+            merge_cols = [
+                "ad_id", "account_id", "ad_name", "audience_tier", "create_time", "ad_age_days",
+                "bid_amount", "yesterday_cost", "yesterday_revenue", "yesterday_roi",
+                "cost_7d_total", "cost_7d_avg", "revenue_7d_total",
+                "动态ROI", "动态ROI_7日均值", "cost_30d_total", "cost_30d_avg",
+                "stable_spend_days_30d", "creative_count", "roi_valid_days"
+            ]
+            # 只保留存在的列
+            merge_cols = [c for c in merge_cols if c in df_metrics_full.columns]
+            df_metrics_merge = df_metrics_full[merge_cols]
+
+            # 左连接:保留df_out的所有行,补充字段
+            df_out = df_out.merge(df_metrics_merge, on="ad_id", how="left", suffixes=("", "_metrics"))
+            logger.info(f"已从 metrics CSV 合并 {len(merge_cols)} 个字段")
+        except Exception as e:
+            logger.warning(f"合并 metrics 字段失败(决策CSV将缺少扩展字段): {e}")
+
         # 过滤:已经是 AD_STATUS_SUSPEND 的广告不应出现在决策表中(已暂停无需再决策)
         ad_status_path = _MINI_DIR / "outputs" / "ad_status" / f"ad_status_{end_date}.csv"
         if ad_status_path.exists():
@@ -1367,8 +1382,7 @@ async def apply_decisions(
             df_out["cost_7d_avg"] = pd.to_numeric(df_out["cost_7d_avg"], errors="coerce").fillna(0)
             df_out = df_out.sort_values("cost_7d_avg", ascending=False).reset_index(drop=True)
 
-        # 删除cost_7d_avg列(仅用于排序,不保存到最终文件)
-        df_out = df_out.drop(columns=["cost_7d_avg"], errors="ignore")
+        # ⚠️ 不再删除 cost_7d_avg,保留所有字段到最终报告
 
         # 保存
         reports_dir = _MINI_DIR / "outputs" / "reports"