init: voypace ad report
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
# 在 ads-center 上每天运行:取前一天3渠道数据 -> 合并 mtg_data.json -> 生成矩阵 -> git push
|
||||
# 缺表渠道(Adsterra/TrafficStars/Eagllwin)由 excel_fetch.py 从运营 Excel 补数(可选,找不到就跳过)
|
||||
# 由 launchd 定时触发。Python: /Users/ads-center/py314env/bin/python
|
||||
import sqlite3, json, subprocess, sys, os
|
||||
from collections import defaultdict
|
||||
from datetime import date, timedelta
|
||||
|
||||
DB = "/Users/ads-center/Documents/database/report_api.db"
|
||||
REPO = "/Users/ads-center/voypace-report-sync/voypace-ad-report"
|
||||
|
||||
def log(m): print(f"[sync] {m}", flush=True)
|
||||
|
||||
# 目标日期:前一天(可用参数覆盖,便于补数)
|
||||
TARGET = sys.argv[1] if len(sys.argv) > 1 else (date.today() - timedelta(days=1)).isoformat()
|
||||
|
||||
os.chdir(REPO)
|
||||
# 先 pull 最新,避免冲突
|
||||
subprocess.run(["git", "pull", "--rebase", "-q"], cwd=REPO)
|
||||
|
||||
camps = json.load(open("campaigns.json"))
|
||||
CAMPS = {r["campid"]: {"product": r["product"], "pkg": r["pkg"]} for r in camps}
|
||||
IDS = list(CAMPS.keys())
|
||||
|
||||
conn = sqlite3.connect(DB); conn.row_factory = sqlite3.Row
|
||||
agg = defaultdict(lambda: {"impression":0,"clicks":0,"conversions":0,"spend":0.0})
|
||||
meta = {}
|
||||
|
||||
def cid_of(t):
|
||||
t = str(t or "")
|
||||
for c in IDS:
|
||||
if c in t: return c
|
||||
return None
|
||||
|
||||
# Mintegral(广告单元名可能落在 Offer_Name 或 Campaign_Id,两字段都匹配)
|
||||
like = " OR ".join([f"Offer_Name LIKE '%{c}%' OR Campaign_Id LIKE '%{c}%'" for c in IDS])
|
||||
for r in conn.execute(f"SELECT Date d,Offer_Name nm,Campaign_Id cmp,Location loc,Impression imp,Click clk,Conversion cv,Spend sp FROM mintegral WHERE Date=? AND ({like})", (TARGET,)):
|
||||
c = cid_of(r["nm"]) or cid_of(r["cmp"])
|
||||
if not c: continue
|
||||
k = (r["d"],"Mintegral",c,(r["loc"] or "").upper()); a=agg[k]
|
||||
a["impression"]+=int(r["imp"] or 0); a["clicks"]+=int(r["clk"] or 0)
|
||||
a["conversions"]+=int(r["cv"] or 0); a["spend"]+=float(r["sp"] or 0)
|
||||
meta[k]=(r["nm"] or r["cmp"],CAMPS[c])
|
||||
|
||||
# Bigo
|
||||
like = " OR ".join([f"campaignName LIKE '%{c}%'" for c in IDS])
|
||||
for r in conn.execute(f"SELECT aggregateTime d,campaignName nm,country loc,impression imp,click clk,real_result_conversion cv,totalCost cost FROM bigo WHERE aggregateTime=? AND ({like})", (TARGET,)):
|
||||
c = cid_of(r["nm"])
|
||||
if not c: continue
|
||||
k = (r["d"],"Bigo",c,(r["loc"] or "").upper()); a=agg[k]
|
||||
a["impression"]+=int(r["imp"] or 0); a["clicks"]+=int(r["clk"] or 0)
|
||||
a["conversions"]+=int(r["cv"] or 0); a["spend"]+=float(r["cost"] or 0)/100.0
|
||||
meta[k]=(r["nm"],CAMPS[c])
|
||||
|
||||
# MediaGo (spend = cpc*click)
|
||||
like = " OR ".join([f"campaign_name LIKE '%{c}%'" for c in IDS])
|
||||
for r in conn.execute(f"SELECT date d,campaign_name nm,adset_country loc,vimp imp,click clk,cv cvn,cpc FROM mediago WHERE date=? AND ({like})", (TARGET,)):
|
||||
c = cid_of(r["nm"])
|
||||
if not c: continue
|
||||
k = (r["d"],"MediaGo",c,(r["loc"] or "").upper()); a=agg[k]
|
||||
clk=int(r["clk"] or 0)
|
||||
a["impression"]+=int(r["imp"] or 0); a["clicks"]+=clk
|
||||
a["conversions"]+=int(r["cvn"] or 0); a["spend"]+=float(r["cpc"] or 0)*clk
|
||||
meta[k]=(r["nm"],CAMPS[c])
|
||||
conn.close()
|
||||
|
||||
new_rows=[]
|
||||
for k,a in agg.items():
|
||||
dt,ch,c,loc=k; nm,info=meta[k]
|
||||
imp,clk,cv=a["impression"],a["clicks"],a["conversions"]
|
||||
new_rows.append({"date":dt,"channel":ch,"campid":c,"offer_name":nm,"product":info["product"],"pkg":info["pkg"],
|
||||
"location":loc,"bid_type":"","impression":imp,"clicks":clk,"conversions":cv,
|
||||
"ctr":round(clk/imp,5) if imp else 0.0,"cvr":round(cv/clk,5) if clk else 0.0,"spend":round(a["spend"],2)})
|
||||
log(f"{TARGET} DB三渠道新增匹配 {len(new_rows)} 条")
|
||||
|
||||
# 可选:从运营 Excel 补齐缺表渠道(Adsterra/TrafficStars/Eagllwin 等)
|
||||
excel_rows = []
|
||||
try:
|
||||
ef = os.path.join(REPO, "excel_fetch.py")
|
||||
if os.path.exists(ef):
|
||||
p = subprocess.run([sys.executable, ef], cwd=REPO, capture_output=True, text=True)
|
||||
if p.returncode == 0 and p.stdout.strip():
|
||||
excel_rows = json.loads(p.stdout)
|
||||
# 只保留目标日期,保持“每天增量”语义(补数全量跑时可去掉此过滤)
|
||||
excel_rows = [r for r in excel_rows if r.get("date") == TARGET]
|
||||
else:
|
||||
log(f"Excel 补数无输出/失败:{p.stderr[:120]}")
|
||||
except Exception as e:
|
||||
log(f"Excel 补数跳过:{e}")
|
||||
if excel_rows:
|
||||
log(f"Excel 补数新增 {len(excel_rows)} 条")
|
||||
new_rows += excel_rows
|
||||
|
||||
# 合并(按 date+channel+campid+location 去重)
|
||||
existing = json.load(open("mtg_data.json")) if os.path.exists("mtg_data.json") else []
|
||||
merged = {(r["date"],r.get("channel",""),r["campid"],r["location"]): r for r in existing}
|
||||
for r in new_rows:
|
||||
merged[(r["date"],r["channel"],r["campid"],r["location"])] = r
|
||||
data = sorted(merged.values(), key=lambda x:(x["date"],x.get("channel",""),-x["impression"]))
|
||||
json.dump(data, open("mtg_data.json","w"), ensure_ascii=False, indent=2)
|
||||
log(f"合并后共 {len(data)} 条")
|
||||
|
||||
# 重新生成矩阵状态(近3天单日最高展示>=100 且产品订单为Active -> Active)
|
||||
orders = json.load(open("orders.json"))
|
||||
pkg_status={}
|
||||
for o in orders:
|
||||
for p in str(o["pkg"]).replace(" ","").split("/"):
|
||||
if p: pkg_status[p]=o.get("status","Pause")
|
||||
g=defaultdict(lambda: defaultdict(int))
|
||||
for r in data: g[(r["channel"],r["pkg"])][r["date"]]+=r["impression"]
|
||||
alldates=sorted(set(r["date"] for r in data)); last3=alldates[-3:]
|
||||
ov={}
|
||||
for (ch,pkg),days in g.items():
|
||||
ps=pkg_status.get(pkg,"Pause")
|
||||
if ps!="Active": ov[f"{ch}|{pkg}"]="Pause"; continue
|
||||
total=sum(days.values()); rmax=max([days.get(dt,0) for dt in last3]) if last3 else 0
|
||||
ov[f"{ch}|{pkg}"]= "none" if total==0 else ("Active" if rmax>=100 else "Pause")
|
||||
json.dump(ov, open("matrix_overrides.json","w"), ensure_ascii=False, indent=2)
|
||||
log("矩阵状态已刷新")
|
||||
|
||||
# git push
|
||||
subprocess.run(["git","add","mtg_data.json","matrix_overrides.json"], cwd=REPO)
|
||||
r=subprocess.run(["git","commit","-m",f"data: daily sync {TARGET}"], cwd=REPO, capture_output=True, text=True)
|
||||
if "nothing to commit" in (r.stdout+r.stderr):
|
||||
log("无变化,跳过 push")
|
||||
else:
|
||||
p=subprocess.run(["git","push","-q","origin","main"], cwd=REPO, capture_output=True, text=True)
|
||||
log("已 push" if p.returncode==0 else f"push失败: {p.stderr[:200]}")
|
||||
log("完成")
|
||||
Reference in New Issue
Block a user