Link filters to selected pkg
This commit is contained in:
+2
-2
@@ -3,9 +3,9 @@
|
|||||||
</style></head><body><main class="wrap" id="app"><div class="error">正在加载数据…</div></main><script>
|
</style></head><body><main class="wrap" id="app"><div class="error">正在加载数据…</div></main><script>
|
||||||
const $=s=>document.querySelector(s),fmt={int:n=>n==null?'—':Math.round(n).toLocaleString('en-US'),money:n=>n==null?'—':'$'+n.toLocaleString('en-US',{minimumFractionDigits:2,maximumFractionDigits:2}),pct:n=>n==null?'—':(n*100).toFixed(2)+'%'};let DATA,grain='week',sort={key:'spend',dir:-1};
|
const $=s=>document.querySelector(s),fmt={int:n=>n==null?'—':Math.round(n).toLocaleString('en-US'),money:n=>n==null?'—':'$'+n.toLocaleString('en-US',{minimumFractionDigits:2,maximumFractionDigits:2}),pct:n=>n==null?'—':(n*100).toFixed(2)+'%'};let DATA,grain='week',sort={key:'spend',dir:-1};
|
||||||
fetch('data.json',{cache:'no-store'}).then(r=>{if(!r.ok)throw Error('data.json '+r.status);return r.json()}).then(d=>{DATA=d;boot()}).catch(e=>{$('#app').innerHTML='<div class="error">数据加载失败:'+e.message+'</div>'});
|
fetch('data.json',{cache:'no-store'}).then(r=>{if(!r.ok)throw Error('data.json '+r.status);return r.json()}).then(d=>{DATA=d;boot()}).catch(e=>{$('#app').innerHTML='<div class="error">数据加载失败:'+e.message+'</div>'});
|
||||||
function boot(){let m=DATA.meta,pkgs=uniq('pkg'),geos=uniq('geo'),plats=uniq('platform');$('#app').innerHTML=`<section class="top"><div><div class="eyebrow">Manta · Experiment Tracking</div><h1>在投 pkg 实验组 / 对照组表现</h1><div class="sub">覆盖 ${m.date_start||'—'} 至 ${m.date_end||'—'} · ${m.active_campaign_count} 个 Active campaign</div></div><div class="fresh">更新于 ${new Date(m.generated_at).toLocaleString('zh-CN',{timeZone:'Asia/Shanghai'})}</div></section><section class="filters"><label>pkg<select id="pkg"><option value="">全部 pkg</option>${pkgs.map(opt).join('')}</select></label><label>组别<select id="group"><option value="">实验组 + 对照组</option><option>实验组</option><option>对照组</option></select></label><label>GEO<select id="geo"><option value="">全部 GEO</option>${geos.map(opt).join('')}</select></label><label>Platform<select id="platform"><option value="">全部 Platform</option>${plats.map(opt).join('')}</select></label><label>开始日期<input id="start" type="date" value="${m.date_start||''}"></label><label>结束日期<input id="end" type="date" value="${m.date_end||''}"></label><button id="reset">重置筛选</button></section><section class="period-bar"><div><h2 style="margin:0">周期指标表现</h2><div class="desc">按 campaign(adping_id)查看</div></div><div style="display:flex;gap:10px;align-items:center"><div class="seg"><button id="week" class="on">Weekly</button><button id="month">Monthly</button></div><select id="period" class="period-select"></select></div></section><section id="kpis" class="kpis"></section><section id="compare" class="compare"></section>${tableSection('Campaign 周期表现','period-desc','period-body',false)}<section class="panel table-panel"><div class="table-head"><div><h2>每天实验组 / 对照组表现</h2><div class="desc" id="daily-desc"></div></div><div class="daily-tools"><label>日期<select id="day"></select></label></div></div><div class="table-wrap"><table><thead>${head(true)}</thead><tbody id="daily-body"></tbody></table></div></section>${tableSection('pkg 汇总','pkg-desc','pkg-body','pkg')}<section class="method"><div><strong>计算口径</strong><br>CTR = Click / Impression × 100%;CVR = Conversion / Click × 100%;IVR = Conversion / Impression × 100%;CAC = Spend / Conversion;eCPI = Spend / Conversion。聚合时先汇总基础量,再重算比率。</div><div><strong>分组与基础信息</strong><br>pkg、adping_id、实验/对照组、GEO、Platform、投放状态来自钉钉「Campaign info」;仅纳入 G 列为 Active 的 campaign。表现数据来自 Tableau「DSP Daily Data」。</div></section>`;['pkg','group','geo','platform','start','end'].forEach(id=>$('#'+id).onchange=filtersChanged);$('#reset').onclick=()=>{['pkg','group','geo','platform'].forEach(id=>$('#'+id).value='');$('#start').value=m.date_start||'';$('#end').value=m.date_end||'';filtersChanged()};$('#week').onclick=()=>setGrain('week');$('#month').onclick=()=>setGrain('month');$('#period').onchange=render;$('#day').onchange=renderDaily;document.querySelectorAll('th[data-k]').forEach(th=>th.onclick=()=>{let k=th.dataset.k;sort=k===sort.key?{key:k,dir:-sort.dir}:{key:k,dir:k==='pkg'?1:-1};render()});filtersChanged()}
|
function boot(){let m=DATA.meta,pkgs=uniq('pkg'),geos=uniq('geo'),plats=uniq('platform');$('#app').innerHTML=`<section class="top"><div><div class="eyebrow">Manta · Experiment Tracking</div><h1>在投 pkg 实验组 / 对照组表现</h1><div class="sub">覆盖 ${m.date_start||'—'} 至 ${m.date_end||'—'} · ${m.active_campaign_count} 个 Active campaign</div></div><div class="fresh">更新于 ${new Date(m.generated_at).toLocaleString('zh-CN',{timeZone:'Asia/Shanghai'})}</div></section><section class="filters"><label>pkg<select id="pkg"><option value="">全部 pkg</option>${pkgs.map(opt).join('')}</select></label><label>组别<select id="group"><option value="">实验组 + 对照组</option><option>实验组</option><option>对照组</option></select></label><label>GEO<select id="geo"><option value="">全部 GEO</option>${geos.map(opt).join('')}</select></label><label>Platform<select id="platform"><option value="">全部 Platform</option>${plats.map(opt).join('')}</select></label><label>开始日期<input id="start" type="date" value="${m.date_start||''}"></label><label>结束日期<input id="end" type="date" value="${m.date_end||''}"></label><button id="reset">重置筛选</button></section><section class="period-bar"><div><h2 style="margin:0">周期指标表现</h2><div class="desc">按 campaign(adping_id)查看</div></div><div style="display:flex;gap:10px;align-items:center"><div class="seg"><button id="week" class="on">Weekly</button><button id="month">Monthly</button></div><select id="period" class="period-select"></select></div></section><section id="kpis" class="kpis"></section><section id="compare" class="compare"></section>${tableSection('Campaign 周期表现','period-desc','period-body',false)}<section class="panel table-panel"><div class="table-head"><div><h2>每天实验组 / 对照组表现</h2><div class="desc" id="daily-desc"></div></div><div class="daily-tools"><label>日期<select id="day"></select></label></div></div><div class="table-wrap"><table><thead>${head(true)}</thead><tbody id="daily-body"></tbody></table></div></section>${tableSection('pkg 汇总','pkg-desc','pkg-body','pkg')}<section class="method"><div><strong>计算口径</strong><br>CTR = Click / Impression × 100%;CVR = Conversion / Click × 100%;IVR = Conversion / Impression × 100%;CAC = Spend / Conversion;eCPI = Spend / Conversion。聚合时先汇总基础量,再重算比率。</div><div><strong>分组与基础信息</strong><br>pkg、adping_id、实验/对照组、GEO、Platform、投放状态来自钉钉「Campaign info」;仅纳入 G 列为 Active 的 campaign。表现数据来自 Tableau「DSP Daily Data」。</div></section>`;$('#pkg').onchange=()=>applyPkgContext();['group','geo','platform','start','end'].forEach(id=>$('#'+id).onchange=filtersChanged);$('#reset').onclick=()=>{$('#pkg').value='';applyPkgContext()};$('#week').onclick=()=>setGrain('week');$('#month').onclick=()=>setGrain('month');$('#period').onchange=render;$('#day').onchange=renderDaily;document.querySelectorAll('th[data-k]').forEach(th=>th.onclick=()=>{let k=th.dataset.k;sort=k===sort.key?{key:k,dir:-sort.dir}:{key:k,dir:k==='pkg'?1:-1};render()});filtersChanged()}
|
||||||
function tableSection(title,desc,body,type){return `<section class="panel table-panel"><div class="table-head"><div><h2>${title}</h2><div class="desc" id="${desc}"></div></div><div class="sub">点击表头排序</div></div><div class="table-wrap"><table><thead>${type==='pkg'?pkgHead():head()}</thead><tbody id="${body}"></tbody></table></div></section>`}function uniq(k){return [...new Set(DATA.campaigns.map(x=>x[k]).filter(Boolean))].sort()}function opt(v){return `<option>${esc(v)}</option>`}function head(day=false){let c=[['pkg','pkg'],['adping_id','Campaign'],['group','组别'],['geo','GEO'],['platform','Platform']];if(day)c.unshift(['date','日期']);return `<tr>${c.concat(metrics()).map(([k,l])=>`<th data-k="${k}">${l}</th>`).join('')}</tr>`}function pkgHead(){return `<tr>${[['pkg','pkg'],...metrics()].map(([k,l])=>`<th data-k="${k}">${l}</th>`).join('')}</tr>`}function metrics(){return [['impression','Impression'],['click','Click'],['conversion','Conversion'],['spend','Spend'],['ctr','CTR'],['cvr','CVR'],['ivr','IVR'],['cac','CAC'],['ecpi','eCPI']]}
|
function tableSection(title,desc,body,type){return `<section class="panel table-panel"><div class="table-head"><div><h2>${title}</h2><div class="desc" id="${desc}"></div></div><div class="sub">点击表头排序</div></div><div class="table-wrap"><table><thead>${type==='pkg'?pkgHead():head()}</thead><tbody id="${body}"></tbody></table></div></section>`}function uniq(k){return [...new Set(DATA.campaigns.map(x=>x[k]).filter(Boolean))].sort()}function opt(v){return `<option>${esc(v)}</option>`}function head(day=false){let c=[['pkg','pkg'],['adping_id','Campaign'],['group','组别'],['geo','GEO'],['platform','Platform']];if(day)c.unshift(['date','日期']);return `<tr>${c.concat(metrics()).map(([k,l])=>`<th data-k="${k}">${l}</th>`).join('')}</tr>`}function pkgHead(){return `<tr>${[['pkg','pkg'],...metrics()].map(([k,l])=>`<th data-k="${k}">${l}</th>`).join('')}</tr>`}function metrics(){return [['impression','Impression'],['click','Click'],['conversion','Conversion'],['spend','Spend'],['ctr','CTR'],['cvr','CVR'],['ivr','IVR'],['cac','CAC'],['ecpi','eCPI']]}
|
||||||
function filtersChanged(){fillPeriods();fillDays();render()}function setGrain(g){grain=g;$('#week').classList.toggle('on',g==='week');$('#month').classList.toggle('on',g==='month');fillPeriods();render()}function baseRows(){let f={pkg:$('#pkg').value,group:$('#group').value,geo:$('#geo').value,platform:$('#platform').value},s=$('#start').value,e=$('#end').value;return DATA.campaign_daily.filter(r=>(!f.pkg||r.pkg===f.pkg)&&(!f.group||r.group===f.group)&&(!f.geo||r.geo===f.geo)&&(!f.platform||r.platform===f.platform)&&(!s||r.date>=s)&&(!e||r.date<=e))}function pkey(date){if(grain==='month')return date.slice(0,7);let d=new Date(date+'T00:00:00Z'),n=d.getUTCDay()||7;d.setUTCDate(d.getUTCDate()-n+1);return d.toISOString().slice(0,10)}function plabel(k){if(grain==='month')return k;let e=new Date(k+'T00:00:00Z');e.setUTCDate(e.getUTCDate()+6);return k+' — '+e.toISOString().slice(0,10)}function fillPeriods(){let cur=$('#period').value,ks=[...new Set(baseRows().map(r=>pkey(r.date)))].sort().reverse();$('#period').innerHTML=ks.map(k=>`<option value="${k}">${plabel(k)}</option>`).join('');if(ks.includes(cur))$('#period').value=cur}function fillDays(){let cur=$('#day').value,ds=[...new Set(baseRows().map(r=>r.date))].sort().reverse();$('#day').innerHTML=ds.map(opt).join('');if(ds.includes(cur))$('#day').value=cur}
|
function filtersChanged(){fillPeriods();fillDays();render()}function setFilterOptions(id,label,values){let el=$('#'+id),cur=el.value;el.innerHTML=`<option value="">${label}</option>`+values.map(opt).join('');el.value=values.includes(cur)?cur:''}function applyPkgContext(){let pkg=$('#pkg').value,campaigns=DATA.campaigns.filter(r=>!pkg||r.pkg===pkg),rows=DATA.campaign_daily.filter(r=>!pkg||r.pkg===pkg),dates=rows.map(r=>r.date).filter(Boolean).sort(),start=dates[0]||DATA.meta.date_start||'',end=dates.at(-1)||DATA.meta.date_end||'';setFilterOptions('group','实验组 + 对照组',[...new Set(campaigns.map(r=>r.group).filter(Boolean))].sort());setFilterOptions('geo','全部 GEO',[...new Set(campaigns.map(r=>r.geo).filter(Boolean))].sort());setFilterOptions('platform','全部 Platform',[...new Set(campaigns.map(r=>r.platform).filter(Boolean))].sort());['start','end'].forEach(id=>{$('#'+id).min=start;$('#'+id).max=end});$('#start').value=start;$('#end').value=end;filtersChanged()}function setGrain(g){grain=g;$('#week').classList.toggle('on',g==='week');$('#month').classList.toggle('on',g==='month');fillPeriods();render()}function baseRows(){let f={pkg:$('#pkg').value,group:$('#group').value,geo:$('#geo').value,platform:$('#platform').value},s=$('#start').value,e=$('#end').value;return DATA.campaign_daily.filter(r=>(!f.pkg||r.pkg===f.pkg)&&(!f.group||r.group===f.group)&&(!f.geo||r.geo===f.geo)&&(!f.platform||r.platform===f.platform)&&(!s||r.date>=s)&&(!e||r.date<=e))}function pkey(date){if(grain==='month')return date.slice(0,7);let d=new Date(date+'T00:00:00Z'),n=d.getUTCDay()||7;d.setUTCDate(d.getUTCDate()-n+1);return d.toISOString().slice(0,10)}function plabel(k){if(grain==='month')return k;let e=new Date(k+'T00:00:00Z');e.setUTCDate(e.getUTCDate()+6);return k+' — '+e.toISOString().slice(0,10)}function fillPeriods(){let cur=$('#period').value,ks=[...new Set(baseRows().map(r=>pkey(r.date)))].sort().reverse();$('#period').innerHTML=ks.map(k=>`<option value="${k}">${plabel(k)}</option>`).join('');if(ks.includes(cur))$('#period').value=cur}function fillDays(){let cur=$('#day').value,ds=[...new Set(baseRows().map(r=>r.date))].sort().reverse();$('#day').innerHTML=ds.map(opt).join('');if(ds.includes(cur))$('#day').value=cur}
|
||||||
function calc(x){x.ctr=x.impression?x.click/x.impression:null;x.cvr=x.click?x.conversion/x.click:null;x.ivr=x.impression?x.conversion/x.impression:null;x.cac=x.conversion?x.spend/x.conversion:null;x.ecpi=x.cac;return x}function agg(rows,keys){let m=new Map;rows.forEach(r=>{let id=keys.map(k=>r[k]).join('|'),x=m.get(id);if(!x){x={};keys.forEach(k=>x[k]=r[k]);Object.assign(x,{impression:0,click:0,conversion:0,spend:0});m.set(id,x)}metrics().slice(0,4).forEach(([k])=>x[k]+=r[k])});return [...m.values()].map(calc)}function total(rows){return calc(rows.reduce((a,r)=>{metrics().slice(0,4).forEach(([k])=>a[k]+=r[k]);return a},{impression:0,click:0,conversion:0,spend:0}))}
|
function calc(x){x.ctr=x.impression?x.click/x.impression:null;x.cvr=x.click?x.conversion/x.click:null;x.ivr=x.impression?x.conversion/x.impression:null;x.cac=x.conversion?x.spend/x.conversion:null;x.ecpi=x.cac;return x}function agg(rows,keys){let m=new Map;rows.forEach(r=>{let id=keys.map(k=>r[k]).join('|'),x=m.get(id);if(!x){x={};keys.forEach(k=>x[k]=r[k]);Object.assign(x,{impression:0,click:0,conversion:0,spend:0});m.set(id,x)}metrics().slice(0,4).forEach(([k])=>x[k]+=r[k])});return [...m.values()].map(calc)}function total(rows){return calc(rows.reduce((a,r)=>{metrics().slice(0,4).forEach(([k])=>a[k]+=r[k]);return a},{impression:0,click:0,conversion:0,spend:0}))}
|
||||||
function render(){let rows=baseRows(),pk=$('#period').value,pr=rows.filter(r=>pkey(r.date)===pk),campaign=agg(pr,['pkg','adping_id','group','geo','platform']),sum=total(pr);$('#period-desc').textContent=(pk?plabel(pk):'当前筛选无数据')+' · '+campaign.length+' 个 campaign';$('#kpis').innerHTML=[['Impression',fmt.int(sum.impression),'曝光量'],['Click',fmt.int(sum.click),'CTR '+fmt.pct(sum.ctr)],['Conversion',fmt.int(sum.conversion),'CVR '+fmt.pct(sum.cvr)],['Spend',fmt.money(sum.spend),'总消耗'],['CAC / eCPI',fmt.money(sum.cac),'IVR '+fmt.pct(sum.ivr)]].map(x=>`<article class="card"><div class="label">${x[0]}</div><div class="value">${x[1]}</div><div class="hint">${x[2]}</div></article>`).join('');renderCompare(pr);renderRows($('#period-body'),campaign);renderDaily();renderPkg(rows)}function renderCompare(rows){let map=Object.fromEntries(agg(rows,['group']).map(x=>[x.group,x]));$('#compare').innerHTML=['实验组','对照组'].map((g,i)=>{let x=map[g]||total([]);return `<article class="panel group-card ${i?'control':''}"><h3><span class="badge ${i?'ctl':'exp'}">${g}</span></h3><div class="mini">${[['CTR',fmt.pct(x.ctr)],['CVR',fmt.pct(x.cvr)],['IVR',fmt.pct(x.ivr)],['CAC',fmt.money(x.cac)],['eCPI',fmt.money(x.ecpi)]].map(v=>`<span>${v[0]}<b>${v[1]}</b></span>`).join('')}</div></article>`}).join('')}function renderDaily(){let d=$('#day').value,items=agg(baseRows().filter(r=>r.date===d),['date','pkg','adping_id','group','geo','platform']);$('#daily-desc').textContent=(d||'当前筛选无数据')+' · '+items.length+' 个 campaign';renderRows($('#daily-body'),items)}function renderPkg(rows){let items=agg(rows,['pkg']).sort((a,b)=>b.spend-a.spend);$('#pkg-desc').textContent=items.length+' 个 pkg · 当前日期与维度筛选汇总';$('#pkg-body').innerHTML=items.map(x=>`<tr><td class="pkg">${esc(x.pkg)}</td>${cells(x)}</tr>`).join('')||empty(10)}
|
function render(){let rows=baseRows(),pk=$('#period').value,pr=rows.filter(r=>pkey(r.date)===pk),campaign=agg(pr,['pkg','adping_id','group','geo','platform']),sum=total(pr);$('#period-desc').textContent=(pk?plabel(pk):'当前筛选无数据')+' · '+campaign.length+' 个 campaign';$('#kpis').innerHTML=[['Impression',fmt.int(sum.impression),'曝光量'],['Click',fmt.int(sum.click),'CTR '+fmt.pct(sum.ctr)],['Conversion',fmt.int(sum.conversion),'CVR '+fmt.pct(sum.cvr)],['Spend',fmt.money(sum.spend),'总消耗'],['CAC / eCPI',fmt.money(sum.cac),'IVR '+fmt.pct(sum.ivr)]].map(x=>`<article class="card"><div class="label">${x[0]}</div><div class="value">${x[1]}</div><div class="hint">${x[2]}</div></article>`).join('');renderCompare(pr);renderRows($('#period-body'),campaign);renderDaily();renderPkg(rows)}function renderCompare(rows){let map=Object.fromEntries(agg(rows,['group']).map(x=>[x.group,x]));$('#compare').innerHTML=['实验组','对照组'].map((g,i)=>{let x=map[g]||total([]);return `<article class="panel group-card ${i?'control':''}"><h3><span class="badge ${i?'ctl':'exp'}">${g}</span></h3><div class="mini">${[['CTR',fmt.pct(x.ctr)],['CVR',fmt.pct(x.cvr)],['IVR',fmt.pct(x.ivr)],['CAC',fmt.money(x.cac)],['eCPI',fmt.money(x.ecpi)]].map(v=>`<span>${v[0]}<b>${v[1]}</b></span>`).join('')}</div></article>`}).join('')}function renderDaily(){let d=$('#day').value,items=agg(baseRows().filter(r=>r.date===d),['date','pkg','adping_id','group','geo','platform']);$('#daily-desc').textContent=(d||'当前筛选无数据')+' · '+items.length+' 个 campaign';renderRows($('#daily-body'),items)}function renderPkg(rows){let items=agg(rows,['pkg']).sort((a,b)=>b.spend-a.spend);$('#pkg-desc').textContent=items.length+' 个 pkg · 当前日期与维度筛选汇总';$('#pkg-body').innerHTML=items.map(x=>`<tr><td class="pkg">${esc(x.pkg)}</td>${cells(x)}</tr>`).join('')||empty(10)}
|
||||||
function renderRows(el,rows){rows.sort((a,b)=>{let x=a[sort.key],y=b[sort.key];if(x==null)x=-Infinity;if(y==null)y=-Infinity;return typeof x==='string'?x.localeCompare(y)*sort.dir:(x-y)*sort.dir});el.innerHTML=rows.map(x=>`<tr>${x.date?`<td>${x.date}</td>`:''}<td class="pkg">${esc(x.pkg)}</td><td>${esc(x.adping_id)}</td><td><span class="badge ${x.group==='实验组'?'exp':'ctl'}">${x.group}</span></td><td>${esc(x.geo||'—')}</td><td>${esc(x.platform||'—')}</td>${cells(x)}</tr>`).join('')||empty(rows.some(x=>x.date)?15:14)}function cells(x){return `<td>${fmt.int(x.impression)}</td><td>${fmt.int(x.click)}</td><td>${fmt.int(x.conversion)}</td><td>${fmt.money(x.spend)}</td><td>${fmt.pct(x.ctr)}</td><td>${fmt.pct(x.cvr)}</td><td>${fmt.pct(x.ivr)}</td><td>${fmt.money(x.cac)}</td><td>${fmt.money(x.ecpi)}</td>`}function empty(n){return `<tr><td colspan="${n}" class="empty">当前筛选无数据</td></tr>`}function esc(s){return String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))}
|
function renderRows(el,rows){rows.sort((a,b)=>{let x=a[sort.key],y=b[sort.key];if(x==null)x=-Infinity;if(y==null)y=-Infinity;return typeof x==='string'?x.localeCompare(y)*sort.dir:(x-y)*sort.dir});el.innerHTML=rows.map(x=>`<tr>${x.date?`<td>${x.date}</td>`:''}<td class="pkg">${esc(x.pkg)}</td><td>${esc(x.adping_id)}</td><td><span class="badge ${x.group==='实验组'?'exp':'ctl'}">${x.group}</span></td><td>${esc(x.geo||'—')}</td><td>${esc(x.platform||'—')}</td>${cells(x)}</tr>`).join('')||empty(rows.some(x=>x.date)?15:14)}function cells(x){return `<td>${fmt.int(x.impression)}</td><td>${fmt.int(x.click)}</td><td>${fmt.int(x.conversion)}</td><td>${fmt.money(x.spend)}</td><td>${fmt.pct(x.ctr)}</td><td>${fmt.pct(x.cvr)}</td><td>${fmt.pct(x.ivr)}</td><td>${fmt.money(x.cac)}</td><td>${fmt.money(x.ecpi)}</td>`}function empty(n){return `<tr><td colspan="${n}" class="empty">当前筛选无数据</td></tr>`}function esc(s){return String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))}
|
||||||
|
|||||||
Reference in New Issue
Block a user