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]")
|
_CTRL_CHARS = re.compile(r"[\x00-\x1f\x7f]")
|
||||||
_GENERIC_TLDS = ("com",) # 引擎 host 落在这些 TLD 时不作为国家 variant 显示
|
_GENERIC_TLDS = ("com",) # 引擎 host 落在这些 TLD 时不作为国家 variant 显示
|
||||||
|
|
||||||
# UTM 标签 ↔ 平台名别名(冲突判定前归一):缩写/中文品牌名 → 标准名;baidu 恒等仅为可读
|
# 广告平台家族归一表(冲突判定前):同一家族的各种写法(缩写/全称/带 ads 后缀/
|
||||||
_UTM_ALIASES = {"fb": "facebook", "ig": "instagram", "tt": "tiktok",
|
# 中文俄文别称)→ 家族名。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"}
|
"wa": "whatsapp", "g": "google", "baidu": "baidu", "百度": "baidu"}
|
||||||
|
|
||||||
|
|
||||||
@@ -94,8 +104,10 @@ def _in_app_name(ua: str) -> str:
|
|||||||
|
|
||||||
def _canon(name: str) -> str:
|
def _canon(name: str) -> str:
|
||||||
"""平台名/UTM 标签宽松归一(仅用于冲突判定):小写 → 去协议与路径 → 去 www. 前缀
|
"""平台名/UTM 标签宽松归一(仅用于冲突判定):小写 → 去协议与路径 → 去 www. 前缀
|
||||||
→ 去最后一段域名后缀(chatgpt.com→chatgpt、google.ru→google)→ 别名表 → 仅留
|
→ 去最后一段域名后缀(chatgpt.com→chatgpt、google.ru→google)→ 去空格("meta
|
||||||
字母数字(含汉字)。fb↔Facebook、百度↔baidu 归一后同源,不再误报 conflict。"""
|
ads"→"metaads")→ 家族表(fb/ig↔Meta Ads→meta;adwords→google;директ→yandex)
|
||||||
|
→ 别名表 → 仅留字母数字(含汉字)。同广告家族(meta/google/bing/yandex)归一后
|
||||||
|
同源,不再误报 conflict。"""
|
||||||
s = (name or "").lower().strip()
|
s = (name or "").lower().strip()
|
||||||
if "://" in s:
|
if "://" in s:
|
||||||
s = s.split("://", 1)[1]
|
s = s.split("://", 1)[1]
|
||||||
@@ -104,6 +116,9 @@ def _canon(name: str) -> str:
|
|||||||
s = s[4:]
|
s = s[4:]
|
||||||
if "." in s:
|
if "." in s:
|
||||||
s = s.rsplit(".", 1)[0]
|
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)
|
s = _UTM_ALIASES.get(s, s)
|
||||||
return re.sub(r"[^0-9a-z一-鿿]", "", 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") == "百度"
|
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():
|
def test_utm_only_is_label_only():
|
||||||
r = classify(utm={"utm_source": "newsletter"})
|
r = classify(utm={"utm_source": "newsletter"})
|
||||||
assert r["channel"] == "referral" # 未知标签 → 有名 referral,但状态诚实标注
|
assert r["channel"] == "referral" # 未知标签 → 有名 referral,但状态诚实标注
|
||||||
|
|||||||
Reference in New Issue
Block a user