Files
minis-analyse-skill/skills/swads-minis-launch-watch/scripts/summary.py
T
blueming3andClaude Opus 5.5 9f9e70ea2d feat: minis launch, cold-start watch and delivery watch skills
Three TikTok Minis skills for Codex and Claude Code, with the cold-start
playbook and five benchmark accounts through 88776-1 (2026-09-30).
install.sh links them into ~/.codex/skills and ~/.claude/skills.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:10:54 +08:00

91 lines
4.4 KiB
Python

"""Gather everything a lifecycle summary needs after an account is stopped or punished.
python3 summary.py --account <external_account_id> --date 2026-09-29 --dir ~/minis-watch/88794-9 [--window today|yesterday]
Prints: hourly curve (direct TikTok), per-ad totals, the milestone events, the loss/peak points from
the timeline, and the same-lifecycle comparison against references/benchmarks.json. The narrative is
written by the agent from this output using references/summary-template.md.
"""
import argparse
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import swads_mcp as mcp # noqa: E402
ap = argparse.ArgumentParser()
ap.add_argument("--account", required=True)
ap.add_argument("--date", required=True)
ap.add_argument("--dir", required=True)
ap.add_argument("--window", default="today", help="ad breakdown window: today or yesterday (the day being summarised)")
a = ap.parse_args()
D = os.path.expanduser(a.dir)
h = mcp.call("runtime_hourly_metrics", {"external_account_id": a.account, "date": a.date})
hours = {}
for r in h.get("rows") or []:
x = hours.setdefault(int(r["hour"]), [0.0, 0.0])
x[0] += float(r.get("spend") or 0); x[1] += float(r.get("ad_revenue_d0") or 0)
print(f"== 每小时(直读 TikTok,{h.get('reporting_timezone')},{h.get('date')})")
cs = cr = 0.0
for k in sorted(hours):
s, r = hours[k]
if s <= 0:
continue
cs += s; cr += r
print(f"{k:02d} 点 消耗 {s:8.2f} ROAS {r / s:5.2f} 累计 {cs:8.2f}/{cr:8.2f} = {cr / cs:.2f}")
print(f"合计 消耗 {cs:.2f} 回收 {cr:.2f} ROAS {(cr / cs if cs else 0):.2f} 盈亏 {cr - cs:+.2f}")
q = mcp.call("metrics_semantic_query", {"external_account_id": a.account, "query": {
"query_kind": "ad_breakdown", "metrics": ["spend", "impressions", "clicks", "d0_ad_revenue"],
"dimensions": ["external_campaign_name", "external_ad_name", "external_ad_id"],
"window": a.window, "exclude_zero_spend": True, "order_by_metric": "spend"}})
cols = [c["name"] for c in q.get("columns") or []]
print("\n== 每条广告(按消耗)")
for r in q.get("rows") or []:
d = dict(zip(cols, r)); s = d["spend"] or 0; rv = d["d0_ad_revenue"] or 0
ctr = d["clicks"] / d["impressions"] * 100 if d.get("impressions") else 0
print(f"{d['external_campaign_name']} | {' '.join(d['external_ad_name'].split(' ')[-2:])} | {d['external_ad_id']} | 消耗 {s:.2f} | ROAS {(rv / s if s else 0):.2f} | CTR {ctr:.1f}%")
ev = os.path.join(D, "events.jsonl")
if os.path.exists(ev):
print("\n== 生命周期事件")
for line in open(ev):
x = json.loads(line)
if x["kind"] not in ("广告审核",):
print(x["t"], x["kind"], x["text"][:160])
tl = os.path.join(D, "timeline.jsonl")
if os.path.exists(tl):
snaps = [json.loads(l) for l in open(tl) if l.strip() and l.startswith('{"t": "' + a.date)]
snaps = [s for s in snaps if s.get("spend")]
if snaps:
worst = max(snaps, key=lambda s: s["spend"] - s["rev"])
best = max(snaps, key=lambda s: s["rev"] / s["spend"] if s["spend"] >= 20 else 0)
print(f"\n== 关键点\n亏得最多:{worst['t']}(数据截至 {worst['fresh']})消耗 {worst['spend']} 回收 {worst['rev']} 亏 {worst['spend'] - worst['rev']:.2f}")
print(f"累计 ROAS 最高:{best['t']} {best['rev'] / best['spend']:.2f}")
bench = json.load(open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "references", "benchmarks.json")))
mine = sorted((k, v) for k, v in hours.items() if v[0] > 0)
ramp = [k for k, v in mine if v[0] >= 20]
print("\n== 同生命周期对比(以各户第一个消耗 ≥ 20 的整点为起量第 1 小时,列累计 ROAS)")
rows = [("本户", ramp[0] if ramp else None, dict(mine))]
for b in bench["accounts"]:
hs = {int(k): v for k, v in b["hourly"].items()}
rh = [k for k in sorted(hs) if hs[k][0] >= 20]
rows.append((f"{b['short']}({b['kind']})", rh[0] if rh else None, hs))
print("起量第 N 小时 | " + " | ".join(r[0] for r in rows))
for n in range(0, 10):
cells = []
for _, r0, hs in rows:
if r0 is None:
cells.append("-"); continue
upto = [v for k, v in hs.items() if k <= r0 + n]
s = sum(v[0] for v in upto); r = sum(v[1] for v in upto)
cells.append(f"{r / s:.2f}" if s and (r0 + n) in hs else "-")
if any(c != "-" for c in cells):
print(f"第 {n + 1} 小时 | " + " | ".join(cells))
for b in bench["accounts"]:
print(f"- {b['short']} {b['date']}:{b['outcome']}。{b['lesson']}")