|
|
@@ -133,6 +133,18 @@ def calc_head_drill_data(date=None):
|
|
|
for crowd in crowd_list:
|
|
|
crowd_df = agg_all[agg_all['crowd'] == crowd]
|
|
|
result['all'][crowd] = {}
|
|
|
+ # 计算整体汇总
|
|
|
+ total_exp = int(crowd_df['exp'].sum())
|
|
|
+ total_share = crowd_df['share_cnt'].sum()
|
|
|
+ total_return = crowd_df['return_n_uv'].sum()
|
|
|
+ total_new_exp = crowd_df['new_exposure_cnt'].sum()
|
|
|
+ result['all'][crowd]['_total'] = {
|
|
|
+ 'exp': total_exp,
|
|
|
+ 'str': round(total_share / (total_exp + 1), 4),
|
|
|
+ 'ros': round(total_return / (total_share + 1), 4),
|
|
|
+ 'rovn': round(total_return / (total_exp + 1), 4),
|
|
|
+ 'vov': round(total_new_exp / (total_exp + 1), 4),
|
|
|
+ }
|
|
|
for _, row in crowd_df.iterrows():
|
|
|
result['all'][crowd][row['rec_cate2']] = {
|
|
|
'exp': int(row['exp']),
|
|
|
@@ -148,6 +160,18 @@ def calc_head_drill_data(date=None):
|
|
|
for crowd in crowd_list:
|
|
|
crowd_df = agg[(agg['head_cate2'] == head_cate) & (agg['crowd'] == crowd)]
|
|
|
result[head_cate][crowd] = {}
|
|
|
+ # 计算该头部品类下的整体汇总
|
|
|
+ total_exp = int(crowd_df['exp'].sum())
|
|
|
+ total_share = crowd_df['share_cnt'].sum()
|
|
|
+ total_return = crowd_df['return_n_uv'].sum()
|
|
|
+ total_new_exp = crowd_df['new_exposure_cnt'].sum()
|
|
|
+ result[head_cate][crowd]['_total'] = {
|
|
|
+ 'exp': total_exp,
|
|
|
+ 'str': round(total_share / (total_exp + 1), 4),
|
|
|
+ 'ros': round(total_return / (total_share + 1), 4),
|
|
|
+ 'rovn': round(total_return / (total_exp + 1), 4),
|
|
|
+ 'vov': round(total_new_exp / (total_exp + 1), 4),
|
|
|
+ }
|
|
|
for _, row in crowd_df.iterrows():
|
|
|
result[head_cate][crowd][row['rec_cate2']] = {
|
|
|
'exp': int(row['exp']),
|
|
|
@@ -706,18 +730,27 @@ html_content = f"""<!DOCTYPE html>
|
|
|
return;
|
|
|
}}
|
|
|
|
|
|
- // 为每个人群计算 Top N
|
|
|
+ // 为每个人群计算 Top N 和整体汇总
|
|
|
const crowdTopN = {{}};
|
|
|
+ const crowdTotal = {{}};
|
|
|
crowdList.forEach(crowd => {{
|
|
|
const items = [];
|
|
|
if (data[crowd]) {{
|
|
|
for (const cat in data[crowd]) {{
|
|
|
- items.push({{
|
|
|
- cat: cat,
|
|
|
- sortVal: data[crowd][cat][sortBy] || 0,
|
|
|
- showVal: data[crowd][cat][showMetric] || 0,
|
|
|
- exp: data[crowd][cat].exp || 0
|
|
|
- }});
|
|
|
+ if (cat === '_total') {{
|
|
|
+ // 保存整体汇总
|
|
|
+ crowdTotal[crowd] = {{
|
|
|
+ exp: data[crowd][cat].exp || 0,
|
|
|
+ showVal: data[crowd][cat][showMetric] || 0
|
|
|
+ }};
|
|
|
+ }} else {{
|
|
|
+ items.push({{
|
|
|
+ cat: cat,
|
|
|
+ sortVal: data[crowd][cat][sortBy] || 0,
|
|
|
+ showVal: data[crowd][cat][showMetric] || 0,
|
|
|
+ exp: data[crowd][cat].exp || 0
|
|
|
+ }});
|
|
|
+ }}
|
|
|
}}
|
|
|
}}
|
|
|
items.sort((a, b) => b.sortVal - a.sortVal);
|
|
|
@@ -776,6 +809,18 @@ html_content = f"""<!DOCTYPE html>
|
|
|
if (crowdTopN[crowd].length === 0) {{
|
|
|
html += `<tr><td colspan="${{colSpan}}" style="color:#999">无数据</td></tr>`;
|
|
|
}} else {{
|
|
|
+ // 先添加整体汇总行 (rn=0)
|
|
|
+ if (crowdTotal[crowd]) {{
|
|
|
+ const totalExp = parseInt(crowdTotal[crowd].exp).toLocaleString();
|
|
|
+ const totalMetric = (crowdTotal[crowd].showVal * 100).toFixed(1) + '%';
|
|
|
+ html += `<tr style="background:#f5f5f5;font-weight:bold">
|
|
|
+ <td class="rn">0</td>
|
|
|
+ <td class="cat" style="background:#e0e0e0">整体</td>
|
|
|
+ <td class="val">${{totalExp}}</td>
|
|
|
+ ${{showMetric !== 'exp' ? `<td class="val">${{totalMetric}}</td>` : ''}}
|
|
|
+ </tr>`;
|
|
|
+ }}
|
|
|
+ // 添加 Top N 品类
|
|
|
crowdTopN[crowd].forEach((item, i) => {{
|
|
|
const expDisplay = parseInt(item.exp).toLocaleString();
|
|
|
const metricDisplay = (item.showVal * 100).toFixed(1) + '%';
|