|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
|
|
|
import argparse
|
|
|
import csv
|
|
|
+import json
|
|
|
import sys
|
|
|
from dataclasses import dataclass
|
|
|
from decimal import Decimal, ROUND_HALF_UP
|
|
|
@@ -30,6 +31,7 @@ DEFAULT_FEEDBACK_KEY = "miniapp_click_default"
|
|
|
DEFAULT_AGE_MIN = 45
|
|
|
DEFAULT_AGE_MAX = 66
|
|
|
DEFAULT_SOURCE_ACCOUNT_ID = 55615440
|
|
|
+DEFAULT_BID_SCENE = "average_cost"
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
@@ -43,6 +45,10 @@ class AccountConfigInput:
|
|
|
enabled: bool = True
|
|
|
material_source: str = "history"
|
|
|
ai_fallback_to_history: bool = False
|
|
|
+ bid_scene: str = DEFAULT_BID_SCENE
|
|
|
+ custom_cost_cap_fen: int | None = None
|
|
|
+ automatic_site_enabled: bool | None = None
|
|
|
+ site_set: list[str] | None = None
|
|
|
|
|
|
|
|
|
def _bid_to_fen(raw: str) -> int:
|
|
|
@@ -74,6 +80,67 @@ def parse_budget_fen(raw: object) -> int | None:
|
|
|
return _bid_to_fen(text)
|
|
|
|
|
|
|
|
|
+def ensure_account_delivery_config_columns() -> None:
|
|
|
+ """Add account-level delivery override columns when deploying old DBs."""
|
|
|
+ from db.connection import get_connection
|
|
|
+
|
|
|
+ conn = get_connection()
|
|
|
+ try:
|
|
|
+ with conn.cursor() as cur:
|
|
|
+ cur.execute(
|
|
|
+ """
|
|
|
+ SELECT COLUMN_NAME
|
|
|
+ FROM information_schema.COLUMNS
|
|
|
+ WHERE TABLE_SCHEMA = DATABASE()
|
|
|
+ AND TABLE_NAME = 'ad_creation_account_config'
|
|
|
+ AND COLUMN_NAME IN (
|
|
|
+ 'bid_scene', 'custom_cost_cap_fen',
|
|
|
+ 'automatic_site_enabled', 'site_set_json'
|
|
|
+ )
|
|
|
+ """
|
|
|
+ )
|
|
|
+ existing = {row["COLUMN_NAME"] for row in (cur.fetchall() or [])}
|
|
|
+ if "bid_scene" not in existing:
|
|
|
+ cur.execute(
|
|
|
+ """
|
|
|
+ ALTER TABLE ad_creation_account_config
|
|
|
+ ADD COLUMN bid_scene VARCHAR(50) DEFAULT NULL
|
|
|
+ COMMENT '出价场景:BID_SCENE_NORMAL_AVERAGE/BID_SCENE_NORMAL_MAX'
|
|
|
+ AFTER bid_amount_fen
|
|
|
+ """
|
|
|
+ )
|
|
|
+ if "custom_cost_cap_fen" not in existing:
|
|
|
+ cur.execute(
|
|
|
+ """
|
|
|
+ ALTER TABLE ad_creation_account_config
|
|
|
+ ADD COLUMN custom_cost_cap_fen INT DEFAULT NULL
|
|
|
+ COMMENT '最大转化量控制成本(分),用于 custom_cost_cap'
|
|
|
+ AFTER bid_scene
|
|
|
+ """
|
|
|
+ )
|
|
|
+ if "automatic_site_enabled" not in existing:
|
|
|
+ cur.execute(
|
|
|
+ """
|
|
|
+ ALTER TABLE ad_creation_account_config
|
|
|
+ ADD COLUMN automatic_site_enabled BOOLEAN DEFAULT NULL
|
|
|
+ COMMENT '账户级是否开启智能版位;NULL使用模板默认手动版位'
|
|
|
+ AFTER daily_budget_fen
|
|
|
+ """
|
|
|
+ )
|
|
|
+ if "site_set_json" not in existing:
|
|
|
+ cur.execute(
|
|
|
+ """
|
|
|
+ ALTER TABLE ad_creation_account_config
|
|
|
+ ADD COLUMN site_set_json TEXT DEFAULT NULL
|
|
|
+ COMMENT '账户级投放版位覆盖JSON;NULL使用投放模板'
|
|
|
+ AFTER automatic_site_enabled
|
|
|
+ """
|
|
|
+ )
|
|
|
+ conn.commit()
|
|
|
+ finally:
|
|
|
+ conn.close()
|
|
|
+
|
|
|
+
|
|
|
def _read_inputs(args: argparse.Namespace) -> list[AccountConfigInput]:
|
|
|
rows: list[AccountConfigInput] = []
|
|
|
if args.csv:
|
|
|
@@ -123,11 +190,14 @@ def upsert_account_config(row: AccountConfigInput, dry_run: bool = False) -> Non
|
|
|
f"[dry-run] account={row.account_id} audience={row.audience_name} "
|
|
|
f"bid={row.bid_min_fen}-{row.bid_max_fen} source={source_account_id} "
|
|
|
f"status={grant_status} material_source={row.material_source} "
|
|
|
- f"ai_fallback={row.ai_fallback_to_history}"
|
|
|
+ f"ai_fallback={row.ai_fallback_to_history} bid_scene={row.bid_scene} "
|
|
|
+ f"custom_cost_cap={row.custom_cost_cap_fen} "
|
|
|
+ f"automatic_site={row.automatic_site_enabled} site_set={row.site_set}"
|
|
|
)
|
|
|
return
|
|
|
|
|
|
ensure_account_material_strategy_columns()
|
|
|
+ ensure_account_delivery_config_columns()
|
|
|
conn = get_connection()
|
|
|
try:
|
|
|
with conn.cursor() as cur:
|
|
|
@@ -182,7 +252,9 @@ def upsert_account_config(row: AccountConfigInput, dry_run: bool = False) -> Non
|
|
|
(account_id, enabled, delivery_version, brand_key, feedback_key,
|
|
|
material_source, ai_fallback_to_history,
|
|
|
audience_name, audience_pack_id, audience_tier_label,
|
|
|
- bid_min_fen, bid_max_fen, bid_amount_fen, daily_budget_fen,
|
|
|
+ bid_min_fen, bid_max_fen, bid_amount_fen, bid_scene,
|
|
|
+ custom_cost_cap_fen,
|
|
|
+ daily_budget_fen, automatic_site_enabled, site_set_json,
|
|
|
age_min, age_max, audience_source_account_id, audience_grant_status,
|
|
|
remark, created_by, updated_by)
|
|
|
VALUES
|
|
|
@@ -190,6 +262,8 @@ def upsert_account_config(row: AccountConfigInput, dry_run: bool = False) -> Non
|
|
|
%s, %s,
|
|
|
%s, %s, %s,
|
|
|
%s, %s, %s, %s,
|
|
|
+ %s,
|
|
|
+ %s, %s, %s,
|
|
|
%s, %s, %s, %s,
|
|
|
%s, %s, %s)
|
|
|
ON DUPLICATE KEY UPDATE
|
|
|
@@ -208,7 +282,11 @@ def upsert_account_config(row: AccountConfigInput, dry_run: bool = False) -> Non
|
|
|
bid_min_fen=VALUES(bid_min_fen),
|
|
|
bid_max_fen=VALUES(bid_max_fen),
|
|
|
bid_amount_fen=VALUES(bid_amount_fen),
|
|
|
+ bid_scene=VALUES(bid_scene),
|
|
|
+ custom_cost_cap_fen=VALUES(custom_cost_cap_fen),
|
|
|
daily_budget_fen=VALUES(daily_budget_fen),
|
|
|
+ automatic_site_enabled=VALUES(automatic_site_enabled),
|
|
|
+ site_set_json=VALUES(site_set_json),
|
|
|
age_min=VALUES(age_min),
|
|
|
age_max=VALUES(age_max),
|
|
|
audience_source_account_id=IF(
|
|
|
@@ -243,7 +321,11 @@ def upsert_account_config(row: AccountConfigInput, dry_run: bool = False) -> Non
|
|
|
row.bid_min_fen,
|
|
|
row.bid_max_fen,
|
|
|
row.bid_amount_fen,
|
|
|
+ row.bid_scene,
|
|
|
+ row.custom_cost_cap_fen,
|
|
|
row.daily_budget_fen,
|
|
|
+ None if row.automatic_site_enabled is None else (1 if row.automatic_site_enabled else 0),
|
|
|
+ json.dumps(row.site_set, ensure_ascii=False) if row.site_set is not None else None,
|
|
|
DEFAULT_AGE_MIN,
|
|
|
DEFAULT_AGE_MAX,
|
|
|
source_account_id,
|
|
|
@@ -259,7 +341,9 @@ def upsert_account_config(row: AccountConfigInput, dry_run: bool = False) -> Non
|
|
|
|
|
|
print(
|
|
|
f"configured account={row.account_id} audience={row.audience_name} "
|
|
|
- f"bid={row.bid_min_fen / 100:.2f}-{row.bid_max_fen / 100:.2f} status={grant_status}"
|
|
|
+ f"bid={row.bid_min_fen / 100:.2f}-{row.bid_max_fen / 100:.2f} "
|
|
|
+ f"bid_scene={row.bid_scene} custom_cost_cap={row.custom_cost_cap_fen} "
|
|
|
+ f"status={grant_status}"
|
|
|
)
|
|
|
|
|
|
|