|
@@ -17,11 +17,14 @@ from typing import Mapping
|
|
|
import pandas as pd
|
|
import pandas as pd
|
|
|
|
|
|
|
|
|
|
|
|
|
-DEFAULT_FISSION_PARAMETER_VERSION = "20260712_A0-A15_v1"
|
|
|
|
|
|
|
+LEGACY_FISSION_PARAMETER_VERSION = "20260712_A0-A15_v1"
|
|
|
|
|
+DEFAULT_FISSION_PARAMETER_VERSION = "20260712_A0-A15_v2"
|
|
|
FISSION_HORIZON_DAYS = 15
|
|
FISSION_HORIZON_DAYS = 15
|
|
|
PARAMETER_ROOT = Path(__file__).resolve().parent / "data" / "fission_multiplier"
|
|
PARAMETER_ROOT = Path(__file__).resolve().parent / "data" / "fission_multiplier"
|
|
|
SOURCE_MULTIPLIER_COLUMN = "T15成熟系数(相对T0)"
|
|
SOURCE_MULTIPLIER_COLUMN = "T15成熟系数(相对T0)"
|
|
|
DISPLAY_MULTIPLIER_COLUMN = "传播裂变系数-对T0裂变"
|
|
DISPLAY_MULTIPLIER_COLUMN = "传播裂变系数-对T0裂变"
|
|
|
|
|
+SOURCE_TOTAL_TO_FIRST_COLUMN = "每首层累计裂变活跃人次"
|
|
|
|
|
+DISPLAY_TOTAL_TO_FIRST_COLUMN = "裂变系数-总裂变UV/首层UV"
|
|
|
|
|
|
|
|
MINIAPP_FILE = "小程序T15成熟系数_中文_20260712.csv"
|
|
MINIAPP_FILE = "小程序T15成熟系数_中文_20260712.csv"
|
|
|
GZH_FILE = "公众号T15成熟系数_中文_20260712.csv"
|
|
GZH_FILE = "公众号T15成熟系数_中文_20260712.csv"
|
|
@@ -45,26 +48,44 @@ class ParameterRelease:
|
|
|
observation_end_date: str
|
|
observation_end_date: str
|
|
|
run_suffix: str
|
|
run_suffix: str
|
|
|
file_sha256: Mapping[str, str]
|
|
file_sha256: Mapping[str, str]
|
|
|
|
|
+ source_version: str
|
|
|
|
|
+ includes_total_to_first: bool
|
|
|
|
|
|
|
|
|
|
|
|
|
RELEASES: Mapping[str, ParameterRelease] = {
|
|
RELEASES: Mapping[str, ParameterRelease] = {
|
|
|
|
|
+ LEGACY_FISSION_PARAMETER_VERSION: ParameterRelease(
|
|
|
|
|
+ version=LEGACY_FISSION_PARAMETER_VERSION,
|
|
|
|
|
+ cohort_date="20260712",
|
|
|
|
|
+ observation_end_date="20260727",
|
|
|
|
|
+ run_suffix="f0712v1",
|
|
|
|
|
+ file_sha256={
|
|
|
|
|
+ MINIAPP_FILE: "9358408ad4957bbd22f689f8bbf66b7abe765f210e5af444d11e329815fee6a4",
|
|
|
|
|
+ GZH_FILE: "1f6f17b1403aa9b56a33195da3c324fbe06cbb714aa2ebabcdbceb891aa7a951",
|
|
|
|
|
+ FALLBACK_FILE: "6e4d517595ec2632dc54a65000abe94000b5313e77420547759b2419314d60ac",
|
|
|
|
|
+ },
|
|
|
|
|
+ source_version=LEGACY_FISSION_PARAMETER_VERSION,
|
|
|
|
|
+ includes_total_to_first=False,
|
|
|
|
|
+ ),
|
|
|
DEFAULT_FISSION_PARAMETER_VERSION: ParameterRelease(
|
|
DEFAULT_FISSION_PARAMETER_VERSION: ParameterRelease(
|
|
|
version=DEFAULT_FISSION_PARAMETER_VERSION,
|
|
version=DEFAULT_FISSION_PARAMETER_VERSION,
|
|
|
cohort_date="20260712",
|
|
cohort_date="20260712",
|
|
|
observation_end_date="20260727",
|
|
observation_end_date="20260727",
|
|
|
- run_suffix="f0712v1",
|
|
|
|
|
|
|
+ run_suffix="f0712v2",
|
|
|
file_sha256={
|
|
file_sha256={
|
|
|
MINIAPP_FILE: "9358408ad4957bbd22f689f8bbf66b7abe765f210e5af444d11e329815fee6a4",
|
|
MINIAPP_FILE: "9358408ad4957bbd22f689f8bbf66b7abe765f210e5af444d11e329815fee6a4",
|
|
|
GZH_FILE: "1f6f17b1403aa9b56a33195da3c324fbe06cbb714aa2ebabcdbceb891aa7a951",
|
|
GZH_FILE: "1f6f17b1403aa9b56a33195da3c324fbe06cbb714aa2ebabcdbceb891aa7a951",
|
|
|
FALLBACK_FILE: "6e4d517595ec2632dc54a65000abe94000b5313e77420547759b2419314d60ac",
|
|
FALLBACK_FILE: "6e4d517595ec2632dc54a65000abe94000b5313e77420547759b2419314d60ac",
|
|
|
},
|
|
},
|
|
|
- )
|
|
|
|
|
|
|
+ source_version=LEGACY_FISSION_PARAMETER_VERSION,
|
|
|
|
|
+ includes_total_to_first=True,
|
|
|
|
|
+ ),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
@dataclass(frozen=True)
|
|
|
class FissionMultiplierMatch:
|
|
class FissionMultiplierMatch:
|
|
|
multiplier: float
|
|
multiplier: float
|
|
|
|
|
+ multiplier_vs_first: float | None
|
|
|
match_level: str
|
|
match_level: str
|
|
|
source: str
|
|
source: str
|
|
|
|
|
|
|
@@ -79,13 +100,14 @@ 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
|
|
|
|
|
+ total_to_first: Mapping[tuple[str, str, str, 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
|
|
|
|
|
|
|
|
def snapshot(self) -> dict[str, object]:
|
|
def snapshot(self) -> dict[str, object]:
|
|
|
- return {
|
|
|
|
|
|
|
+ snapshot = {
|
|
|
"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 +119,28 @@ 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,
|
|
|
}
|
|
}
|
|
|
|
|
+ if self.release.includes_total_to_first:
|
|
|
|
|
+ snapshot["includes_total_to_first"] = True
|
|
|
|
|
+ snapshot["source_version"] = self.release.source_version
|
|
|
|
|
+ return snapshot
|
|
|
|
|
+
|
|
|
|
|
+ def _match_result(
|
|
|
|
|
+ self,
|
|
|
|
|
+ multiplier: float,
|
|
|
|
|
+ entity_type: str,
|
|
|
|
|
+ match_level: str,
|
|
|
|
|
+ key_primary: str,
|
|
|
|
|
+ key_secondary: str,
|
|
|
|
|
+ source: str,
|
|
|
|
|
+ ) -> FissionMultiplierMatch:
|
|
|
|
|
+ return FissionMultiplierMatch(
|
|
|
|
|
+ multiplier=multiplier,
|
|
|
|
|
+ multiplier_vs_first=self.total_to_first.get(
|
|
|
|
|
+ (entity_type, match_level, key_primary, key_secondary)
|
|
|
|
|
+ ),
|
|
|
|
|
+ match_level=match_level,
|
|
|
|
|
+ source=source,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
def match(
|
|
def match(
|
|
|
self,
|
|
self,
|
|
@@ -112,26 +156,38 @@ class FissionMultiplierParameters:
|
|
|
goal_key = normalize_name(optimize_goal)
|
|
goal_key = normalize_name(optimize_goal)
|
|
|
exact_key = (package_key, goal_key)
|
|
exact_key = (package_key, goal_key)
|
|
|
if goal_key and exact_key in self.miniapp_exact:
|
|
if goal_key and exact_key in self.miniapp_exact:
|
|
|
- return FissionMultiplierMatch(
|
|
|
|
|
|
|
+ return self._match_result(
|
|
|
self.miniapp_exact[exact_key],
|
|
self.miniapp_exact[exact_key],
|
|
|
|
|
+ "self",
|
|
|
MATCH_MINIAPP_EXACT,
|
|
MATCH_MINIAPP_EXACT,
|
|
|
|
|
+ package_key,
|
|
|
|
|
+ goal_key,
|
|
|
f"人群包+转化目标精确:{package_key}|{goal_key}",
|
|
f"人群包+转化目标精确:{package_key}|{goal_key}",
|
|
|
)
|
|
)
|
|
|
if package_key in self.miniapp_by_package:
|
|
if package_key in self.miniapp_by_package:
|
|
|
- return FissionMultiplierMatch(
|
|
|
|
|
|
|
+ return self._match_result(
|
|
|
self.miniapp_by_package[package_key],
|
|
self.miniapp_by_package[package_key],
|
|
|
|
|
+ "self",
|
|
|
MATCH_MINIAPP_PACKAGE,
|
|
MATCH_MINIAPP_PACKAGE,
|
|
|
|
|
+ package_key,
|
|
|
|
|
+ "",
|
|
|
f"人群包回退:{package_key}",
|
|
f"人群包回退:{package_key}",
|
|
|
)
|
|
)
|
|
|
if goal_key and goal_key in self.miniapp_by_goal:
|
|
if goal_key and goal_key in self.miniapp_by_goal:
|
|
|
- return FissionMultiplierMatch(
|
|
|
|
|
|
|
+ return self._match_result(
|
|
|
self.miniapp_by_goal[goal_key],
|
|
self.miniapp_by_goal[goal_key],
|
|
|
|
|
+ "self",
|
|
|
MATCH_MINIAPP_GOAL,
|
|
MATCH_MINIAPP_GOAL,
|
|
|
|
|
+ goal_key,
|
|
|
|
|
+ "",
|
|
|
f"转化目标回退:{goal_key}",
|
|
f"转化目标回退:{goal_key}",
|
|
|
)
|
|
)
|
|
|
- return FissionMultiplierMatch(
|
|
|
|
|
|
|
+ return self._match_result(
|
|
|
self.miniapp_channel,
|
|
self.miniapp_channel,
|
|
|
|
|
+ "self",
|
|
|
MATCH_MINIAPP_CHANNEL,
|
|
MATCH_MINIAPP_CHANNEL,
|
|
|
|
|
+ "",
|
|
|
|
|
+ "",
|
|
|
"小程序渠道回退",
|
|
"小程序渠道回退",
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -140,26 +196,36 @@ class FissionMultiplierParameters:
|
|
|
account_key = normalize_name(official_account)
|
|
account_key = normalize_name(official_account)
|
|
|
exact_key = (partner_key, account_key)
|
|
exact_key = (partner_key, account_key)
|
|
|
if partner_key and account_key and exact_key in self.gzh_exact:
|
|
if partner_key and account_key and exact_key in self.gzh_exact:
|
|
|
- return FissionMultiplierMatch(
|
|
|
|
|
|
|
+ return self._match_result(
|
|
|
self.gzh_exact[exact_key],
|
|
self.gzh_exact[exact_key],
|
|
|
|
|
+ "gzh",
|
|
|
MATCH_GZH_EXACT,
|
|
MATCH_GZH_EXACT,
|
|
|
|
|
+ partner_key,
|
|
|
|
|
+ account_key,
|
|
|
f"合作方+公众号精确:{partner_key}|{account_key}",
|
|
f"合作方+公众号精确:{partner_key}|{account_key}",
|
|
|
)
|
|
)
|
|
|
if partner_key in self.gzh_by_partner:
|
|
if partner_key in self.gzh_by_partner:
|
|
|
- return FissionMultiplierMatch(
|
|
|
|
|
|
|
+ return self._match_result(
|
|
|
self.gzh_by_partner[partner_key],
|
|
self.gzh_by_partner[partner_key],
|
|
|
|
|
+ "gzh",
|
|
|
MATCH_GZH_PARTNER,
|
|
MATCH_GZH_PARTNER,
|
|
|
|
|
+ partner_key,
|
|
|
|
|
+ "",
|
|
|
f"合作方回退:{partner_key}",
|
|
f"合作方回退:{partner_key}",
|
|
|
)
|
|
)
|
|
|
- return FissionMultiplierMatch(
|
|
|
|
|
|
|
+ return self._match_result(
|
|
|
self.gzh_channel,
|
|
self.gzh_channel,
|
|
|
|
|
+ "gzh",
|
|
|
MATCH_GZH_CHANNEL,
|
|
MATCH_GZH_CHANNEL,
|
|
|
|
|
+ "",
|
|
|
|
|
+ "",
|
|
|
"公众号渠道回退",
|
|
"公众号渠道回退",
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
if entity_type == "qiwei":
|
|
if entity_type == "qiwei":
|
|
|
return FissionMultiplierMatch(
|
|
return FissionMultiplierMatch(
|
|
|
QIWEI_REFERENCE_MULTIPLIER,
|
|
QIWEI_REFERENCE_MULTIPLIER,
|
|
|
|
|
+ None,
|
|
|
MATCH_QIWEI_REFERENCE,
|
|
MATCH_QIWEI_REFERENCE,
|
|
|
f"企微渠道临时参考系数{QIWEI_REFERENCE_MULTIPLIER:g}_待补算:"
|
|
f"企微渠道临时参考系数{QIWEI_REFERENCE_MULTIPLIER:g}_待补算:"
|
|
|
f"{normalize_name(partner)}",
|
|
f"{normalize_name(partner)}",
|
|
@@ -182,15 +248,20 @@ def parameter_values(
|
|
|
key_secondary: str,
|
|
key_secondary: str,
|
|
|
multiplier: float,
|
|
multiplier: float,
|
|
|
) -> None:
|
|
) -> None:
|
|
|
- rows.append(
|
|
|
|
|
- {
|
|
|
|
|
|
|
+ row = {
|
|
|
"entity_type": entity_type,
|
|
"entity_type": entity_type,
|
|
|
"match_level": match_level,
|
|
"match_level": match_level,
|
|
|
"key_primary": key_primary,
|
|
"key_primary": key_primary,
|
|
|
"key_secondary": key_secondary,
|
|
"key_secondary": key_secondary,
|
|
|
"multiplier": float(multiplier),
|
|
"multiplier": float(multiplier),
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ if parameters.release.includes_total_to_first:
|
|
|
|
|
+ key = (entity_type, match_level, key_primary, key_secondary)
|
|
|
|
|
+ multiplier_vs_first = parameters.total_to_first.get(key)
|
|
|
|
|
+ if multiplier_vs_first is None:
|
|
|
|
|
+ raise ValueError(f"传播裂变系数缺少总裂变UV/首层UV参数: {key}")
|
|
|
|
|
+ row["multiplier_vs_first"] = float(multiplier_vs_first)
|
|
|
|
|
+ rows.append(row)
|
|
|
|
|
|
|
|
for (package, goal), multiplier in parameters.miniapp_exact.items():
|
|
for (package, goal), multiplier in parameters.miniapp_exact.items():
|
|
|
append("self", MATCH_MINIAPP_EXACT, package, goal, multiplier)
|
|
append("self", MATCH_MINIAPP_EXACT, package, goal, multiplier)
|
|
@@ -241,6 +312,7 @@ def parameters_from_database(
|
|
|
|
|
|
|
|
metadata = json.loads(str(release_row.get("metadata_json") or "{}"))
|
|
metadata = json.loads(str(release_row.get("metadata_json") or "{}"))
|
|
|
file_sha256 = metadata.get("file_sha256") or {}
|
|
file_sha256 = metadata.get("file_sha256") or {}
|
|
|
|
|
+ registered_release = RELEASES.get(str(release_row["version"]))
|
|
|
release = ParameterRelease(
|
|
release = ParameterRelease(
|
|
|
version=str(release_row["version"]),
|
|
version=str(release_row["version"]),
|
|
|
cohort_date=str(release_row["cohort_date"]).replace("-", ""),
|
|
cohort_date=str(release_row["cohort_date"]).replace("-", ""),
|
|
@@ -249,8 +321,17 @@ def parameters_from_database(
|
|
|
),
|
|
),
|
|
|
run_suffix=str(release_row["run_suffix"]),
|
|
run_suffix=str(release_row["run_suffix"]),
|
|
|
file_sha256=file_sha256,
|
|
file_sha256=file_sha256,
|
|
|
|
|
+ source_version=str(
|
|
|
|
|
+ metadata.get("source_version")
|
|
|
|
|
+ or (registered_release.source_version if registered_release else release_row["version"])
|
|
|
|
|
+ ),
|
|
|
|
|
+ includes_total_to_first=bool(
|
|
|
|
|
+ metadata.get("includes_total_to_first")
|
|
|
|
|
+ or (registered_release.includes_total_to_first if registered_release else False)
|
|
|
|
|
+ ),
|
|
|
)
|
|
)
|
|
|
indexed: dict[tuple[str, str, str, str], float] = {}
|
|
indexed: dict[tuple[str, str, str, str], float] = {}
|
|
|
|
|
+ total_to_first: dict[tuple[str, str, str, str], float] = {}
|
|
|
for row in value_rows:
|
|
for row in value_rows:
|
|
|
key = (
|
|
key = (
|
|
|
str(row["entity_type"]),
|
|
str(row["entity_type"]),
|
|
@@ -264,6 +345,16 @@ def parameters_from_database(
|
|
|
if not math.isfinite(multiplier) or multiplier < 1:
|
|
if not math.isfinite(multiplier) or multiplier < 1:
|
|
|
raise ValueError(f"数据库传播裂变系数无效: {key}={multiplier}")
|
|
raise ValueError(f"数据库传播裂变系数无效: {key}={multiplier}")
|
|
|
indexed[key] = multiplier
|
|
indexed[key] = multiplier
|
|
|
|
|
+ raw_vs_first = row.get("multiplier_vs_first")
|
|
|
|
|
+ if raw_vs_first is not None:
|
|
|
|
|
+ multiplier_vs_first = float(raw_vs_first)
|
|
|
|
|
+ if not math.isfinite(multiplier_vs_first) or multiplier_vs_first < 0:
|
|
|
|
|
+ raise ValueError(
|
|
|
|
|
+ f"数据库总裂变UV/首层UV系数无效: {key}={multiplier_vs_first}"
|
|
|
|
|
+ )
|
|
|
|
|
+ total_to_first[key] = multiplier_vs_first
|
|
|
|
|
+ if release.includes_total_to_first and set(total_to_first) != set(indexed):
|
|
|
|
|
+ raise ValueError("数据库总裂变UV/首层UV参数与T0参数匹配键不一致")
|
|
|
|
|
|
|
|
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 {
|
|
@@ -301,6 +392,7 @@ 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),
|
|
|
|
|
+ total_to_first=total_to_first,
|
|
|
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"]),
|
|
@@ -366,6 +458,7 @@ def _validated_multipliers(
|
|
|
"A0-A15累计裂变活跃人次",
|
|
"A0-A15累计裂变活跃人次",
|
|
|
"T1-T15相对T0尾部系数",
|
|
"T1-T15相对T0尾部系数",
|
|
|
SOURCE_MULTIPLIER_COLUMN,
|
|
SOURCE_MULTIPLIER_COLUMN,
|
|
|
|
|
+ SOURCE_TOTAL_TO_FIRST_COLUMN,
|
|
|
]
|
|
]
|
|
|
for column in numeric_columns:
|
|
for column in numeric_columns:
|
|
|
if column in result:
|
|
if column in result:
|
|
@@ -391,6 +484,21 @@ def _validated_multipliers(
|
|
|
if bool(tail_errors.gt(1e-9).any()):
|
|
if bool(tail_errors.gt(1e-9).any()):
|
|
|
raise ValueError(f"{label}参数不满足尾部系数=传播裂变系数-1")
|
|
raise ValueError(f"{label}参数不满足尾部系数=传播裂变系数-1")
|
|
|
|
|
|
|
|
|
|
+ expected_vs_first = (
|
|
|
|
|
+ result["A0-A15累计裂变活跃人次"] / result["首层UV"]
|
|
|
|
|
+ ).where(result["首层UV"].gt(0))
|
|
|
|
|
+ if SOURCE_TOTAL_TO_FIRST_COLUMN in frame:
|
|
|
|
|
+ checkable_vs_first = result["首层UV"].gt(0) & result[
|
|
|
|
|
+ SOURCE_TOTAL_TO_FIRST_COLUMN
|
|
|
|
|
+ ].notna()
|
|
|
|
|
+ errors_vs_first = (
|
|
|
|
|
+ result.loc[checkable_vs_first, SOURCE_TOTAL_TO_FIRST_COLUMN]
|
|
|
|
|
+ - expected_vs_first[checkable_vs_first]
|
|
|
|
|
+ ).abs()
|
|
|
|
|
+ if bool(errors_vs_first.gt(1e-9).any()):
|
|
|
|
|
+ raise ValueError(f"{label}参数不满足累计活跃人次/首层UV恒等式")
|
|
|
|
|
+ result[SOURCE_TOTAL_TO_FIRST_COLUMN] = expected_vs_first
|
|
|
|
|
+
|
|
|
available = result[result["样本状态"].eq("可用")].copy()
|
|
available = result[result["样本状态"].eq("可用")].copy()
|
|
|
valid = available[SOURCE_MULTIPLIER_COLUMN].map(
|
|
valid = available[SOURCE_MULTIPLIER_COLUMN].map(
|
|
|
lambda value: bool(pd.notna(value) and math.isfinite(float(value)) and value >= 1)
|
|
lambda value: bool(pd.notna(value) and math.isfinite(float(value)) and value >= 1)
|
|
@@ -406,6 +514,7 @@ def _mapping(
|
|
|
*,
|
|
*,
|
|
|
normalizers: list,
|
|
normalizers: list,
|
|
|
label: str,
|
|
label: str,
|
|
|
|
|
+ value_column: str = SOURCE_MULTIPLIER_COLUMN,
|
|
|
) -> dict:
|
|
) -> dict:
|
|
|
available = frame[frame["样本状态"].eq("可用")].copy()
|
|
available = frame[frame["样本状态"].eq("可用")].copy()
|
|
|
keys = [
|
|
keys = [
|
|
@@ -416,19 +525,23 @@ def _mapping(
|
|
|
raise ValueError(f"{label}存在空匹配键")
|
|
raise ValueError(f"{label}存在空匹配键")
|
|
|
if len(keys) != len(set(keys)):
|
|
if len(keys) != len(set(keys)):
|
|
|
raise ValueError(f"{label}存在重复匹配键")
|
|
raise ValueError(f"{label}存在重复匹配键")
|
|
|
- values = available[SOURCE_MULTIPLIER_COLUMN].astype(float).tolist()
|
|
|
|
|
|
|
+ values = available[value_column].astype(float).tolist()
|
|
|
if len(key_columns) == 1:
|
|
if len(key_columns) == 1:
|
|
|
return {key[0]: value for key, value in zip(keys, values)}
|
|
return {key[0]: value for key, value in zip(keys, values)}
|
|
|
return dict(zip(keys, values))
|
|
return dict(zip(keys, values))
|
|
|
|
|
|
|
|
|
|
|
|
|
-def _single_channel_multiplier(frame: pd.DataFrame, level: str) -> float:
|
|
|
|
|
|
|
+def _single_channel_multiplier(
|
|
|
|
|
+ frame: pd.DataFrame,
|
|
|
|
|
+ level: str,
|
|
|
|
|
+ value_column: str = SOURCE_MULTIPLIER_COLUMN,
|
|
|
|
|
+) -> float:
|
|
|
rows = frame[
|
|
rows = frame[
|
|
|
frame["参数层级"].eq(level) & frame["样本状态"].eq("可用")
|
|
frame["参数层级"].eq(level) & frame["样本状态"].eq("可用")
|
|
|
]
|
|
]
|
|
|
if len(rows) != 1:
|
|
if len(rows) != 1:
|
|
|
raise ValueError(f"{level}必须且只能有一条可用参数,实际为{len(rows)}")
|
|
raise ValueError(f"{level}必须且只能有一条可用参数,实际为{len(rows)}")
|
|
|
- return float(rows.iloc[0][SOURCE_MULTIPLIER_COLUMN])
|
|
|
|
|
|
|
+ return float(rows.iloc[0][value_column])
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_fission_multiplier_parameters(
|
|
def load_fission_multiplier_parameters(
|
|
@@ -440,7 +553,7 @@ def load_fission_multiplier_parameters(
|
|
|
release = RELEASES[version]
|
|
release = RELEASES[version]
|
|
|
except KeyError as exc:
|
|
except KeyError as exc:
|
|
|
raise ValueError(f"未知传播裂变系数参数版本: {version}") from exc
|
|
raise ValueError(f"未知传播裂变系数参数版本: {version}") from exc
|
|
|
- directory = (parameter_root or PARAMETER_ROOT) / version
|
|
|
|
|
|
|
+ directory = (parameter_root or PARAMETER_ROOT) / release.source_version
|
|
|
|
|
|
|
|
exact_required = {
|
|
exact_required = {
|
|
|
"首层投放日期",
|
|
"首层投放日期",
|
|
@@ -496,44 +609,133 @@ def load_fission_multiplier_parameters(
|
|
|
mini_goal_rows = fallback[fallback["参数层级"].eq("小程序转化目标回退")]
|
|
mini_goal_rows = fallback[fallback["参数层级"].eq("小程序转化目标回退")]
|
|
|
gzh_partner_rows = fallback[fallback["参数层级"].eq("公众号合作方回退")]
|
|
gzh_partner_rows = fallback[fallback["参数层级"].eq("公众号合作方回退")]
|
|
|
|
|
|
|
|
|
|
+ miniapp_exact = _mapping(
|
|
|
|
|
+ miniapp,
|
|
|
|
|
+ ["人群包", "转化目标"],
|
|
|
|
|
+ normalizers=[normalize_package, normalize_name],
|
|
|
|
|
+ label="小程序精确参数",
|
|
|
|
|
+ )
|
|
|
|
|
+ miniapp_by_package = _mapping(
|
|
|
|
|
+ mini_package_rows,
|
|
|
|
|
+ ["人群包"],
|
|
|
|
|
+ normalizers=[normalize_package],
|
|
|
|
|
+ label="小程序人群包回退参数",
|
|
|
|
|
+ )
|
|
|
|
|
+ miniapp_by_goal = _mapping(
|
|
|
|
|
+ mini_goal_rows,
|
|
|
|
|
+ ["转化目标"],
|
|
|
|
|
+ normalizers=[normalize_name],
|
|
|
|
|
+ label="小程序转化目标回退参数",
|
|
|
|
|
+ )
|
|
|
|
|
+ gzh_exact = _mapping(
|
|
|
|
|
+ gzh,
|
|
|
|
|
+ ["合作方", "公众号"],
|
|
|
|
|
+ normalizers=[normalize_name, normalize_name],
|
|
|
|
|
+ label="公众号精确参数",
|
|
|
|
|
+ )
|
|
|
|
|
+ gzh_by_partner = _mapping(
|
|
|
|
|
+ gzh_partner_rows,
|
|
|
|
|
+ ["合作方"],
|
|
|
|
|
+ normalizers=[normalize_name],
|
|
|
|
|
+ label="公众号合作方回退参数",
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ total_to_first: dict[tuple[str, str, str, str], float] = {}
|
|
|
|
|
+
|
|
|
|
|
+ def add_total_to_first(
|
|
|
|
|
+ entity_type: str,
|
|
|
|
|
+ match_level: str,
|
|
|
|
|
+ mapping: Mapping,
|
|
|
|
|
+ ) -> None:
|
|
|
|
|
+ for raw_key, value in mapping.items():
|
|
|
|
|
+ keys = raw_key if isinstance(raw_key, tuple) else (raw_key, "")
|
|
|
|
|
+ total_to_first[(entity_type, match_level, keys[0], keys[1])] = value
|
|
|
|
|
+
|
|
|
|
|
+ if release.includes_total_to_first:
|
|
|
|
|
+ add_total_to_first(
|
|
|
|
|
+ "self",
|
|
|
|
|
+ MATCH_MINIAPP_EXACT,
|
|
|
|
|
+ _mapping(
|
|
|
|
|
+ miniapp,
|
|
|
|
|
+ ["人群包", "转化目标"],
|
|
|
|
|
+ normalizers=[normalize_package, normalize_name],
|
|
|
|
|
+ label="小程序精确参数",
|
|
|
|
|
+ value_column=SOURCE_TOTAL_TO_FIRST_COLUMN,
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ add_total_to_first(
|
|
|
|
|
+ "self",
|
|
|
|
|
+ MATCH_MINIAPP_PACKAGE,
|
|
|
|
|
+ _mapping(
|
|
|
|
|
+ mini_package_rows,
|
|
|
|
|
+ ["人群包"],
|
|
|
|
|
+ normalizers=[normalize_package],
|
|
|
|
|
+ label="小程序人群包回退参数",
|
|
|
|
|
+ value_column=SOURCE_TOTAL_TO_FIRST_COLUMN,
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ add_total_to_first(
|
|
|
|
|
+ "self",
|
|
|
|
|
+ MATCH_MINIAPP_GOAL,
|
|
|
|
|
+ _mapping(
|
|
|
|
|
+ mini_goal_rows,
|
|
|
|
|
+ ["转化目标"],
|
|
|
|
|
+ normalizers=[normalize_name],
|
|
|
|
|
+ label="小程序转化目标回退参数",
|
|
|
|
|
+ value_column=SOURCE_TOTAL_TO_FIRST_COLUMN,
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ total_to_first[("self", MATCH_MINIAPP_CHANNEL, "", "")] = (
|
|
|
|
|
+ _single_channel_multiplier(
|
|
|
|
|
+ fallback,
|
|
|
|
|
+ "小程序渠道回退",
|
|
|
|
|
+ SOURCE_TOTAL_TO_FIRST_COLUMN,
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ add_total_to_first(
|
|
|
|
|
+ "gzh",
|
|
|
|
|
+ MATCH_GZH_EXACT,
|
|
|
|
|
+ _mapping(
|
|
|
|
|
+ gzh,
|
|
|
|
|
+ ["合作方", "公众号"],
|
|
|
|
|
+ normalizers=[normalize_name, normalize_name],
|
|
|
|
|
+ label="公众号精确参数",
|
|
|
|
|
+ value_column=SOURCE_TOTAL_TO_FIRST_COLUMN,
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ add_total_to_first(
|
|
|
|
|
+ "gzh",
|
|
|
|
|
+ MATCH_GZH_PARTNER,
|
|
|
|
|
+ _mapping(
|
|
|
|
|
+ gzh_partner_rows,
|
|
|
|
|
+ ["合作方"],
|
|
|
|
|
+ normalizers=[normalize_name],
|
|
|
|
|
+ label="公众号合作方回退参数",
|
|
|
|
|
+ value_column=SOURCE_TOTAL_TO_FIRST_COLUMN,
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ total_to_first[("gzh", MATCH_GZH_CHANNEL, "", "")] = (
|
|
|
|
|
+ _single_channel_multiplier(
|
|
|
|
|
+ fallback,
|
|
|
|
|
+ "公众号渠道回退",
|
|
|
|
|
+ SOURCE_TOTAL_TO_FIRST_COLUMN,
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
return FissionMultiplierParameters(
|
|
return FissionMultiplierParameters(
|
|
|
release=release,
|
|
release=release,
|
|
|
- miniapp_exact=_mapping(
|
|
|
|
|
- miniapp,
|
|
|
|
|
- ["人群包", "转化目标"],
|
|
|
|
|
- normalizers=[normalize_package, normalize_name],
|
|
|
|
|
- label="小程序精确参数",
|
|
|
|
|
- ),
|
|
|
|
|
- miniapp_by_package=_mapping(
|
|
|
|
|
- mini_package_rows,
|
|
|
|
|
- ["人群包"],
|
|
|
|
|
- normalizers=[normalize_package],
|
|
|
|
|
- label="小程序人群包回退参数",
|
|
|
|
|
- ),
|
|
|
|
|
- miniapp_by_goal=_mapping(
|
|
|
|
|
- mini_goal_rows,
|
|
|
|
|
- ["转化目标"],
|
|
|
|
|
- normalizers=[normalize_name],
|
|
|
|
|
- label="小程序转化目标回退参数",
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ miniapp_exact=miniapp_exact,
|
|
|
|
|
+ miniapp_by_package=miniapp_by_package,
|
|
|
|
|
+ miniapp_by_goal=miniapp_by_goal,
|
|
|
miniapp_channel=_single_channel_multiplier(
|
|
miniapp_channel=_single_channel_multiplier(
|
|
|
fallback, "小程序渠道回退"
|
|
fallback, "小程序渠道回退"
|
|
|
),
|
|
),
|
|
|
- gzh_exact=_mapping(
|
|
|
|
|
- gzh,
|
|
|
|
|
- ["合作方", "公众号"],
|
|
|
|
|
- normalizers=[normalize_name, normalize_name],
|
|
|
|
|
- label="公众号精确参数",
|
|
|
|
|
- ),
|
|
|
|
|
- gzh_by_partner=_mapping(
|
|
|
|
|
- gzh_partner_rows,
|
|
|
|
|
- ["合作方"],
|
|
|
|
|
- normalizers=[normalize_name],
|
|
|
|
|
- label="公众号合作方回退参数",
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ gzh_exact=gzh_exact,
|
|
|
|
|
+ gzh_by_partner=gzh_by_partner,
|
|
|
gzh_channel=_single_channel_multiplier(
|
|
gzh_channel=_single_channel_multiplier(
|
|
|
fallback, "公众号渠道回退"
|
|
fallback, "公众号渠道回退"
|
|
|
),
|
|
),
|
|
|
|
|
+ total_to_first=total_to_first,
|
|
|
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),
|
|
@@ -552,6 +754,7 @@ def apply_fission_multiplier(
|
|
|
|
|
|
|
|
result = daily.copy()
|
|
result = daily.copy()
|
|
|
multipliers: list[float] = []
|
|
multipliers: list[float] = []
|
|
|
|
|
+ multipliers_vs_first: list[float | None] = []
|
|
|
levels: list[str] = []
|
|
levels: list[str] = []
|
|
|
sources: list[str] = []
|
|
sources: list[str] = []
|
|
|
for row in result.to_dict("records"):
|
|
for row in result.to_dict("records"):
|
|
@@ -563,10 +766,12 @@ def apply_fission_multiplier(
|
|
|
official_account=row["公众号名"],
|
|
official_account=row["公众号名"],
|
|
|
)
|
|
)
|
|
|
multipliers.append(match.multiplier)
|
|
multipliers.append(match.multiplier)
|
|
|
|
|
+ multipliers_vs_first.append(match.multiplier_vs_first)
|
|
|
levels.append(match.match_level)
|
|
levels.append(match.match_level)
|
|
|
sources.append(match.source)
|
|
sources.append(match.source)
|
|
|
|
|
|
|
|
result[DISPLAY_MULTIPLIER_COLUMN] = multipliers
|
|
result[DISPLAY_MULTIPLIER_COLUMN] = multipliers
|
|
|
|
|
+ result[DISPLAY_TOTAL_TO_FIRST_COLUMN] = multipliers_vs_first
|
|
|
result["传播裂变系数匹配层级"] = levels
|
|
result["传播裂变系数匹配层级"] = levels
|
|
|
result["传播裂变系数来源"] = sources
|
|
result["传播裂变系数来源"] = sources
|
|
|
result["传播裂变参数版本"] = parameters.release.version
|
|
result["传播裂变参数版本"] = parameters.release.version
|