|
@@ -25,6 +25,7 @@ ROOT = Path(__file__).resolve().parent
|
|
|
MINIAPP_CHANNEL = "小程序投流-稳定"
|
|
MINIAPP_CHANNEL = "小程序投流-稳定"
|
|
|
REVENUE_TABLE = "ads_ad_own_package_detail_15min"
|
|
REVENUE_TABLE = "ads_ad_own_package_detail_15min"
|
|
|
COST_TABLE = "opengid_base_data"
|
|
COST_TABLE = "opengid_base_data"
|
|
|
|
|
+MINIAPP_REVENUE_TABLE = "ad_pqtid_behavior_info"
|
|
|
DEFAULT_START_HOUR = 6
|
|
DEFAULT_START_HOUR = 6
|
|
|
DEFAULT_END_HOUR = 22
|
|
DEFAULT_END_HOUR = 22
|
|
|
|
|
|
|
@@ -34,14 +35,18 @@ REPORT_HEADERS = [
|
|
|
"总投放成本",
|
|
"总投放成本",
|
|
|
"小程序投流成本",
|
|
"小程序投流成本",
|
|
|
"商业化收入",
|
|
"商业化收入",
|
|
|
|
|
+ "小程序投流收入",
|
|
|
"CPM",
|
|
"CPM",
|
|
|
"收入/总投放",
|
|
"收入/总投放",
|
|
|
"收入/小程序成本",
|
|
"收入/小程序成本",
|
|
|
|
|
+ "小程序投流收入/小程序成本",
|
|
|
"当日累计总收入",
|
|
"当日累计总收入",
|
|
|
|
|
+ "当日累计小程序投流收入",
|
|
|
"当日累计总投放成本",
|
|
"当日累计总投放成本",
|
|
|
"当日累计小程序投流成本",
|
|
"当日累计小程序投流成本",
|
|
|
"当日累计收入/总投放",
|
|
"当日累计收入/总投放",
|
|
|
"当日累计收入/小程序成本",
|
|
"当日累计收入/小程序成本",
|
|
|
|
|
+ "当日累计小程序投流收入/小程序成本",
|
|
|
"总去重UV",
|
|
"总去重UV",
|
|
|
"总单用户成本",
|
|
"总单用户成本",
|
|
|
"小程序去重UV",
|
|
"小程序去重UV",
|
|
@@ -174,6 +179,31 @@ ORDER BY dt, hour_of_day
|
|
|
return reader.to_pandas()
|
|
return reader.to_pandas()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def fetch_miniapp_revenue_rows(
|
|
|
|
|
+ client: "ODPS",
|
|
|
|
|
+ *,
|
|
|
|
|
+ start_date: date,
|
|
|
|
|
+ end_date: date,
|
|
|
|
|
+ miniapp_channel: str,
|
|
|
|
|
+) -> pd.DataFrame:
|
|
|
|
|
+ escaped_channel = miniapp_channel.replace("'", "''")
|
|
|
|
|
+ sql = f"""
|
|
|
|
|
+SELECT
|
|
|
|
|
+ dt AS data_date,
|
|
|
|
|
+ CAST(hh AS BIGINT) AS hour_of_day,
|
|
|
|
|
+ SUM(NVL(`真实收入`, 0)) AS miniapp_revenue
|
|
|
|
|
+FROM loghubods.{MINIAPP_REVENUE_TABLE}
|
|
|
|
|
+WHERE dt >= '{start_date:%Y%m%d}'
|
|
|
|
|
+ AND dt <= '{end_date:%Y%m%d}'
|
|
|
|
|
+ AND `增长渠道来源` = '{escaped_channel}'
|
|
|
|
|
+GROUP BY dt, CAST(hh AS BIGINT)
|
|
|
|
|
+ORDER BY dt, hour_of_day
|
|
|
|
|
+""".strip()
|
|
|
|
|
+ instance = client.execute_sql(sql, hints={"odps.sql.submit.mode": "script"})
|
|
|
|
|
+ with instance.open_reader(tunnel=True) as reader:
|
|
|
|
|
+ return reader.to_pandas()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def hourly_revenue(revenue_rows: pd.DataFrame) -> pd.DataFrame:
|
|
def hourly_revenue(revenue_rows: pd.DataFrame) -> pd.DataFrame:
|
|
|
columns = [
|
|
columns = [
|
|
|
"data_date",
|
|
"data_date",
|
|
@@ -255,10 +285,27 @@ def hourly_cost(cost_rows: pd.DataFrame) -> pd.DataFrame:
|
|
|
return rows[columns].sort_values(["data_date", "hour_of_day"])
|
|
return rows[columns].sort_values(["data_date", "hour_of_day"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def hourly_miniapp_revenue(revenue_rows: pd.DataFrame) -> pd.DataFrame:
|
|
|
|
|
+ columns = ["data_date", "hour_of_day", "miniapp_revenue"]
|
|
|
|
|
+ if revenue_rows.empty:
|
|
|
|
|
+ return pd.DataFrame(columns=columns)
|
|
|
|
|
+
|
|
|
|
|
+ rows = revenue_rows.copy()
|
|
|
|
|
+ rows["data_date"] = rows["data_date"].astype(str)
|
|
|
|
|
+ rows["hour_of_day"] = pd.to_numeric(rows["hour_of_day"], errors="coerce")
|
|
|
|
|
+ rows = rows.dropna(subset=["hour_of_day"])
|
|
|
|
|
+ rows["hour_of_day"] = rows["hour_of_day"].astype("int64")
|
|
|
|
|
+ rows["miniapp_revenue"] = pd.to_numeric(
|
|
|
|
|
+ rows["miniapp_revenue"], errors="coerce"
|
|
|
|
|
+ ).fillna(0.0)
|
|
|
|
|
+ return rows[columns].sort_values(["data_date", "hour_of_day"])
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def build_hourly_report(
|
|
def build_hourly_report(
|
|
|
*,
|
|
*,
|
|
|
revenue_rows: pd.DataFrame,
|
|
revenue_rows: pd.DataFrame,
|
|
|
cost_rows: pd.DataFrame,
|
|
cost_rows: pd.DataFrame,
|
|
|
|
|
+ miniapp_revenue_rows: pd.DataFrame | None = None,
|
|
|
start_date: date,
|
|
start_date: date,
|
|
|
end_date: date,
|
|
end_date: date,
|
|
|
start_hour: int = DEFAULT_START_HOUR,
|
|
start_hour: int = DEFAULT_START_HOUR,
|
|
@@ -282,12 +329,21 @@ def build_hourly_report(
|
|
|
hourly_revenue(revenue_rows),
|
|
hourly_revenue(revenue_rows),
|
|
|
on=["data_date", "hour_of_day"],
|
|
on=["data_date", "hour_of_day"],
|
|
|
how="left",
|
|
how="left",
|
|
|
|
|
+ ).merge(
|
|
|
|
|
+ hourly_miniapp_revenue(
|
|
|
|
|
+ miniapp_revenue_rows
|
|
|
|
|
+ if miniapp_revenue_rows is not None
|
|
|
|
|
+ else pd.DataFrame()
|
|
|
|
|
+ ),
|
|
|
|
|
+ on=["data_date", "hour_of_day"],
|
|
|
|
|
+ how="left",
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
numeric_defaults = {
|
|
numeric_defaults = {
|
|
|
"total_cost": 0.0,
|
|
"total_cost": 0.0,
|
|
|
"miniapp_cost": 0.0,
|
|
"miniapp_cost": 0.0,
|
|
|
"commercial_revenue": 0.0,
|
|
"commercial_revenue": 0.0,
|
|
|
|
|
+ "miniapp_revenue": 0.0,
|
|
|
"total_uv": 0.0,
|
|
"total_uv": 0.0,
|
|
|
"total_unit_user_cost": 0.0,
|
|
"total_unit_user_cost": 0.0,
|
|
|
"miniapp_uv": 0.0,
|
|
"miniapp_uv": 0.0,
|
|
@@ -313,10 +369,21 @@ def build_hourly_report(
|
|
|
),
|
|
),
|
|
|
axis=1,
|
|
axis=1,
|
|
|
)
|
|
)
|
|
|
|
|
+ report["小程序投流收入/小程序成本"] = report.apply(
|
|
|
|
|
+ lambda row: (
|
|
|
|
|
+ row["miniapp_revenue"] / row["miniapp_cost"]
|
|
|
|
|
+ if row["miniapp_cost"] > 0
|
|
|
|
|
+ else None
|
|
|
|
|
+ ),
|
|
|
|
|
+ axis=1,
|
|
|
|
|
+ )
|
|
|
report = report.sort_values(["data_date", "hour_of_day"]).reset_index(drop=True)
|
|
report = report.sort_values(["data_date", "hour_of_day"]).reset_index(drop=True)
|
|
|
report["当日累计总收入"] = report.groupby("data_date")[
|
|
report["当日累计总收入"] = report.groupby("data_date")[
|
|
|
"commercial_revenue"
|
|
"commercial_revenue"
|
|
|
].cumsum()
|
|
].cumsum()
|
|
|
|
|
+ report["当日累计小程序投流收入"] = report.groupby("data_date")[
|
|
|
|
|
+ "miniapp_revenue"
|
|
|
|
|
+ ].cumsum()
|
|
|
report["当日累计总投放成本"] = report.groupby("data_date")[
|
|
report["当日累计总投放成本"] = report.groupby("data_date")[
|
|
|
"total_cost"
|
|
"total_cost"
|
|
|
].cumsum()
|
|
].cumsum()
|
|
@@ -339,6 +406,14 @@ def build_hourly_report(
|
|
|
),
|
|
),
|
|
|
axis=1,
|
|
axis=1,
|
|
|
)
|
|
)
|
|
|
|
|
+ report["当日累计小程序投流收入/小程序成本"] = report.apply(
|
|
|
|
|
+ lambda row: (
|
|
|
|
|
+ row["当日累计小程序投流收入"] / row["当日累计小程序投流成本"]
|
|
|
|
|
+ if row["当日累计小程序投流成本"] > 0
|
|
|
|
|
+ else None
|
|
|
|
|
+ ),
|
|
|
|
|
+ axis=1,
|
|
|
|
|
+ )
|
|
|
report["日期"] = pd.to_datetime(report["data_date"], format="%Y%m%d").dt.strftime(
|
|
report["日期"] = pd.to_datetime(report["data_date"], format="%Y%m%d").dt.strftime(
|
|
|
"%Y-%m-%d"
|
|
"%Y-%m-%d"
|
|
|
)
|
|
)
|
|
@@ -348,6 +423,7 @@ def build_hourly_report(
|
|
|
"total_cost": "总投放成本",
|
|
"total_cost": "总投放成本",
|
|
|
"miniapp_cost": "小程序投流成本",
|
|
"miniapp_cost": "小程序投流成本",
|
|
|
"commercial_revenue": "商业化收入",
|
|
"commercial_revenue": "商业化收入",
|
|
|
|
|
+ "miniapp_revenue": "小程序投流收入",
|
|
|
"cpm": "CPM",
|
|
"cpm": "CPM",
|
|
|
"total_uv": "总去重UV",
|
|
"total_uv": "总去重UV",
|
|
|
"total_unit_user_cost": "总单用户成本",
|
|
"total_unit_user_cost": "总单用户成本",
|
|
@@ -395,21 +471,46 @@ def create_workbook(
|
|
|
cell.alignment = Alignment(horizontal="center", vertical="center")
|
|
cell.alignment = Alignment(horizontal="center", vertical="center")
|
|
|
|
|
|
|
|
widths = [
|
|
widths = [
|
|
|
- 13, 9, 14, 16, 14, 10, 14, 17, 16, 18, 22, 20, 24,
|
|
|
|
|
- 11, 14, 13, 16, 17, 18,
|
|
|
|
|
|
|
+ 13, 9, 14, 16, 14, 16, 10, 14, 17, 22, 16, 20, 18, 22,
|
|
|
|
|
+ 20, 24, 28, 11, 14, 13, 16, 17, 18,
|
|
|
]
|
|
]
|
|
|
for index, width in enumerate(widths, start=1):
|
|
for index, width in enumerate(widths, start=1):
|
|
|
sheet.column_dimensions[get_column_letter(index)].width = width
|
|
sheet.column_dimensions[get_column_letter(index)].width = width
|
|
|
for row in sheet.iter_rows(min_row=2):
|
|
for row in sheet.iter_rows(min_row=2):
|
|
|
for cell in row:
|
|
for cell in row:
|
|
|
cell.alignment = Alignment(horizontal="center", vertical="center")
|
|
cell.alignment = Alignment(horizontal="center", vertical="center")
|
|
|
- for column in ("C", "D", "E", "F", "I", "J", "K", "O", "Q"):
|
|
|
|
|
|
|
+ money_headers = (
|
|
|
|
|
+ "总投放成本",
|
|
|
|
|
+ "小程序投流成本",
|
|
|
|
|
+ "商业化收入",
|
|
|
|
|
+ "小程序投流收入",
|
|
|
|
|
+ "CPM",
|
|
|
|
|
+ "当日累计总收入",
|
|
|
|
|
+ "当日累计小程序投流收入",
|
|
|
|
|
+ "当日累计总投放成本",
|
|
|
|
|
+ "当日累计小程序投流成本",
|
|
|
|
|
+ "总单用户成本",
|
|
|
|
|
+ "小程序单用户成本",
|
|
|
|
|
+ )
|
|
|
|
|
+ ratio_headers = (
|
|
|
|
|
+ "收入/总投放",
|
|
|
|
|
+ "收入/小程序成本",
|
|
|
|
|
+ "小程序投流收入/小程序成本",
|
|
|
|
|
+ "当日累计收入/总投放",
|
|
|
|
|
+ "当日累计收入/小程序成本",
|
|
|
|
|
+ "当日累计小程序投流收入/小程序成本",
|
|
|
|
|
+ )
|
|
|
|
|
+ count_headers = ("总去重UV", "小程序去重UV", "收入15分钟窗口数")
|
|
|
|
|
+ for header in money_headers:
|
|
|
|
|
+ column = get_column_letter(REPORT_HEADERS.index(header) + 1)
|
|
|
for cell in sheet[column][1:]:
|
|
for cell in sheet[column][1:]:
|
|
|
cell.number_format = "0.00"
|
|
cell.number_format = "0.00"
|
|
|
- for column in ("G", "H", "L", "M"):
|
|
|
|
|
|
|
+ for header in ratio_headers:
|
|
|
|
|
+ column = get_column_letter(REPORT_HEADERS.index(header) + 1)
|
|
|
for cell in sheet[column][1:]:
|
|
for cell in sheet[column][1:]:
|
|
|
cell.number_format = "0.0000"
|
|
cell.number_format = "0.0000"
|
|
|
- for column in ("N", "P", "R"):
|
|
|
|
|
|
|
+ for header in count_headers:
|
|
|
|
|
+ column = get_column_letter(REPORT_HEADERS.index(header) + 1)
|
|
|
for cell in sheet[column][1:]:
|
|
for cell in sheet[column][1:]:
|
|
|
cell.number_format = "0"
|
|
cell.number_format = "0"
|
|
|
sheet.freeze_panes = "A2"
|
|
sheet.freeze_panes = "A2"
|
|
@@ -423,9 +524,14 @@ def create_workbook(
|
|
|
("每日统计时段", f"{start_hour:02d}:00-{end_hour:02d}:59"),
|
|
("每日统计时段", f"{start_hour:02d}:00-{end_hour:02d}:59"),
|
|
|
("统计小时数", len(report)),
|
|
("统计小时数", len(report)),
|
|
|
("小程序成本渠道", miniapp_channel),
|
|
("小程序成本渠道", miniapp_channel),
|
|
|
|
|
+ (
|
|
|
|
|
+ "小程序收入来源",
|
|
|
|
|
+ f"loghubods.{MINIAPP_REVENUE_TABLE}.真实收入",
|
|
|
|
|
+ ),
|
|
|
("总投放成本", float(report["总投放成本"].sum())),
|
|
("总投放成本", float(report["总投放成本"].sum())),
|
|
|
("小程序投流成本", float(report["小程序投流成本"].sum())),
|
|
("小程序投流成本", float(report["小程序投流成本"].sum())),
|
|
|
("商业化收入", float(report["商业化收入"].sum())),
|
|
("商业化收入", float(report["商业化收入"].sum())),
|
|
|
|
|
+ ("小程序投流收入", float(report["小程序投流收入"].sum())),
|
|
|
]
|
|
]
|
|
|
summary.append(["字段", "值"])
|
|
summary.append(["字段", "值"])
|
|
|
for row in summary_rows:
|
|
for row in summary_rows:
|
|
@@ -474,9 +580,16 @@ def main() -> None:
|
|
|
end_date=end_date,
|
|
end_date=end_date,
|
|
|
miniapp_channel=args.miniapp_channel,
|
|
miniapp_channel=args.miniapp_channel,
|
|
|
)
|
|
)
|
|
|
|
|
+ miniapp_revenue_rows = fetch_miniapp_revenue_rows(
|
|
|
|
|
+ client,
|
|
|
|
|
+ start_date=start_date,
|
|
|
|
|
+ end_date=end_date,
|
|
|
|
|
+ miniapp_channel=args.miniapp_channel,
|
|
|
|
|
+ )
|
|
|
report = build_hourly_report(
|
|
report = build_hourly_report(
|
|
|
revenue_rows=revenue_rows,
|
|
revenue_rows=revenue_rows,
|
|
|
cost_rows=cost_rows,
|
|
cost_rows=cost_rows,
|
|
|
|
|
+ miniapp_revenue_rows=miniapp_revenue_rows,
|
|
|
start_date=start_date,
|
|
start_date=start_date,
|
|
|
end_date=end_date,
|
|
end_date=end_date,
|
|
|
start_hour=args.start_hour,
|
|
start_hour=args.start_hour,
|