Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aab3f279d8 | ||
|
|
9e152bd0bf | ||
|
|
89ca7531f0 | ||
|
|
51603ba251 | ||
|
|
e2882a4916 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"title": "King · 当月 Campaign 表现",
|
||||
"generated_at": "2026-08-27T15:26:33+08:00",
|
||||
"generated_at": "2026-08-27T15:50:27+08:00",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"period": {
|
||||
"start": "2026-08-01",
|
||||
@@ -29,7 +29,7 @@
|
||||
"completion": {
|
||||
"revenue": 27.47,
|
||||
"gp": 19.52,
|
||||
"gp_rate": 71.05
|
||||
"gp_rate": null
|
||||
},
|
||||
"time_progress_percent": 83.87,
|
||||
"month_days": 31,
|
||||
@@ -1196,6 +1196,6 @@
|
||||
"performance_cost": "Cost 取 Sands Report 2.0 的 Cost 合计,需本地抓取",
|
||||
"performance_gp": "GP = Revenue − Cost;GP rate = GP ÷ Revenue × 100",
|
||||
"performance_target": "Target 来自 Oversea FY2627 Monthly FFR 表 King 行",
|
||||
"performance_completion": "完成率 = 实际 ÷ Target × 100;Target 为整月目标"
|
||||
"performance_completion": "完成率 = 实际 ÷ Target × 100,仅用于 Revenue 与 GP;GP rate 为比率,只看与目标的百分点差距"
|
||||
}
|
||||
}
|
||||
+18
-9
@@ -36,14 +36,6 @@ td.name{max-width:340px;overflow:hidden;text-overflow:ellipsis}
|
||||
</style></head><body><main class="shell">
|
||||
<header class="hero"><div><h1>King · Campaign 表现</h1><p id="period">正在加载数据…</p></div><div class="updated" id="updated">数据加载中</div></header>
|
||||
|
||||
<div class="scope">
|
||||
<label>Campaign <select id="scope"></select></label>
|
||||
<label>App <select id="appFilter"></select></label>
|
||||
<label>搜索 <input id="search" type="search" placeholder="Campaign 关键词" autocomplete="off"></label>
|
||||
<span id="scopeHint"></span>
|
||||
</div>
|
||||
<div class="grid" id="kpis"></div>
|
||||
|
||||
<section class="panel" id="perfPanel" hidden>
|
||||
<div class="panel-head"><div class="panel-title">Target 完成情况</div><span class="hint" id="perfHint"></span></div>
|
||||
<div class="table-wrap"><table>
|
||||
@@ -52,6 +44,14 @@ td.name{max-width:340px;overflow:hidden;text-overflow:ellipsis}
|
||||
<div class="legend" id="perfNote"></div>
|
||||
</section>
|
||||
|
||||
<div class="scope">
|
||||
<label>Campaign <select id="scope"></select></label>
|
||||
<label>App <select id="appFilter"></select></label>
|
||||
<label>搜索 <input id="search" type="search" placeholder="Campaign 关键词" autocomplete="off"></label>
|
||||
<span id="scopeHint"></span>
|
||||
</div>
|
||||
<div class="grid" id="kpis"></div>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-head"><div class="panel-title">Install 与 Cost 趋势</div></div>
|
||||
<div class="chart-wrap"><svg id="mainChart" class="chart" viewBox="0 0 1120 380" role="img" aria-label="Install 与 Cost 趋势图"></svg><div class="tip" id="mainTip"></div></div>
|
||||
@@ -232,10 +232,17 @@ function renderPerformance(){
|
||||
const color=diff>=0?'#23a673':'#e4568b';
|
||||
return `<span style="color:${color}">${diff>=0?'+':''}${num(diff,2)} pt</span>`;
|
||||
};
|
||||
/* 比率不做完成率,只看与目标的百分点差距 */
|
||||
const ratePoints=(actualRate,targetRate)=>{
|
||||
if(!ok(actualRate)||!ok(targetRate))return '—';
|
||||
const diff=Number(actualRate)-Number(targetRate);
|
||||
const color=diff>=0?'#23a673':'#e4568b';
|
||||
return `<span style="color:${color}" title="实际 GP rate 与目标的差距">${diff>=0?'+':''}${num(diff,2)} pt</span>`;
|
||||
};
|
||||
const rows=[
|
||||
['Revenue',usd(target.revenue),usd(actual.revenue),pct(done.revenue),gap(done.revenue)],
|
||||
['GP',usd(target.gp),usd(actual.gp),pct(done.gp),gap(done.gp)],
|
||||
['GP rate',pct(target.gp_rate),pct(actual.gp_rate),pct(done.gp_rate),'—']
|
||||
['GP rate',pct(target.gp_rate),pct(actual.gp_rate),'—',ratePoints(actual.gp_rate,target.gp_rate)]
|
||||
];
|
||||
$('perfRows').innerHTML=rows.map(row=>`<tr><td>${row[0]}</td><td>${row[1]}</td><td>${row[2]}</td><td>${row[3]}</td><td>${row[4]}</td></tr>`).join('')
|
||||
+`<tr class="total-row"><td>Cost(Sands)</td><td>—</td><td>${usd(actual.cost)}</td><td>—</td><td>—</td></tr>`;
|
||||
@@ -249,6 +256,8 @@ function renderPerformance(){
|
||||
else{
|
||||
if(sands.fetched_at)notes.push(`Sands Cost 抓取于 ${esc(String(sands.fetched_at).replace('T',' '))},不随每日自动更新`);
|
||||
if(sands.scope)notes.push(`Cost 范围:${esc(sands.scope)}`);
|
||||
if(sands.carried_over)notes.push('<b style="color:#b45309">Cost 沿用上次抓取值,需本地重新抓取才会更新</b>');
|
||||
if(sands.stale)notes.push(`<b style="color:#b42318">Cost 数据滞后:抓取于 ${esc(sands.fetched_date||'')},周期到 ${esc((report.period||{}).end||'')},滞后约 ${num(sands.lag_days)} 天,GP 会偏高</b>`);
|
||||
if(Array.isArray(sands.missing_pkgs)&&sands.missing_pkgs.length)
|
||||
notes.push(`<b style="color:#b42318">Cost 不完整,缺少包:${esc(sands.missing_pkgs.join(', '))}</b>`);
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
24
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# 平台按 schedule.conf 每 24 小时执行一次。
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
TMP_FILE="$SCRIPT_DIR/data.json.tmp"
|
||||
OUTPUT_FILE="$SCRIPT_DIR/data.json"
|
||||
|
||||
rm -f "$TMP_FILE"
|
||||
python3 "$SCRIPT_DIR/update_report_data.py" "$TMP_FILE"
|
||||
|
||||
mv "$TMP_FILE" "$OUTPUT_FILE"
|
||||
echo "Updated $OUTPUT_FILE"
|
||||
+28
-2
@@ -364,6 +364,18 @@ def load_sands_cost():
|
||||
]
|
||||
path = next((item for item in candidates if item.exists()), None)
|
||||
if path is None:
|
||||
# 服务器上没有 sands_cost.json(Sands 需登录),沿用上一版 data.json 里的值
|
||||
previous = here.parent / "data.json"
|
||||
if previous.exists():
|
||||
try:
|
||||
sands = (json.loads(previous.read_text(encoding="utf-8")).get("performance") or {}).get("sands")
|
||||
except Exception:
|
||||
sands = None
|
||||
if sands and sands.get("cost"):
|
||||
sands = dict(sands)
|
||||
sands["carried_over"] = True
|
||||
print(f" Sands Cost:沿用上一版 {sands['cost']}(抓取于 {sands.get('fetched_at') or '未知'})")
|
||||
return sands
|
||||
print(" Sands Cost:未找到 sands_cost.json,将留空")
|
||||
return None
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
@@ -413,7 +425,8 @@ def build_performance(revenue, sands, targets, period_days, month_days):
|
||||
"completion": {
|
||||
"revenue": ratio(revenue, targets.get("revenue") if targets else None),
|
||||
"gp": ratio(gp, targets.get("gp") if targets else None),
|
||||
"gp_rate": ratio(gp_rate, target_gp_rate),
|
||||
# GP rate 是比率,不计算完成率;页面显示与目标的百分点差距
|
||||
"gp_rate": None,
|
||||
},
|
||||
"time_progress_percent": round(period_days / month_days * 100, 2) if month_days else None,
|
||||
"month_days": month_days,
|
||||
@@ -445,6 +458,19 @@ def build_payload(now):
|
||||
except Exception as error:
|
||||
print(f" Target 读取失败({error});请把该表共享给服务账号或开放链接查看")
|
||||
sands = load_sands_cost()
|
||||
# 判断 Cost 是否滞后于数据周期
|
||||
if sands and sands.get("fetched_at"):
|
||||
try:
|
||||
fetched = datetime.fromisoformat(sands["fetched_at"]).date()
|
||||
except ValueError:
|
||||
fetched = None
|
||||
if fetched:
|
||||
lag = (end_date - fetched).days + 1
|
||||
sands["fetched_date"] = fetched.isoformat()
|
||||
sands["lag_days"] = max(0, lag)
|
||||
sands["stale"] = lag > 0
|
||||
if sands["stale"]:
|
||||
print(f" 警告:Sands Cost 抓取于 {fetched},可能未覆盖到 {end_date},滞后约 {lag} 天")
|
||||
|
||||
# 消耗表里可能出现 Cost_Client 未覆盖的 Campaign,一并纳入
|
||||
for key, _day in wide_costs:
|
||||
@@ -546,7 +572,7 @@ def build_payload(now):
|
||||
"performance_cost": "Cost 取 Sands Report 2.0 的 Cost 合计,需本地抓取",
|
||||
"performance_gp": "GP = Revenue − Cost;GP rate = GP ÷ Revenue × 100",
|
||||
"performance_target": "Target 来自 Oversea FY2627 Monthly FFR 表 King 行",
|
||||
"performance_completion": "完成率 = 实际 ÷ Target × 100;Target 为整月目标",
|
||||
"performance_completion": "完成率 = 实际 ÷ Target × 100,仅用于 Revenue 与 GP;GP rate 为比率,只看与目标的百分点差距",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user