|
|
@@ -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"
|