From 82e64d1512d4b4bb475d8cf5e2dca7986659839b Mon Sep 17 00:00:00 2001 From: wangwj1 Date: Mon, 24 Aug 2026 15:55:14 +0800 Subject: [PATCH] Add Ozon country CAC report --- data.json | 445 ++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 30 ++++ schedule.conf | 1 + update.sh | 139 ++++++++++++++++ 4 files changed, 615 insertions(+) create mode 100644 data.json create mode 100644 index.html create mode 100644 schedule.conf create mode 100755 update.sh diff --git a/data.json b/data.json new file mode 100644 index 0000000..5ff890e --- /dev/null +++ b/data.json @@ -0,0 +1,445 @@ +{ + "title": "Ozon · 过去14天国家投放表现", + "generated_at": "2026-08-24T15:42:16+08:00", + "timezone": "Asia/Shanghai", + "period": { + "start": "2026-08-10", + "end": "2026-08-23", + "days": 14 + }, + "countries": [ + "KZ", + "UZ", + "KG", + "AM" + ], + "summary": [ + { + "country": "KZ", + "installs": 12980.0, + "spend": 9086.0, + "avg_cac": 807.3846, + "cac_days": 13 + }, + { + "country": "UZ", + "installs": 5630.0, + "spend": 2815.0, + "avg_cac": 5723.6923, + "cac_days": 13 + }, + { + "country": "KG", + "installs": 1724.0, + "spend": 862.0, + "avg_cac": 5302.0, + "cac_days": 10 + }, + { + "country": "AM", + "installs": 971.0, + "spend": 485.5, + "avg_cac": 3127.553, + "cac_days": 10 + } + ], + "records": [ + { + "date": "2026-08-10", + "country": "KZ", + "installs": 1075.0, + "spend": 752.5, + "cac": 636.0 + }, + { + "date": "2026-08-11", + "country": "KZ", + "installs": 1027.0, + "spend": 718.9, + "cac": 745.0 + }, + { + "date": "2026-08-12", + "country": "KZ", + "installs": 1034.0, + "spend": 723.8, + "cac": 954.0 + }, + { + "date": "2026-08-13", + "country": "KZ", + "installs": 1097.0, + "spend": 767.9, + "cac": 837.0 + }, + { + "date": "2026-08-14", + "country": "KZ", + "installs": 1072.0, + "spend": 750.4, + "cac": 724.0 + }, + { + "date": "2026-08-15", + "country": "KZ", + "installs": 718.0, + "spend": 502.6, + "cac": 837.0 + }, + { + "date": "2026-08-16", + "country": "KZ", + "installs": 675.0, + "spend": 472.5, + "cac": 1062.0 + }, + { + "date": "2026-08-17", + "country": "KZ", + "installs": 715.0, + "spend": 500.5, + "cac": 702.0 + }, + { + "date": "2026-08-18", + "country": "KZ", + "installs": 682.0, + "spend": 477.4, + "cac": 677.0 + }, + { + "date": "2026-08-19", + "country": "KZ", + "installs": 612.0, + "spend": 428.4, + "cac": 597.0 + }, + { + "date": "2026-08-20", + "country": "KZ", + "installs": 602.0, + "spend": 421.4, + "cac": 770.0 + }, + { + "date": "2026-08-21", + "country": "KZ", + "installs": 1117.0, + "spend": 781.9, + "cac": 909.0 + }, + { + "date": "2026-08-22", + "country": "KZ", + "installs": 1298.0, + "spend": 908.6, + "cac": 1046.0 + }, + { + "date": "2026-08-23", + "country": "KZ", + "installs": 1256.0, + "spend": 879.2, + "cac": null + }, + { + "date": "2026-08-10", + "country": "UZ", + "installs": 550.0, + "spend": 275.0, + "cac": 4493.0 + }, + { + "date": "2026-08-11", + "country": "UZ", + "installs": 614.0, + "spend": 307.0, + "cac": 5103.0 + }, + { + "date": "2026-08-12", + "country": "UZ", + "installs": 614.0, + "spend": 307.0, + "cac": 3349.0 + }, + { + "date": "2026-08-13", + "country": "UZ", + "installs": 608.0, + "spend": 304.0, + "cac": 7011.0 + }, + { + "date": "2026-08-14", + "country": "UZ", + "installs": 587.0, + "spend": 293.5, + "cac": 2545.0 + }, + { + "date": "2026-08-15", + "country": "UZ", + "installs": 224.0, + "spend": 112.0, + "cac": 3768.0 + }, + { + "date": "2026-08-16", + "country": "UZ", + "installs": 176.0, + "spend": 88.0, + "cac": 0.0 + }, + { + "date": "2026-08-17", + "country": "UZ", + "installs": 211.0, + "spend": 105.5, + "cac": 8147.0 + }, + { + "date": "2026-08-18", + "country": "UZ", + "installs": 170.0, + "spend": 85.0, + "cac": 20293.0 + }, + { + "date": "2026-08-19", + "country": "UZ", + "installs": 141.0, + "spend": 70.5, + "cac": 8912.0 + }, + { + "date": "2026-08-20", + "country": "UZ", + "installs": 124.0, + "spend": 62.0, + "cac": 3829.0 + }, + { + "date": "2026-08-21", + "country": "UZ", + "installs": 509.0, + "spend": 254.5, + "cac": 3233.0 + }, + { + "date": "2026-08-22", + "country": "UZ", + "installs": 530.0, + "spend": 265.0, + "cac": 3725.0 + }, + { + "date": "2026-08-23", + "country": "UZ", + "installs": 572.0, + "spend": 286.0, + "cac": null + }, + { + "date": "2026-08-10", + "country": "KG", + "installs": 197.0, + "spend": 98.5, + "cac": 2186.0 + }, + { + "date": "2026-08-11", + "country": "KG", + "installs": 196.0, + "spend": 98.0, + "cac": 2537.0 + }, + { + "date": "2026-08-12", + "country": "KG", + "installs": 186.0, + "spend": 93.0, + "cac": 3633.0 + }, + { + "date": "2026-08-13", + "country": "KG", + "installs": 179.0, + "spend": 89.5, + "cac": 20857.0 + }, + { + "date": "2026-08-14", + "country": "KG", + "installs": 167.0, + "spend": 83.5, + "cac": 3520.0 + }, + { + "date": "2026-08-15", + "country": "KG", + "installs": 31.0, + "spend": 15.5, + "cac": 2545.0 + }, + { + "date": "2026-08-16", + "country": "KG", + "installs": 26.0, + "spend": 13.0, + "cac": 15506.0 + }, + { + "date": "2026-08-17", + "country": "KG", + "installs": 30.0, + "spend": 15.0, + "cac": 0.0 + }, + { + "date": "2026-08-18", + "country": "KG", + "installs": 31.0, + "spend": 15.5, + "cac": 0.0 + }, + { + "date": "2026-08-19", + "country": "KG", + "installs": 22.0, + "spend": 11.0, + "cac": 2236.0 + }, + { + "date": "2026-08-20", + "country": "KG", + "installs": 28.0, + "spend": 14.0, + "cac": null + }, + { + "date": "2026-08-21", + "country": "KG", + "installs": 192.0, + "spend": 96.0, + "cac": null + }, + { + "date": "2026-08-22", + "country": "KG", + "installs": 210.0, + "spend": 105.0, + "cac": null + }, + { + "date": "2026-08-23", + "country": "KG", + "installs": 229.0, + "spend": 114.5, + "cac": null + }, + { + "date": "2026-08-10", + "country": "AM", + "installs": 124.0, + "spend": 62.0, + "cac": 7132.05 + }, + { + "date": "2026-08-11", + "country": "AM", + "installs": 136.0, + "spend": 68.0, + "cac": 7806.27 + }, + { + "date": "2026-08-12", + "country": "AM", + "installs": 126.0, + "spend": 63.0, + "cac": 3517.38 + }, + { + "date": "2026-08-13", + "country": "AM", + "installs": 124.0, + "spend": 62.0, + "cac": 7146.1 + }, + { + "date": "2026-08-14", + "country": "AM", + "installs": 112.0, + "spend": 56.0, + "cac": 1686.59 + }, + { + "date": "2026-08-15", + "country": "AM", + "installs": 19.0, + "spend": 9.5, + "cac": 0.0 + }, + { + "date": "2026-08-16", + "country": "AM", + "installs": 25.0, + "spend": 12.5, + "cac": 0.0 + }, + { + "date": "2026-08-17", + "country": "AM", + "installs": 30.0, + "spend": 15.0, + "cac": 0.0 + }, + { + "date": "2026-08-18", + "country": "AM", + "installs": 28.0, + "spend": 14.0, + "cac": 3987.14 + }, + { + "date": "2026-08-19", + "country": "AM", + "installs": 19.0, + "spend": 9.5, + "cac": 0.0 + }, + { + "date": "2026-08-20", + "country": "AM", + "installs": 15.0, + "spend": 7.5, + "cac": null + }, + { + "date": "2026-08-21", + "country": "AM", + "installs": 75.0, + "spend": 37.5, + "cac": null + }, + { + "date": "2026-08-22", + "country": "AM", + "installs": 67.0, + "spend": 33.5, + "cac": null + }, + { + "date": "2026-08-23", + "country": "AM", + "installs": 71.0, + "spend": 35.5, + "cac": null + } + ], + "definitions": { + "installs": "Google Sheet 各国家区块的 Install", + "spend": "Google Sheet 各国家区块的 Daily Spend(USD)", + "cac": "Google Sheet 各国家区块的 CAC from client report;汇总值为有数据日期的日均 CAC" + } +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..9446f12 --- /dev/null +++ b/index.html @@ -0,0 +1,30 @@ + + + + + +Ozon 国家投放表现 + + + +
+

Ozon · 国家投放表现

正在加载过去 14 天数据…

数据加载中
+
总安装
00000
+
14 天趋势
+
国家汇总
国家安装数消耗日均 CAC
每日明细
日期国家安装数消耗CAC
+ +
+ + + diff --git a/schedule.conf b/schedule.conf new file mode 100644 index 0000000..1d5e644 --- /dev/null +++ b/schedule.conf @@ -0,0 +1 @@ +24 diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..28513dd --- /dev/null +++ b/update.sh @@ -0,0 +1,139 @@ +#!/bin/sh +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +TMP_FILE="$SCRIPT_DIR/data.json.tmp" +OUTPUT_FILE="$SCRIPT_DIR/data.json" + +python3 - "$TMP_FILE" <<'PY' +import csv +import json +import re +import sys +import urllib.request +from collections import defaultdict +from datetime import datetime, timedelta +from zoneinfo import ZoneInfo + +CSV_URL = ( + "https://docs.google.com/spreadsheets/d/" + "1KaNOwaZ3DqjTXRK8XhF1OGF9qUmOh0NLXOgxVrh14h0/" + "export?format=csv&gid=1445753035" +) +TIMEZONE = ZoneInfo("Asia/Shanghai") +DAYS = 14 +BLOCKS = [ + {"country": "KZ", "date": 2, "geo": 3, "installs": 5, "spend": 8, "cac": 10}, + {"country": "UZ", "date": 14, "geo": 15, "installs": 17, "spend": 18, "cac": 20}, + {"country": "KG", "date": 25, "geo": 26, "installs": 28, "spend": 29, "cac": 31}, + {"country": "AM", "date": 35, "geo": 36, "installs": 38, "spend": 39, "cac": 41}, +] + + +def parse_date(value): + text = str(value or "").strip() + for fmt in ("%Y/%m/%d", "%Y-%m-%d", "%Y%m%d", "%m/%d/%Y"): + try: + return datetime.strptime(text, fmt).date() + except ValueError: + pass + match = re.fullmatch(r"(\d{4})[/.-](\d{1,2})[/.-](\d{1,2})", text) + if not match: + return None + try: + return datetime(*map(int, match.groups())).date() + except ValueError: + return None + + +def parse_number(value): + text = str(value or "").strip().replace("\xa0", "").replace(",", "") + if not text or text in {"-", "—"}: + return None + negative = text.startswith("(") and text.endswith(")") + cleaned = re.sub(r"[^0-9.\-]", "", text) + try: + number = float(cleaned) + return -number if negative else number + except ValueError: + return None + + +def cell(row, index): + return row[index] if index < len(row) else "" + + +request = urllib.request.Request(CSV_URL, headers={"User-Agent": "AgentStage-Ozon-Report/1.0"}) +with urllib.request.urlopen(request, timeout=90) as response: + text = response.read().decode("utf-8-sig") +rows = list(csv.reader(text.splitlines())) + +now = datetime.now(TIMEZONE) +end_date = now.date() - timedelta(days=1) +start_date = end_date - timedelta(days=DAYS - 1) +aggregated = defaultdict(lambda: {"installs": 0.0, "spend": 0.0, "cac_values": []}) + +for row in rows: + for block in BLOCKS: + day = parse_date(cell(row, block["date"])) + if day is None or not (start_date <= day <= end_date): + continue + country = str(cell(row, block["geo"])).strip().upper() or block["country"] + if country != block["country"]: + continue + key = (country, day.isoformat()) + installs = parse_number(cell(row, block["installs"])) + spend = parse_number(cell(row, block["spend"])) + cac = parse_number(cell(row, block["cac"])) + aggregated[key]["installs"] += installs or 0 + aggregated[key]["spend"] += spend or 0 + if cac is not None: + aggregated[key]["cac_values"].append(cac) + +records = [] +for country in [item["country"] for item in BLOCKS]: + for offset in range(DAYS): + day = (start_date + timedelta(days=offset)).isoformat() + values = aggregated.get((country, day), {"installs": 0, "spend": 0, "cac_values": []}) + cac_values = values["cac_values"] + records.append({ + "date": day, + "country": country, + "installs": round(values["installs"], 2), + "spend": round(values["spend"], 4), + "cac": round(sum(cac_values) / len(cac_values), 4) if cac_values else None, + }) + +summary = [] +for country in [item["country"] for item in BLOCKS]: + country_rows = [row for row in records if row["country"] == country] + cac_values = [row["cac"] for row in country_rows if row["cac"] is not None] + summary.append({ + "country": country, + "installs": round(sum(row["installs"] for row in country_rows), 2), + "spend": round(sum(row["spend"] for row in country_rows), 4), + "avg_cac": round(sum(cac_values) / len(cac_values), 4) if cac_values else None, + "cac_days": len(cac_values), + }) + +payload = { + "title": "Ozon · 过去14天国家投放表现", + "generated_at": now.isoformat(timespec="seconds"), + "timezone": "Asia/Shanghai", + "period": {"start": start_date.isoformat(), "end": end_date.isoformat(), "days": DAYS}, + "countries": [item["country"] for item in BLOCKS], + "summary": summary, + "records": records, + "definitions": { + "installs": "Google Sheet 各国家区块的 Install", + "spend": "Google Sheet 各国家区块的 Daily Spend(USD)", + "cac": "Google Sheet 各国家区块的 CAC from client report;汇总值为有数据日期的日均 CAC", + }, +} + +with open(sys.argv[1], "w", encoding="utf-8") as handle: + json.dump(payload, handle, ensure_ascii=False, indent=2) +PY + +mv "$TMP_FILE" "$OUTPUT_FILE" +echo "Updated $OUTPUT_FILE"