Publish top10 KA FFR report

This commit is contained in:
SHAREit
2026-09-01 15:22:45 +08:00
parent caf98a8c09
commit 16be038cf0
3 changed files with 615 additions and 232 deletions
+571 -228
View File
File diff suppressed because it is too large Load Diff
+19 -1
View File
@@ -33,6 +33,8 @@ tr.link{cursor:pointer}tr.link:hover{background:#f8fafc}
.bar>b{position:relative;display:block;text-align:right;padding-right:4px;font-size:11px;line-height:16px} .bar>b{position:relative;display:block;text-align:right;padding-right:4px;font-size:11px;line-height:16px}
.bar.over>i{background:var(--blue)}.bar.low>i{background:var(--pink)} .bar.over>i{background:var(--blue)}.bar.low>i{background:var(--pink)}
.notes{font-size:11px;color:#94a3b8;margin-top:8px;line-height:1.8} .notes{font-size:11px;color:#94a3b8;margin-top:8px;line-height:1.8}
.alert{margin-top:14px;padding:13px 16px;border-radius:12px;border:1px solid #fecaca;background:#fef2f2;color:#b42318;font-size:12px;line-height:1.8}
.alert b{color:#991b1b}
.error{margin:40px auto;max-width:700px;text-align:center;padding:44px;color:#b42318} .error{margin:40px auto;max-width:700px;text-align:center;padding:44px;color:#b42318}
.footer{text-align:center;color:#94a3b8;font-size:11px;margin:18px} .footer{text-align:center;color:#94a3b8;font-size:11px;margin:18px}
@media(max-width:1100px){.grid{grid-template-columns:repeat(3,1fr)}} @media(max-width:1100px){.grid{grid-template-columns:repeat(3,1fr)}}
@@ -40,6 +42,7 @@ tr.link{cursor:pointer}tr.link:hover{background:#f8fafc}
</style></head><body><main class="shell"> </style></head><body><main class="shell">
<header class="hero"><div><h1>Top10 KA · FFR 与 GP</h1><p id="period">正在加载数据…</p></div><div class="updated" id="updated">数据加载中</div></header> <header class="hero"><div><h1>Top10 KA · FFR 与 GP</h1><p id="period">正在加载数据…</p></div><div class="updated" id="updated">数据加载中</div></header>
<div class="tabs" id="tabs"></div> <div class="tabs" id="tabs"></div>
<div id="alert"></div>
<div id="view"></div> <div id="view"></div>
<div class="footer">Revenue 来自各 KA 日报表客户口径消耗 · Cost 来自 Sands · GP = Revenue − Cost</div> <div class="footer">Revenue 来自各 KA 日报表客户口径消耗 · Cost 来自 Sands · GP = Revenue − Cost</div>
</main> </main>
@@ -254,6 +257,21 @@ function renderView(){
drawChart(item); drawChart(item);
} }
/* Cost 抓取时间晚于数据周期时提示,避免误用滞后的 GP */
function renderAlert(){
const sands=report.sands||{},p=report.period||{},box=$('alert');
const messages=[];
if(!sands.by_pkg&&!ok(sands.total_cost))
messages.push('<b>Cost 数据缺失</b>:尚未抓取 Sands,GP 与 GP rate 无法计算。');
else if(sands.stale)
messages.push(`<b>Cost 数据滞后</b>:Sands 抓取于 ${esc(sands.fetched_date||String(sands.fetched_at||'').slice(0,10))},`
+`数据周期到 ${esc(p.end||'—')},滞后约 ${num(sands.lag_days)} 天。`
+'GP 与完成率会偏高,请重新抓取后再用于汇报。');
if(sands.carried_over)
messages.push('<b>Cost 沿用上次抓取值</b>:本次更新未包含新的 Sands 数据。');
box.innerHTML=messages.length?`<div class="alert">${messages.join('<br>')}</div>`:'';
}
function render(){ function render(){
const p=report.period||{},sands=report.sands||{}; const p=report.period||{},sands=report.sands||{};
$('period').textContent=`${p.start||'—'} 至 ${p.end||'—'} · ${num(p.days)}/${num(p.month_days)} 天 · 时间进度 ${pct(p.time_progress_percent)}`; $('period').textContent=`${p.start||'—'} 至 ${p.end||'—'} · ${num(p.days)}/${num(p.month_days)} 天 · 时间进度 ${pct(p.time_progress_percent)}`;
@@ -261,7 +279,7 @@ function render(){
if(sands.fetched_at)parts.push(`Sands Cost 抓取于 ${esc(String(sands.fetched_at).replace('T',' '))}`); if(sands.fetched_at)parts.push(`Sands Cost 抓取于 ${esc(String(sands.fetched_at).replace('T',' '))}`);
if(sands.carried_over)parts.push('Cost 沿用上次抓取值'); if(sands.carried_over)parts.push('Cost 沿用上次抓取值');
$('updated').innerHTML=parts.join('<br>'); $('updated').innerHTML=parts.join('<br>');
renderTabs();renderView(); renderAlert();renderTabs();renderView();
} }
window.addEventListener('resize',()=>{if(report&&current!=='overview')drawChart(findAccount(current))}); window.addEventListener('resize',()=>{if(report&&current!=='overview')drawChart(findAccount(current))});
+25 -3
View File
@@ -576,13 +576,35 @@ def main():
sheets = config["sheets"] sheets = config["sheets"]
now = datetime.now(TIMEZONE) now = datetime.now(TIMEZONE)
end_date = now.date() - timedelta(days=1) # 默认当月 1 号至昨天;--month YYYY-MM 可指定月份(如已结束的月份则取整月)
start_date = end_date.replace(day=1) month_arg = next((item.split("=", 1)[-1] for item in sys.argv[2:] if item.startswith("--month")), None)
if month_arg:
year, month = (int(part) for part in month_arg.split("-")[:2])
start_date = datetime(year, month, 1).date()
else:
start_date = (now.date() - timedelta(days=1)).replace(day=1)
month_end = (start_date.replace(day=28) + timedelta(days=4)).replace(day=1) - timedelta(days=1)
end_date = min(month_end, now.date() - timedelta(days=1))
if end_date < start_date:
raise SystemExit(f"指定月份 {start_date:%Y-%m} 尚无已结束的日期")
days = (end_date - start_date).days + 1 days = (end_date - start_date).days + 1
month_days = ((start_date.replace(day=28) + timedelta(days=4)).replace(day=1) - timedelta(days=1)).day month_days = month_end.day
print(f"取数范围:{start_date} 至 {end_date}({days}/{month_days} 天)") print(f"取数范围:{start_date} 至 {end_date}({days}/{month_days} 天)")
sands = load_sands() sands = load_sands()
# 判断 Cost 是否滞后于数据周期:抓取日在周期结束日当天或更早,说明未覆盖完整周期
if sands and sands.get("fetched_at"):
try:
fetched = datetime.fromisoformat(sands["fetched_at"]).date()
except ValueError:
fetched = None
if fetched:
sands["fetched_date"] = fetched.isoformat()
lag = (end_date - fetched).days + 1
sands["lag_days"] = max(0, lag)
sands["stale"] = lag > 0
if sands["stale"]:
print(f"警告:Sands Cost 抓取于 {fetched},可能未覆盖到 {end_date},滞后约 {lag} 天")
try: try:
target_rows, _ = fetch_rows(config["target_spreadsheet_id"], config["target_gid"]) target_rows, _ = fetch_rows(config["target_spreadsheet_id"], config["target_gid"])
targets = read_targets(target_rows) targets = read_targets(target_rows)