Files
amazon-za-delivery/update.sh
T
2026-08-24 16:51:55 +08:00

175 lines
7.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
export REPORT_DIR="$SCRIPT_DIR"
python3 - <<'PY'
import csv
import json
import os
from collections import defaultdict
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
root = Path(os.environ["REPORT_DIR"])
package = "com.amazon.mShop.android.shopping"
geo = "ZA"
launch_start = date(2026, 8, 1)
launch_end = date(2026, 8, 31)
monthly_budget_zar = 511_500.0
daily_budget_zar = 16_500.0
unit_price_zar = 49.5
fx_zar_per_usd = 16.5
spend = defaultdict(float)
repostback_installs = defaultdict(int)
with (root / "source-delivery.csv").open(encoding="utf-8-sig", newline="") as f:
for row in csv.DictReader(f):
if row.get("广告包") == package and row.get("国家") == geo:
day = row.get("日期", "")
if len(day) == 8 and day.isdigit():
spend[day] += float(row.get("平台消耗") or 0)
repostback_installs[day] += int(float(row.get("归因安装") or 0))
events = defaultdict(lambda: defaultdict(int))
with (root / "source-events.csv").open(encoding="utf-8-sig", newline="") as f:
reader = csv.reader(f)
next(reader, None)
for row in reader:
if len(row) < 7 or row[2] != package or row[3] != geo:
continue
name, day = row[0], row[1]
if name not in {"event1", "event2"}:
continue
# The source export leaves its last three headers blank. The second
# numeric column after midas_campaign_id is the repostback count.
try:
repostback = int(float(row[6] or 0))
except (ValueError, TypeError):
repostback = 0
events[day][name] += repostback
source_days = sorted(set(spend) | set(events))
data_cutoff = source_days[-1] if source_days else None
days = []
cursor = launch_start
while cursor <= launch_end:
days.append(cursor.strftime("%Y%m%d"))
cursor += timedelta(days=1)
daily = []
for day in days:
install = repostback_installs[day]
event1 = events[day]["event1"]
event2 = events[day]["event2"]
conversions = event1 + event2
payable_zar = conversions * unit_price_zar
midas_spend_usd = round(spend[day], 2)
is_actual = bool(data_cutoff and day <= data_cutoff)
daily.append({
"date": f"{day[:4]}-{day[4:6]}-{day[6:]}",
"is_actual": is_actual,
"install": install,
"event1": event1,
"event2": event2,
"e1e2": conversions,
"e1e2_rate": round(conversions / install * 100, 2) if install else None,
"midas_spend": midas_spend_usd,
"midas_spend_zar": round(midas_spend_usd * fx_zar_per_usd, 2),
"payable_spend": round(conversions * 3, 2),
"payable_spend_zar": round(payable_zar, 2),
"daily_budget_zar": daily_budget_zar,
"daily_cap": round(daily_budget_zar / unit_price_zar),
"ffr_rate": round(conversions / (daily_budget_zar / unit_price_zar) * 100, 2),
"ecpi_usd": round(midas_spend_usd / install, 4) if install else None,
})
actual_daily = [x for x in daily if x["is_actual"]]
totals = {
"install": sum(x["install"] for x in actual_daily),
"event1": sum(x["event1"] for x in actual_daily),
"event2": sum(x["event2"] for x in actual_daily),
"e1e2": sum(x["e1e2"] for x in actual_daily),
"midas_spend": round(sum(x["midas_spend"] for x in actual_daily), 2),
"midas_spend_zar": round(sum(x["midas_spend_zar"] for x in actual_daily), 2),
"payable_spend": round(sum(x["payable_spend"] for x in actual_daily), 2),
"payable_spend_zar": round(sum(x["payable_spend_zar"] for x in actual_daily), 2),
}
totals["e1e2_rate"] = round(totals["e1e2"] / totals["install"] * 100, 2) if totals["install"] else None
elapsed_days = len(actual_daily)
remaining_days = max((launch_end - datetime.strptime(data_cutoff, "%Y%m%d").date()).days, 0) if data_cutoff else (launch_end - launch_start).days + 1
expected_budget_to_date = min(elapsed_days * daily_budget_zar, monthly_budget_zar)
remaining_budget_zar = monthly_budget_zar - totals["payable_spend_zar"]
avg_daily_used = totals["payable_spend_zar"] / elapsed_days if elapsed_days else 0
projected_month_spend = avg_daily_used * ((launch_end - launch_start).days + 1)
budget = {
"monthly_budget_zar": monthly_budget_zar,
"monthly_budget_usd": round(monthly_budget_zar / fx_zar_per_usd, 2),
"daily_budget_zar": daily_budget_zar,
"daily_budget_usd": round(daily_budget_zar / fx_zar_per_usd, 2),
"unit_price_zar": unit_price_zar,
"unit_price_usd": round(unit_price_zar / fx_zar_per_usd, 2),
"fx_zar_per_usd": fx_zar_per_usd,
"daily_cap": round(daily_budget_zar / unit_price_zar),
"used_budget_zar": totals["payable_spend_zar"],
"used_rate": round(totals["payable_spend_zar"] / monthly_budget_zar * 100, 2),
"remaining_budget_zar": round(remaining_budget_zar, 2),
"remaining_budget_usd": round(remaining_budget_zar / fx_zar_per_usd, 2),
"expected_budget_to_date_zar": expected_budget_to_date,
"pacing_rate": round(totals["payable_spend_zar"] / expected_budget_to_date * 100, 2) if expected_budget_to_date else None,
"elapsed_days": elapsed_days,
"remaining_days": remaining_days,
"required_daily_budget_zar": round(remaining_budget_zar / remaining_days, 2) if remaining_days else 0,
"required_daily_events": round(remaining_budget_zar / remaining_days / unit_price_zar) if remaining_days else 0,
"projected_month_spend_zar": round(projected_month_spend, 2),
"projected_month_variance_zar": round(monthly_budget_zar - projected_month_spend, 2),
"mtd_expected_ffr": round(min(datetime.now().day, 31) / 31 * 100, 2),
}
weeks = []
for row in actual_daily:
row_date = datetime.strptime(row["date"], "%Y-%m-%d").date()
week_start = row_date - timedelta(days=row_date.weekday())
week_end = week_start + timedelta(days=6)
label_start = max(week_start, launch_start)
label_end = min(week_end, launch_end)
key = week_start.isoformat()
if not weeks or weeks[-1]["key"] != key:
weeks.append({"key": key, "label": f"{label_start.strftime('%b %d')}–{label_end.strftime('%b %d')}", "install": 0, "event1": 0, "event2": 0, "e1e2": 0, "payable_spend": 0.0, "midas_spend": 0.0})
week = weeks[-1]
for metric in ("install", "event1", "event2", "e1e2"):
week[metric] += row[metric]
week["payable_spend"] += row["payable_spend"]
week["midas_spend"] += row["midas_spend"]
for week in weeks:
week["e1e2_rate"] = round(week["e1e2"] / week["install"] * 100, 2) if week["install"] else None
week["payable_spend"] = round(week["payable_spend"], 2)
week["midas_spend"] = round(week["midas_spend"], 2)
week.pop("key", None)
payload = {
"meta": {
"package": package,
"geo": geo,
"generated_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"date_start": daily[0]["date"] if daily else None,
"date_end": daily[-1]["date"] if daily else None,
"data_cutoff": f"{data_cutoff[:4]}-{data_cutoff[4:6]}-{data_cutoff[6:]}" if data_cutoff else None,
"currency": "USD",
"install_definition": "delivery repostback (attributed installs); af_app_opened excluded",
"event_definition": "event1/event2 repostback",
"rate_definition": "(event1 + event2) / Install",
"payable_definition": "(event1 + event2) × $3",
},
"totals": totals,
"budget": budget,
"daily": daily,
"weekly": weeks,
}
tmp = root / "data.json.tmp"
tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
tmp.replace(root / "data.json")
PY