| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- """Translate versioned ROI metrics into auditable recommendations and actions."""
- from __future__ import annotations
- import hashlib
- import math
- from typing import Any
- import pandas as pd
- from .fission_multiplier import (
- DISPLAY_MULTIPLIER_COLUMN,
- DISPLAY_TOTAL_TO_FIRST_COLUMN,
- )
- from .metrics import ENTITY_KEYS, ENTITY_SELF
- ACTION_PAUSE_CREATIVE = "PAUSE_CREATIVE"
- ACTION_SCALE_BID = "SCALE_BID"
- MODE_ACTIONABLE = "ACTIONABLE"
- MODE_NOTIFY_ONLY = "NOTIFY_ONLY"
- def safe_int(value: Any) -> int | None:
- if value is None or value == "":
- return None
- try:
- number = int(float(value))
- except (TypeError, ValueError):
- return None
- return number if number > 0 else None
- def _entity_hash(row: pd.Series) -> str:
- keys = ENTITY_KEYS[str(row["entity_type"])]
- raw = "\x1f".join(str(row.get(key, "")) for key in keys)
- return hashlib.sha256(raw.encode("utf-8")).hexdigest()
- def _finite(value: Any) -> float | None:
- try:
- number = float(value)
- except (TypeError, ValueError):
- return None
- return number if math.isfinite(number) else None
- def annotate_execution(
- summary: pd.DataFrame,
- managed_account_ids: set[int],
- run_id: str,
- ) -> tuple[pd.DataFrame, list[dict[str, Any]], list[dict[str, Any]]]:
- """Keep policy recommendations separate from Tencent eligibility.
- Returns annotated summary, complete metric snapshots, and deduplicated
- executable action items.
- """
- annotated = summary.copy()
- annotated["执行模式"] = MODE_NOTIFY_ONLY
- annotated["执行说明"] = "无调控建议"
- annotated["审批选择"] = "不可执行"
- annotated["执行状态"] = ""
- annotated["执行结果"] = ""
- annotated["动作幂等键"] = ""
- actions: list[dict[str, Any]] = []
- snapshots: list[dict[str, Any]] = []
- seen_actions: set[str] = set()
- for index, row in annotated.iterrows():
- entity_type = str(row.get("entity_type") or "")
- recommendation = str(row.get("动作") or "")
- account_id = safe_int(row.get("账号id"))
- adgroup_id = safe_int(row.get("广告id"))
- creative_id = safe_int(row.get("创意id"))
- execution_mode = MODE_NOTIFY_ONLY
- execution_reason = "无调控建议"
- action_type: str | None = None
- if recommendation:
- if entity_type != ENTITY_SELF:
- execution_reason = "合作渠道当前仅通知,不执行腾讯写操作"
- elif account_id not in managed_account_ids:
- execution_reason = "账户不在自动化管理范围,仅通知"
- elif not adgroup_id:
- execution_reason = "缺少有效广告ID,仅通知"
- elif recommendation == "关停" and not creative_id:
- execution_reason = "缺少有效动态创意ID,仅通知"
- elif recommendation == "关停":
- action_type = ACTION_PAUSE_CREATIVE
- elif recommendation == "扩量":
- action_type = ACTION_SCALE_BID
- else:
- execution_reason = "该建议当前没有自动执行器,仅通知"
- if action_type:
- suffix = creative_id if action_type == ACTION_PAUSE_CREATIVE else adgroup_id
- idempotency_key = f"{run_id}:{action_type}:{account_id}:{suffix}"
- execution_mode = MODE_ACTIONABLE
- execution_reason = (
- "审批后暂停该动态创意"
- if action_type == ACTION_PAUSE_CREATIVE
- else "审批后按ROI策略提高广告永久基础出价"
- )
- if idempotency_key not in seen_actions:
- actions.append(
- {
- "idempotency_key": idempotency_key,
- "action_type": action_type,
- "account_id": account_id,
- "adgroup_id": adgroup_id,
- "dynamic_creative_id": creative_id,
- "execution_status": "PENDING",
- }
- )
- seen_actions.add(idempotency_key)
- annotated.at[index, "执行模式"] = execution_mode
- annotated.at[index, "执行说明"] = execution_reason
- if execution_mode == MODE_ACTIONABLE:
- annotated.at[index, "审批选择"] = ""
- annotated.at[index, "执行状态"] = "待审批"
- annotated.at[index, "动作幂等键"] = idempotency_key
- daily_metrics = {
- column: _finite(row.get(column))
- for column in annotated.columns
- if column.startswith(
- (
- "首层UV_20",
- "T0裂变数_20",
- "成本_20",
- "效率收入_20",
- "裂变效率收入_20",
- "实际ROI_20",
- "ROI_20",
- )
- )
- }
- daily_metrics.update(
- {
- "fission_parameter_version": str(
- row.get("传播裂变参数版本") or ""
- ),
- "fission_cohort_date": str(
- row.get("传播裂变参数cohort日期") or ""
- ),
- "fission_match_level": str(
- row.get("传播裂变系数匹配层级") or ""
- ),
- "fission_source": str(row.get("传播裂变系数来源") or ""),
- "fission_parameter_status": str(
- row.get("传播裂变参数状态") or ""
- ),
- "control_participation_status": str(
- row.get("调控参与状态") or ""
- ),
- "roi_method": str(row.get("ROI计算口径") or ""),
- }
- )
- snapshots.append(
- {
- "entity_hash": _entity_hash(row),
- "entity_type": entity_type,
- "channel": str(row.get("channel") or ""),
- "account_id": account_id,
- "account_name": str(row.get("账号名称") or ""),
- "adgroup_id": adgroup_id,
- "adgroup_name": str(row.get("广告名称") or ""),
- "dynamic_creative_id": creative_id,
- "audience_name": str(row.get("包名") or ""),
- "conversion_goal": str(row.get("广告优化目标") or ""),
- "partner_name": str(row.get("合作方名") or ""),
- "official_account_name": str(row.get("公众号名") or ""),
- "fission_parameter_version": str(
- row.get("传播裂变参数版本") or ""
- ),
- "fission_cohort_date": str(
- row.get("传播裂变参数cohort日期") or ""
- ),
- "fission_multiplier_vs_t0": _finite(
- row.get(DISPLAY_MULTIPLIER_COLUMN)
- ),
- "fission_multiplier_vs_first": _finite(
- row.get(DISPLAY_TOTAL_TO_FIRST_COLUMN)
- ),
- "fission_match_level": str(
- row.get("传播裂变系数匹配层级") or ""
- ),
- "fission_source": str(row.get("传播裂变系数来源") or ""),
- "ad_age": safe_int(row.get("广告age")),
- "avg_first_uv": _finite(row.get("日均首层UV")),
- "first_uv": _finite(row.get("三日首层UV")),
- "t0_fission_count": _finite(row.get("三日T0裂变数")),
- "t0_fission_rate": _finite(row.get("T0裂变率")),
- "cost": _finite(row.get("成本")),
- "efficiency_revenue": _finite(row.get("效率收入")),
- "fission_revenue": _finite(row.get("T0实际裂变收入")),
- "actual_total_revenue": _finite(
- row.get("实际全链路效率收入")
- ),
- "actual_roi": _finite(row.get("实际ROI")),
- "predicted_tail_revenue": _finite(
- row.get("预测T1-T15裂变收入")
- ),
- "predicted_fission_revenue": _finite(
- row.get("预测T0-T15裂变收入")
- ),
- "total_revenue": _finite(row.get("全链路效率收入")),
- "roi": _finite(row.get("ROI")),
- "stop_threshold": _finite(row.get("t_stop")),
- "scale_threshold": _finite(row.get("t_up")),
- "recommended_action": recommendation or None,
- "action_reason": str(row.get("动作原因") or ""),
- "execution_mode": execution_mode,
- "ineligible_reason": (
- execution_reason if execution_mode == MODE_NOTIFY_ONLY else None
- ),
- "daily_metrics_json": daily_metrics,
- }
- )
- return annotated, snapshots, actions
|