fix: 来源冲突比较升级为广告家族归一(meta/google/bing/yandex 家族)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -19,8 +19,18 @@ from tracker import constants as C
|
||||
_CTRL_CHARS = re.compile(r"[\x00-\x1f\x7f]")
|
||||
_GENERIC_TLDS = ("com",) # 引擎 host 落在这些 TLD 时不作为国家 variant 显示
|
||||
|
||||
# UTM 标签 ↔ 平台名别名(冲突判定前归一):缩写/中文品牌名 → 标准名;baidu 恒等仅为可读
|
||||
_UTM_ALIASES = {"fb": "facebook", "ig": "instagram", "tt": "tiktok",
|
||||
# 广告平台家族归一表(冲突判定前):同一家族的各种写法(缩写/全称/带 ads 后缀/
|
||||
# 中文俄文别称)→ 家族名。fbclid 推出的 "Meta Ads" 与 utm_source=fb/ig 同属 meta
|
||||
# 家族 → 印证而非矛盾。键为去空格后的紧凑形态("meta ads" → "metaads")。
|
||||
_AD_FAMILIES = {
|
||||
"fb": "meta", "facebook": "meta", "fbads": "meta",
|
||||
"ig": "meta", "instagram": "meta", "meta": "meta", "metaads": "meta",
|
||||
"google": "google", "googleads": "google", "adwords": "google",
|
||||
"bing": "bing", "bingads": "bing", "microsoftads": "bing",
|
||||
"yandex": "yandex", "yandexads": "yandex", "директ": "yandex",
|
||||
}
|
||||
# UTM 标签 ↔ 平台名别名(家族表之外的零散别名);baidu 恒等仅为可读
|
||||
_UTM_ALIASES = {"tt": "tiktok",
|
||||
"wa": "whatsapp", "g": "google", "baidu": "baidu", "百度": "baidu"}
|
||||
|
||||
|
||||
@@ -94,8 +104,10 @@ def _in_app_name(ua: str) -> str:
|
||||
|
||||
def _canon(name: str) -> str:
|
||||
"""平台名/UTM 标签宽松归一(仅用于冲突判定):小写 → 去协议与路径 → 去 www. 前缀
|
||||
→ 去最后一段域名后缀(chatgpt.com→chatgpt、google.ru→google)→ 别名表 → 仅留
|
||||
字母数字(含汉字)。fb↔Facebook、百度↔baidu 归一后同源,不再误报 conflict。"""
|
||||
→ 去最后一段域名后缀(chatgpt.com→chatgpt、google.ru→google)→ 去空格("meta
|
||||
ads"→"metaads")→ 家族表(fb/ig↔Meta Ads→meta;adwords→google;директ→yandex)
|
||||
→ 别名表 → 仅留字母数字(含汉字)。同广告家族(meta/google/bing/yandex)归一后
|
||||
同源,不再误报 conflict。"""
|
||||
s = (name or "").lower().strip()
|
||||
if "://" in s:
|
||||
s = s.split("://", 1)[1]
|
||||
@@ -104,6 +116,9 @@ def _canon(name: str) -> str:
|
||||
s = s[4:]
|
||||
if "." in s:
|
||||
s = s.rsplit(".", 1)[0]
|
||||
s = re.sub(r"\s+", "", s)
|
||||
if s in _AD_FAMILIES: # 家族命中即返回家族名(已是规范 token)
|
||||
return _AD_FAMILIES[s]
|
||||
s = _UTM_ALIASES.get(s, s)
|
||||
return re.sub(r"[^0-9a-z一-鿿]", "", s)
|
||||
|
||||
|
||||
@@ -145,6 +145,33 @@ def test_utm_chinese_alias_baidu_matches():
|
||||
assert r["detail"].get("utm_source") == "百度"
|
||||
|
||||
|
||||
# ------------------------------------------- 广告家族归一(付费点击参数 ↔ UTM 同家族)
|
||||
def test_fbclid_with_utm_fb_same_meta_family_combined():
|
||||
r = classify(landing_path="/?fbclid=IwAR0", utm={"utm_source": "fb"})
|
||||
assert r["channel"] == "paid_search" and r["platform"] == "Meta Ads"
|
||||
assert r["resolution"] == "resolved" and r["evidence_kind"] == "combined"
|
||||
assert r["detail"].get("utm_source") == "fb" # fb ↔ Meta Ads 同 meta 家族
|
||||
|
||||
|
||||
def test_fbclid_with_utm_ig_combined():
|
||||
r = classify(landing_path="/?fbclid=IwAR0", utm={"utm_source": "ig"})
|
||||
assert r["platform"] == "Meta Ads" # ig 也在 meta 家族
|
||||
assert r["resolution"] != "conflict" and r["evidence_kind"] == "combined"
|
||||
|
||||
|
||||
def test_gclid_with_utm_google_combined():
|
||||
r = classify(landing_path="/offer?gclid=EAIaIQ", utm={"utm_source": "google"})
|
||||
assert r["channel"] == "paid_search" and r["platform"] == "Google Ads"
|
||||
assert r["resolution"] != "conflict" and r["evidence_kind"] == "combined"
|
||||
|
||||
|
||||
def test_fbclid_with_utm_tiktok_still_conflict():
|
||||
r = classify(landing_path="/?fbclid=IwAR0", utm={"utm_source": "tiktok"})
|
||||
assert r["resolution"] == "conflict" # 跨家族(meta vs tiktok)= 真矛盾
|
||||
assert r["detail"]["referrer_evidence"]["platform"] == "Meta Ads"
|
||||
assert r["detail"]["utm_evidence"]["platform"] == "tiktok"
|
||||
|
||||
|
||||
def test_utm_only_is_label_only():
|
||||
r = classify(utm={"utm_source": "newsletter"})
|
||||
assert r["channel"] == "referral" # 未知标签 → 有名 referral,但状态诚实标注
|
||||
|
||||
Reference in New Issue
Block a user