|
@@ -25,6 +25,7 @@ DISPLAY_MULTIPLIER_COLUMN = "传播裂变系数-对T0裂变"
|
|
|
|
|
|
|
|
MINIAPP_FILE = "小程序T15成熟系数_中文_20260712.csv"
|
|
MINIAPP_FILE = "小程序T15成熟系数_中文_20260712.csv"
|
|
|
GZH_FILE = "公众号T15成熟系数_中文_20260712.csv"
|
|
GZH_FILE = "公众号T15成熟系数_中文_20260712.csv"
|
|
|
|
|
+QIWEI_FILE = "企微T15成熟系数_中文_20260712.csv"
|
|
|
FALLBACK_FILE = "T15分级回退系数_中文_20260712.csv"
|
|
FALLBACK_FILE = "T15分级回退系数_中文_20260712.csv"
|
|
|
|
|
|
|
|
MATCH_MINIAPP_EXACT = "miniapp_package_goal_exact"
|
|
MATCH_MINIAPP_EXACT = "miniapp_package_goal_exact"
|
|
@@ -34,8 +35,11 @@ MATCH_MINIAPP_CHANNEL = "miniapp_channel_fallback"
|
|
|
MATCH_GZH_EXACT = "gzh_partner_account_exact"
|
|
MATCH_GZH_EXACT = "gzh_partner_account_exact"
|
|
|
MATCH_GZH_PARTNER = "gzh_partner_fallback"
|
|
MATCH_GZH_PARTNER = "gzh_partner_fallback"
|
|
|
MATCH_GZH_CHANNEL = "gzh_channel_fallback"
|
|
MATCH_GZH_CHANNEL = "gzh_channel_fallback"
|
|
|
-MATCH_QIWEI_REFERENCE = "qiwei_channel_reference_pending"
|
|
|
|
|
|
|
+MATCH_QIWEI_PARTNER = "qiwei_partner_exact"
|
|
|
|
|
+MATCH_QIWEI_REFERENCE = "qiwei_reference_pending"
|
|
|
QIWEI_REFERENCE_MULTIPLIER = 2.5
|
|
QIWEI_REFERENCE_MULTIPLIER = 2.5
|
|
|
|
|
+QIWEI_REFERENCE_VERSION = "qiwei_reference_2p5_v1"
|
|
|
|
|
+QIWEI_REFERENCE_RUN_SUFFIX = "q2p5v1"
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
@dataclass(frozen=True)
|
|
@@ -79,13 +83,16 @@ class FissionMultiplierParameters:
|
|
|
gzh_exact: Mapping[tuple[str, str], float]
|
|
gzh_exact: Mapping[tuple[str, str], float]
|
|
|
gzh_by_partner: Mapping[str, float]
|
|
gzh_by_partner: Mapping[str, float]
|
|
|
gzh_channel: float
|
|
gzh_channel: float
|
|
|
|
|
+ qiwei_by_partner: Mapping[str, float]
|
|
|
miniapp_exact_rows: int
|
|
miniapp_exact_rows: int
|
|
|
miniapp_exact_available_rows: int
|
|
miniapp_exact_available_rows: int
|
|
|
gzh_exact_rows: int
|
|
gzh_exact_rows: int
|
|
|
gzh_exact_available_rows: int
|
|
gzh_exact_available_rows: int
|
|
|
|
|
+ qiwei_exact_rows: int
|
|
|
|
|
+ qiwei_exact_available_rows: int
|
|
|
|
|
|
|
|
def snapshot(self) -> dict[str, object]:
|
|
def snapshot(self) -> dict[str, object]:
|
|
|
- return {
|
|
|
|
|
|
|
+ result: dict[str, object] = {
|
|
|
"version": self.release.version,
|
|
"version": self.release.version,
|
|
|
"cohort_date": self.release.cohort_date,
|
|
"cohort_date": self.release.cohort_date,
|
|
|
"observation_end_date": self.release.observation_end_date,
|
|
"observation_end_date": self.release.observation_end_date,
|
|
@@ -97,6 +104,16 @@ class FissionMultiplierParameters:
|
|
|
"miniapp_channel_multiplier": self.miniapp_channel,
|
|
"miniapp_channel_multiplier": self.miniapp_channel,
|
|
|
"gzh_channel_multiplier": self.gzh_channel,
|
|
"gzh_channel_multiplier": self.gzh_channel,
|
|
|
}
|
|
}
|
|
|
|
|
+ # Keep legacy release hashes stable until a release explicitly carries
|
|
|
|
|
+ # audited WeCom parameters.
|
|
|
|
|
+ if self.qiwei_by_partner:
|
|
|
|
|
+ result.update(
|
|
|
|
|
+ {
|
|
|
|
|
+ "qiwei_exact_rows": self.qiwei_exact_rows,
|
|
|
|
|
+ "qiwei_exact_available_rows": self.qiwei_exact_available_rows,
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ return result
|
|
|
|
|
|
|
|
def match(
|
|
def match(
|
|
|
self,
|
|
self,
|
|
@@ -158,11 +175,18 @@ class FissionMultiplierParameters:
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
if entity_type == "qiwei":
|
|
if entity_type == "qiwei":
|
|
|
|
|
+ partner_key = normalize_name(partner)
|
|
|
|
|
+ if partner_key and partner_key in self.qiwei_by_partner:
|
|
|
|
|
+ return FissionMultiplierMatch(
|
|
|
|
|
+ self.qiwei_by_partner[partner_key],
|
|
|
|
|
+ MATCH_QIWEI_PARTNER,
|
|
|
|
|
+ f"企微合作方精确:{partner_key}",
|
|
|
|
|
+ )
|
|
|
return FissionMultiplierMatch(
|
|
return FissionMultiplierMatch(
|
|
|
QIWEI_REFERENCE_MULTIPLIER,
|
|
QIWEI_REFERENCE_MULTIPLIER,
|
|
|
MATCH_QIWEI_REFERENCE,
|
|
MATCH_QIWEI_REFERENCE,
|
|
|
- f"企微渠道临时参考系数{QIWEI_REFERENCE_MULTIPLIER:g}_待补算:"
|
|
|
|
|
- f"{normalize_name(partner)}",
|
|
|
|
|
|
|
+ f"企微默认参考系数{QIWEI_REFERENCE_MULTIPLIER:g}_待发布合作方参数:"
|
|
|
|
|
+ f"{partner_key}",
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
raise ValueError(f"不支持的ROI实体类型: {entity_type}")
|
|
raise ValueError(f"不支持的ROI实体类型: {entity_type}")
|
|
@@ -205,6 +229,9 @@ def parameter_values(
|
|
|
for partner, multiplier in parameters.gzh_by_partner.items():
|
|
for partner, multiplier in parameters.gzh_by_partner.items():
|
|
|
append("gzh", MATCH_GZH_PARTNER, partner, "", multiplier)
|
|
append("gzh", MATCH_GZH_PARTNER, partner, "", multiplier)
|
|
|
append("gzh", MATCH_GZH_CHANNEL, "", "", parameters.gzh_channel)
|
|
append("gzh", MATCH_GZH_CHANNEL, "", "", parameters.gzh_channel)
|
|
|
|
|
+
|
|
|
|
|
+ for partner, multiplier in parameters.qiwei_by_partner.items():
|
|
|
|
|
+ append("qiwei", MATCH_QIWEI_PARTNER, partner, "", multiplier)
|
|
|
return sorted(
|
|
return sorted(
|
|
|
rows,
|
|
rows,
|
|
|
key=lambda row: (
|
|
key=lambda row: (
|
|
@@ -265,6 +292,19 @@ def parameters_from_database(
|
|
|
raise ValueError(f"数据库传播裂变系数无效: {key}={multiplier}")
|
|
raise ValueError(f"数据库传播裂变系数无效: {key}={multiplier}")
|
|
|
indexed[key] = multiplier
|
|
indexed[key] = multiplier
|
|
|
|
|
|
|
|
|
|
+ unsupported_qiwei_levels = sorted(
|
|
|
|
|
+ {
|
|
|
|
|
+ match_level
|
|
|
|
|
+ for entity_type, match_level, _, _ in indexed
|
|
|
|
|
+ if entity_type == "qiwei" and match_level != MATCH_QIWEI_PARTNER
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ if unsupported_qiwei_levels:
|
|
|
|
|
+ raise ValueError(
|
|
|
|
|
+ "数据库企微传播裂变参数只支持合作方精确匹配: "
|
|
|
|
|
+ + ", ".join(unsupported_qiwei_levels)
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
def values(entity_type: str, level: str) -> dict[tuple[str, str], float]:
|
|
def values(entity_type: str, level: str) -> dict[tuple[str, str], float]:
|
|
|
return {
|
|
return {
|
|
|
(primary, secondary): multiplier
|
|
(primary, secondary): multiplier
|
|
@@ -292,6 +332,18 @@ def parameters_from_database(
|
|
|
key[0]: multiplier
|
|
key[0]: multiplier
|
|
|
for key, multiplier in values("gzh", MATCH_GZH_PARTNER).items()
|
|
for key, multiplier in values("gzh", MATCH_GZH_PARTNER).items()
|
|
|
}
|
|
}
|
|
|
|
|
+ qiwei_partner_values = values("qiwei", MATCH_QIWEI_PARTNER)
|
|
|
|
|
+ if any(secondary for _, secondary in qiwei_partner_values):
|
|
|
|
|
+ raise ValueError("数据库企微合作方参数不能包含次级匹配键")
|
|
|
|
|
+ if any(not primary for primary, _ in qiwei_partner_values):
|
|
|
|
|
+ raise ValueError("数据库企微合作方参数不能包含空合作方键")
|
|
|
|
|
+ qiwei_partner = {
|
|
|
|
|
+ key[0]: multiplier for key, multiplier in qiwei_partner_values.items()
|
|
|
|
|
+ }
|
|
|
|
|
+ qiwei_metadata = {"qiwei_exact_rows", "qiwei_exact_available_rows"}
|
|
|
|
|
+ if qiwei_partner and not qiwei_metadata.issubset(metadata):
|
|
|
|
|
+ missing = sorted(qiwei_metadata - set(metadata))
|
|
|
|
|
+ raise ValueError(f"数据库企微传播裂变参数元数据缺失: {', '.join(missing)}")
|
|
|
result = FissionMultiplierParameters(
|
|
result = FissionMultiplierParameters(
|
|
|
release=release,
|
|
release=release,
|
|
|
miniapp_exact=mini_exact,
|
|
miniapp_exact=mini_exact,
|
|
@@ -301,10 +353,17 @@ def parameters_from_database(
|
|
|
gzh_exact=gzh_exact,
|
|
gzh_exact=gzh_exact,
|
|
|
gzh_by_partner=gzh_partner,
|
|
gzh_by_partner=gzh_partner,
|
|
|
gzh_channel=single("gzh", MATCH_GZH_CHANNEL),
|
|
gzh_channel=single("gzh", MATCH_GZH_CHANNEL),
|
|
|
|
|
+ qiwei_by_partner=qiwei_partner,
|
|
|
miniapp_exact_rows=int(metadata["miniapp_exact_rows"]),
|
|
miniapp_exact_rows=int(metadata["miniapp_exact_rows"]),
|
|
|
miniapp_exact_available_rows=int(metadata["miniapp_exact_available_rows"]),
|
|
miniapp_exact_available_rows=int(metadata["miniapp_exact_available_rows"]),
|
|
|
gzh_exact_rows=int(metadata["gzh_exact_rows"]),
|
|
gzh_exact_rows=int(metadata["gzh_exact_rows"]),
|
|
|
gzh_exact_available_rows=int(metadata["gzh_exact_available_rows"]),
|
|
gzh_exact_available_rows=int(metadata["gzh_exact_available_rows"]),
|
|
|
|
|
+ qiwei_exact_rows=(
|
|
|
|
|
+ int(metadata["qiwei_exact_rows"]) if qiwei_partner else 0
|
|
|
|
|
+ ),
|
|
|
|
|
+ qiwei_exact_available_rows=(
|
|
|
|
|
+ int(metadata["qiwei_exact_available_rows"]) if qiwei_partner else 0
|
|
|
|
|
+ ),
|
|
|
)
|
|
)
|
|
|
expected_hash = str(release_row["content_sha256"])
|
|
expected_hash = str(release_row["content_sha256"])
|
|
|
if parameter_content_sha256(result) != expected_hash:
|
|
if parameter_content_sha256(result) != expected_hash:
|
|
@@ -463,6 +522,15 @@ def load_fission_multiplier_parameters(
|
|
|
label="公众号",
|
|
label="公众号",
|
|
|
expected_sha256=release.file_sha256[GZH_FILE],
|
|
expected_sha256=release.file_sha256[GZH_FILE],
|
|
|
)
|
|
)
|
|
|
|
|
+ has_qiwei_file = QIWEI_FILE in release.file_sha256
|
|
|
|
|
+ qiwei = None
|
|
|
|
|
+ if has_qiwei_file:
|
|
|
|
|
+ qiwei = _read_parameter_file(
|
|
|
|
|
+ directory / QIWEI_FILE,
|
|
|
|
|
+ required=exact_required | {"合作方"},
|
|
|
|
|
+ label="企微",
|
|
|
|
|
+ expected_sha256=release.file_sha256[QIWEI_FILE],
|
|
|
|
|
+ )
|
|
|
fallback = _read_parameter_file(
|
|
fallback = _read_parameter_file(
|
|
|
directory / FALLBACK_FILE,
|
|
directory / FALLBACK_FILE,
|
|
|
required={
|
|
required={
|
|
@@ -483,9 +551,14 @@ def load_fission_multiplier_parameters(
|
|
|
)
|
|
)
|
|
|
miniapp = _validated_multipliers(miniapp, label="小程序")
|
|
miniapp = _validated_multipliers(miniapp, label="小程序")
|
|
|
gzh = _validated_multipliers(gzh, label="公众号")
|
|
gzh = _validated_multipliers(gzh, label="公众号")
|
|
|
|
|
+ if qiwei is not None:
|
|
|
|
|
+ qiwei = _validated_multipliers(qiwei, label="企微")
|
|
|
fallback = _validated_multipliers(fallback, label="分级回退")
|
|
fallback = _validated_multipliers(fallback, label="分级回退")
|
|
|
|
|
|
|
|
- for label, frame in (("小程序", miniapp), ("公众号", gzh)):
|
|
|
|
|
|
|
+ exact_frames = [("小程序", miniapp), ("公众号", gzh)]
|
|
|
|
|
+ if qiwei is not None:
|
|
|
|
|
+ exact_frames.append(("企微", qiwei))
|
|
|
|
|
+ for label, frame in exact_frames:
|
|
|
dates = {str(value) for value in frame["首层投放日期"].dropna()}
|
|
dates = {str(value) for value in frame["首层投放日期"].dropna()}
|
|
|
if dates != {release.cohort_date}:
|
|
if dates != {release.cohort_date}:
|
|
|
raise ValueError(
|
|
raise ValueError(
|
|
@@ -495,6 +568,16 @@ def load_fission_multiplier_parameters(
|
|
|
mini_package_rows = fallback[fallback["参数层级"].eq("小程序人群包回退")]
|
|
mini_package_rows = fallback[fallback["参数层级"].eq("小程序人群包回退")]
|
|
|
mini_goal_rows = fallback[fallback["参数层级"].eq("小程序转化目标回退")]
|
|
mini_goal_rows = fallback[fallback["参数层级"].eq("小程序转化目标回退")]
|
|
|
gzh_partner_rows = fallback[fallback["参数层级"].eq("公众号合作方回退")]
|
|
gzh_partner_rows = fallback[fallback["参数层级"].eq("公众号合作方回退")]
|
|
|
|
|
+ qiwei_by_partner = (
|
|
|
|
|
+ _mapping(
|
|
|
|
|
+ qiwei,
|
|
|
|
|
+ ["合作方"],
|
|
|
|
|
+ normalizers=[normalize_name],
|
|
|
|
|
+ label="企微合作方参数",
|
|
|
|
|
+ )
|
|
|
|
|
+ if qiwei is not None
|
|
|
|
|
+ else {}
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
return FissionMultiplierParameters(
|
|
return FissionMultiplierParameters(
|
|
|
release=release,
|
|
release=release,
|
|
@@ -534,10 +617,17 @@ def load_fission_multiplier_parameters(
|
|
|
gzh_channel=_single_channel_multiplier(
|
|
gzh_channel=_single_channel_multiplier(
|
|
|
fallback, "公众号渠道回退"
|
|
fallback, "公众号渠道回退"
|
|
|
),
|
|
),
|
|
|
|
|
+ qiwei_by_partner=qiwei_by_partner,
|
|
|
miniapp_exact_rows=len(miniapp),
|
|
miniapp_exact_rows=len(miniapp),
|
|
|
miniapp_exact_available_rows=int(miniapp["样本状态"].eq("可用").sum()),
|
|
miniapp_exact_available_rows=int(miniapp["样本状态"].eq("可用").sum()),
|
|
|
gzh_exact_rows=len(gzh),
|
|
gzh_exact_rows=len(gzh),
|
|
|
gzh_exact_available_rows=int(gzh["样本状态"].eq("可用").sum()),
|
|
gzh_exact_available_rows=int(gzh["样本状态"].eq("可用").sum()),
|
|
|
|
|
+ qiwei_exact_rows=len(qiwei) if qiwei is not None else 0,
|
|
|
|
|
+ qiwei_exact_available_rows=(
|
|
|
|
|
+ int(qiwei["样本状态"].eq("可用").sum())
|
|
|
|
|
+ if qiwei is not None
|
|
|
|
|
+ else 0
|
|
|
|
|
+ ),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@@ -572,17 +662,24 @@ def apply_fission_multiplier(
|
|
|
result["传播裂变参数版本"] = parameters.release.version
|
|
result["传播裂变参数版本"] = parameters.release.version
|
|
|
result["传播裂变参数cohort日期"] = parameters.release.cohort_date
|
|
result["传播裂变参数cohort日期"] = parameters.release.cohort_date
|
|
|
qiwei = result["entity_type"].eq("qiwei")
|
|
qiwei = result["entity_type"].eq("qiwei")
|
|
|
- result["传播裂变参数状态"] = "正式参数"
|
|
|
|
|
- result.loc[qiwei, "传播裂变参数状态"] = (
|
|
|
|
|
- f"临时参考系数{QIWEI_REFERENCE_MULTIPLIER:g}_待分渠道补算"
|
|
|
|
|
|
|
+ qiwei_reference = qiwei & result["传播裂变系数匹配层级"].eq(
|
|
|
|
|
+ MATCH_QIWEI_REFERENCE
|
|
|
)
|
|
)
|
|
|
|
|
+ qiwei_formal = qiwei & ~qiwei_reference
|
|
|
|
|
+ result["传播裂变参数状态"] = "正式参数"
|
|
|
result["调控参与状态"] = "参与阈值和调控"
|
|
result["调控参与状态"] = "参与阈值和调控"
|
|
|
result.loc[qiwei, "调控参与状态"] = "仅展示_不进入阈值和调控"
|
|
result.loc[qiwei, "调控参与状态"] = "仅展示_不进入阈值和调控"
|
|
|
result["ROI计算口径"] = (
|
|
result["ROI计算口径"] = (
|
|
|
"首层实际效率收入+T0实际裂变收入+预测T1-T15裂变收入"
|
|
"首层实际效率收入+T0实际裂变收入+预测T1-T15裂变收入"
|
|
|
)
|
|
)
|
|
|
- result.loc[qiwei, "ROI计算口径"] = (
|
|
|
|
|
|
|
+ result.loc[qiwei_formal, "传播裂变参数状态"] = "企微正式参数_仅展示"
|
|
|
|
|
+ result.loc[qiwei_reference, "传播裂变参数版本"] = QIWEI_REFERENCE_VERSION
|
|
|
|
|
+ result.loc[qiwei_reference, "传播裂变参数cohort日期"] = ""
|
|
|
|
|
+ result.loc[qiwei_reference, "传播裂变参数状态"] = (
|
|
|
|
|
+ f"默认参考系数{QIWEI_REFERENCE_MULTIPLIER:g}_待发布合作方参数"
|
|
|
|
|
+ )
|
|
|
|
|
+ result.loc[qiwei_reference, "ROI计算口径"] = (
|
|
|
"首层实际效率收入+T0实际裂变收入+预测T1-T15裂变收入_"
|
|
"首层实际效率收入+T0实际裂变收入+预测T1-T15裂变收入_"
|
|
|
- f"传播裂变系数临时取{QIWEI_REFERENCE_MULTIPLIER:g}"
|
|
|
|
|
|
|
+ f"传播裂变系数默认取{QIWEI_REFERENCE_MULTIPLIER:g}"
|
|
|
)
|
|
)
|
|
|
return result
|
|
return result
|