| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- """Account-level creative material source strategy.
- The default remains historical recall. AI generation and external recall are
- enabled only for accounts explicitly configured for those module B branches.
- """
- from __future__ import annotations
- import logging
- from dataclasses import dataclass
- logger = logging.getLogger(__name__)
- MATERIAL_SOURCE_HISTORY = "history"
- MATERIAL_SOURCE_AI_GENERATED = "ai_generated"
- MATERIAL_SOURCE_EXTERNAL_RECALL = "external_recall"
- _AI_SOURCE_VALUES = {
- "ai",
- "ai_generated",
- "ai生成",
- "ai生成素材",
- "生成素材",
- "新生成素材",
- }
- _HISTORY_SOURCE_VALUES = {
- "",
- "history",
- "历史",
- "历史素材",
- "历史投放素材",
- "历史已投素材",
- }
- _EXTERNAL_SOURCE_VALUES = {
- "external",
- "external_recall",
- "外部",
- "外部素材",
- "外部素材召回",
- "外部合作素材",
- }
- _TRUE_VALUES = {"1", "true", "yes", "y", "是", "允许", "回退"}
- @dataclass(frozen=True)
- class AccountMaterialStrategy:
- account_id: int
- material_source: str = MATERIAL_SOURCE_HISTORY
- ai_fallback_to_history: bool = False
- @property
- def use_ai_generated(self) -> bool:
- return self.material_source == MATERIAL_SOURCE_AI_GENERATED
- @property
- def use_external_recall(self) -> bool:
- return self.material_source == MATERIAL_SOURCE_EXTERNAL_RECALL
- def normalize_material_source(raw: object) -> str:
- text = str(raw or "").strip().lower()
- if text in _AI_SOURCE_VALUES:
- return MATERIAL_SOURCE_AI_GENERATED
- if text in _HISTORY_SOURCE_VALUES:
- return MATERIAL_SOURCE_HISTORY
- if text in _EXTERNAL_SOURCE_VALUES:
- return MATERIAL_SOURCE_EXTERNAL_RECALL
- raise ValueError(f"未知素材来源:{raw}")
- def parse_bool_flag(raw: object) -> bool:
- return str(raw or "").strip().lower() in _TRUE_VALUES
- def ensure_account_material_strategy_columns() -> None:
- """Add strategy columns when deploying to an older DB schema."""
- 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 ('material_source', 'ai_fallback_to_history')
- """
- )
- existing = {row["COLUMN_NAME"] for row in (cur.fetchall() or [])}
- if "material_source" not in existing:
- cur.execute(
- """
- ALTER TABLE ad_creation_account_config
- ADD COLUMN material_source VARCHAR(50) NOT NULL DEFAULT 'history'
- COMMENT '创意素材来源:history/ai_generated/external_recall'
- AFTER feedback_key
- """
- )
- if "ai_fallback_to_history" not in existing:
- cur.execute(
- """
- ALTER TABLE ad_creation_account_config
- ADD COLUMN ai_fallback_to_history BOOLEAN NOT NULL DEFAULT FALSE
- COMMENT 'AI生成失败时是否回退历史素材'
- AFTER material_source
- """
- )
- conn.commit()
- finally:
- conn.close()
- def load_account_material_strategy(account_id: int) -> AccountMaterialStrategy:
- """Load configured material strategy. Missing config defaults to history."""
- from db.connection import get_connection
- try:
- ensure_account_material_strategy_columns()
- except Exception as e:
- logger.warning(
- "[material_strategy] ensure columns failed account=%d, fallback history: %s",
- account_id, e,
- )
- return AccountMaterialStrategy(account_id=account_id)
- conn = get_connection()
- try:
- with conn.cursor() as cur:
- cur.execute(
- """
- SELECT material_source, ai_fallback_to_history
- FROM ad_creation_account_config
- WHERE account_id=%s
- LIMIT 1
- """,
- (account_id,),
- )
- row = cur.fetchone() or {}
- finally:
- conn.close()
- try:
- source = normalize_material_source(row.get("material_source"))
- except ValueError:
- logger.warning(
- "[material_strategy] account=%d material_source=%r 非法,按 history 处理",
- account_id, row.get("material_source"),
- )
- source = MATERIAL_SOURCE_HISTORY
- return AccountMaterialStrategy(
- account_id=account_id,
- material_source=source,
- ai_fallback_to_history=bool(row.get("ai_fallback_to_history")),
- )
|