| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- """Account-level bidding and placement config parsed from Feishu/DB."""
- from __future__ import annotations
- from dataclasses import dataclass
- from functools import lru_cache
- import json
- from pathlib import Path
- BID_MODE_AVERAGE_COST = "average_cost"
- BID_MODE_MAX_CONVERSION = "max_conversion"
- @dataclass(frozen=True)
- class PlacementConfig:
- automatic_site_enabled: bool
- site_set: list[str]
- @dataclass(frozen=True)
- class GeoLocationConfig:
- location_types: list[str]
- region_ids: list[int]
- _BID_SCENE_VALUES = {
- "": BID_MODE_AVERAGE_COST,
- "平均成本": BID_MODE_AVERAGE_COST,
- "稳定成本": BID_MODE_AVERAGE_COST,
- "最大转化量": BID_MODE_MAX_CONVERSION,
- "最大转化量出价": BID_MODE_MAX_CONVERSION,
- "average_cost": BID_MODE_AVERAGE_COST,
- "max_conversion": BID_MODE_MAX_CONVERSION,
- # 兼容已经同步过的旧值。
- "BID_SCENE_NORMAL_AVERAGE": BID_MODE_AVERAGE_COST,
- "BID_SCENE_NORMAL_MAX": BID_MODE_MAX_CONVERSION,
- }
- _PLACEMENT_GROUPS = {
- "微信朋友圈": ["SITE_SET_MOMENTS"],
- "朋友圈": ["SITE_SET_MOMENTS"],
- "微信公众号与小程序": ["SITE_SET_WECHAT", "SITE_SET_WECHAT_PLUGIN"],
- "微信公众号": ["SITE_SET_WECHAT"],
- "微信插件": ["SITE_SET_WECHAT_PLUGIN"],
- "搜索场景": ["SITE_SET_SEARCH_SCENE"],
- "腾讯平台与内容媒体": [
- "SITE_SET_TENCENT_NEWS",
- "SITE_SET_TENCENT_VIDEO",
- "SITE_SET_KANDIAN",
- "SITE_SET_QQ_MUSIC_GAME",
- ],
- "腾讯营销联盟": ["SITE_SET_MOBILE_UNION"],
- }
- def parse_bid_scene(raw: object) -> str:
- text = str(raw or "").strip()
- if text in _BID_SCENE_VALUES:
- return _BID_SCENE_VALUES[text]
- raise ValueError(f"未知出价方式:{raw}")
- def parse_placement_config(raw: object) -> PlacementConfig | None:
- text = str(raw or "").strip()
- if not text:
- return None
- if text.upper() == "AIM+":
- return PlacementConfig(automatic_site_enabled=True, site_set=[])
- site_set: list[str] = []
- for part in text.replace(",", ",").split(","):
- name = part.strip()
- if not name:
- continue
- values = _PLACEMENT_GROUPS.get(name)
- if values is None:
- raise ValueError(f"未知投放版位:{name}")
- for value in values:
- if value not in site_set:
- site_set.append(value)
- if not site_set:
- raise ValueError(f"投放版位为空:{raw}")
- return PlacementConfig(automatic_site_enabled=False, site_set=site_set)
- @lru_cache(maxsize=1)
- def _region_name_mapping() -> dict[str, int]:
- path = Path(__file__).resolve().parents[1] / "data" / "tencent_constants" / "regions_all.json"
- regions = json.loads(path.read_text(encoding="utf-8"))
- mapping: dict[str, int] = {}
- for region in regions:
- name = str(region.get("name") or "").strip()
- if not name:
- continue
- region_id = int(region["id"])
- mapping.setdefault(name, region_id)
- for suffix in ("特别行政区", "壮族自治区", "回族自治区", "维吾尔自治区", "自治区", "省", "市"):
- if name.endswith(suffix):
- mapping.setdefault(name[:-len(suffix)], region_id)
- break
- return mapping
- def parse_geo_location_config(raw: object) -> GeoLocationConfig | None:
- """Parse Feishu geography: blank keeps the delivery template; 不限 omits geo targeting."""
- text = str(raw or "").strip()
- if not text:
- return None
- if text.lower() in {"不限", "不限地域", "全国", "all"}:
- return GeoLocationConfig(location_types=[], region_ids=[])
- names = [part.strip() for part in text.replace(",", ",").split(",") if part.strip()]
- if not names:
- raise ValueError(f"地域为空:{raw}")
- name_mapping = _region_name_mapping()
- region_ids: list[int] = []
- for name in names:
- try:
- region_id = int(name)
- except ValueError:
- region_id = name_mapping.get(name, 0)
- if not region_id:
- raise ValueError(f"未知地域:{name}")
- if region_id not in region_ids:
- region_ids.append(region_id)
- return GeoLocationConfig(location_types=["LIVE_IN"], region_ids=region_ids)
|