|
|
@@ -0,0 +1,118 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+"""
|
|
|
+渠道品类数据提取 + 审计脚本(货架电商复盘技能·阶段二可复用工具)
|
|
|
+
|
|
|
+用法:每期复盘改 INPUT / OUT_JSON / OUT_MD 三个路径即可重跑。
|
|
|
+输入要求:源表格式与「传统电商各平台业绩情况26年9.9.xlsx」一致
|
|
|
+ - 第1行=月份表头,第2行=指标行(GSV/GP/GP率),A列=渠道→品类树
|
|
|
+ - 渠道层下一行是「全店」,品类行按标签匹配
|
|
|
+
|
|
|
+输出:
|
|
|
+ 1. JSON:渠道 → 品类 → {月份+指标: 值},含「未列出品类」差额
|
|
|
+ 2. Markdown 审计表:加和校验结果(品类合计 vs 全店),差额>8% 标记需确认
|
|
|
+"""
|
|
|
+import openpyxl, json, sys
|
|
|
+
|
|
|
+INPUT = "/Users/dou/Desktop/BOP/管理会/传统电商各平台业绩情况26年9.9.xlsx"
|
|
|
+SHEET = "各渠道品类销售数据"
|
|
|
+OUT_JSON = "/Users/dou/Desktop/BOP/7-8月复盘/process/渠道品类数据_4-8月.json"
|
|
|
+OUT_MD = "/Users/dou/Desktop/BOP/7-8月复盘/process/审计报告.md"
|
|
|
+MONTHS = ["5月", "6月", "7月", "8月"] # ← 每期改这里
|
|
|
+THRESHOLD = 8.0 # 差额占比告警阈值 %
|
|
|
+
|
|
|
+CHANS = ["天猫", "京东", "猫超", "唯品", "拼多多", "平台分销", "社交电商"]
|
|
|
+# 渠道命名口径(企业已确认):源表「平台分销」行=电商分销;
|
|
|
+# 对外汇报的「平台分销」= 社交电商 + 电商分销 合并。MERGE_GROUPS 生成合并行。
|
|
|
+MERGE_GROUPS = {"平台分销(合并)": ["社交电商", "平台分销"]}
|
|
|
+CATS = ["色修", "修护", "胖管香氛", "圆管香氛", "儿童", "儿童牙膏", "黑蜂",
|
|
|
+ "其他牙膏", "条装漱口水", "瓶装漱口水", "口喷", "其他品类"]
|
|
|
+
|
|
|
+def main():
|
|
|
+ wb = openpyxl.load_workbook(INPUT, data_only=True) # 非 read_only:规避维度缓存失真
|
|
|
+ ws = wb[SHEET]
|
|
|
+ rows = list(ws.iter_rows(values_only=True))
|
|
|
+ wb.close()
|
|
|
+
|
|
|
+ # ---- 1. 动态列映射:月份(第1行) × 指标(第2行) → 列号 ----
|
|
|
+ month_col, cur = {}, None
|
|
|
+ for i, v in enumerate(rows[0]):
|
|
|
+ s = str(v) if v is not None else ""
|
|
|
+ if "月" in s or "Q" in s:
|
|
|
+ cur = s
|
|
|
+ m2 = str(rows[1][i]) if i < len(rows[1]) and rows[1][i] is not None else ""
|
|
|
+ if cur and m2 in ("GSV", "GP", "GP率"):
|
|
|
+ month_col[(cur, m2)] = i
|
|
|
+ want = [(m, k) for m in MONTHS for k in ("GSV", "GP")]
|
|
|
+ have = {k: v for k, v in month_col.items() if k in want}
|
|
|
+ missing = [k for k in want if k not in month_col]
|
|
|
+ if missing:
|
|
|
+ print(f"⚠️ 源表缺少这些月份/指标列: {missing}")
|
|
|
+
|
|
|
+ # ---- 2. 行扫描:渠道→全店/品类(标签匹配,不硬编码行号)----
|
|
|
+ result, i, cur_chan = {}, 2, None
|
|
|
+ while i < len(rows):
|
|
|
+ lab = str(rows[i][0]).strip() if rows[i][0] is not None else ""
|
|
|
+ if lab in CHANS:
|
|
|
+ cur_chan, result[cur_chan] = lab, {}
|
|
|
+ elif cur_chan and (lab == "全店" or lab in CATS):
|
|
|
+ rec = {}
|
|
|
+ for k, ci in have.items():
|
|
|
+ v = rows[i][ci] if ci < len(rows[i]) else None
|
|
|
+ if isinstance(v, (int, float)):
|
|
|
+ rec[f"{k[0]}{k[1]}"] = round(v, 2)
|
|
|
+ if rec:
|
|
|
+ key = "儿童" if lab == "儿童牙膏" else lab # 口径统一
|
|
|
+ result[cur_chan].setdefault(key, {}).update(rec)
|
|
|
+ i += 1
|
|
|
+
|
|
|
+ # ---- 3.5 合并口径行(如 平台分销(合并)=社交电商+电商分销)----
|
|
|
+ for merged_name, parts in MERGE_GROUPS.items():
|
|
|
+ merged = {}
|
|
|
+ for ch in parts:
|
|
|
+ for cat, rec in result.get(ch, {}).items():
|
|
|
+ for k, v in rec.items():
|
|
|
+ if k.endswith("差额"): # 差额不可跨渠道相加,跳过
|
|
|
+ continue
|
|
|
+ merged.setdefault(cat, {})[k] = round(merged.get(cat, {}).get(k, 0) + v, 2)
|
|
|
+ if merged:
|
|
|
+ result[merged_name] = merged
|
|
|
+
|
|
|
+ # ---- 4. 审计:品类合计 vs 全店 → 未列出品类差额 ----
|
|
|
+ audit = []
|
|
|
+ for ch, cats in result.items():
|
|
|
+ if "全店" not in cats:
|
|
|
+ audit.append((ch, "缺全店行", None, None)); continue
|
|
|
+ for suffix in ("GSV", "GP"):
|
|
|
+ m = f"{MONTHS[-1]}{suffix}" # 审计最后一个月
|
|
|
+ tot = cats["全店"].get(m)
|
|
|
+ s = sum(c.get(m, 0) for k, c in cats.items() if k != "全店")
|
|
|
+ if tot:
|
|
|
+ gap = round(tot - s, 2)
|
|
|
+ cats.setdefault("未列出品类", {})[f"{MONTHS[-1]}{suffix}差额"] = gap
|
|
|
+ pct = abs(gap) / tot * 100
|
|
|
+ audit.append((ch, m, gap, pct))
|
|
|
+
|
|
|
+ with open(OUT_JSON, "w", encoding="utf-8") as f:
|
|
|
+ json.dump(result, f, ensure_ascii=False, indent=1)
|
|
|
+
|
|
|
+ # ---- 4. 输出审计报告 ----
|
|
|
+ lines = ["# 数据提取审计报告", "",
|
|
|
+ f"- 源表:`{INPUT}` → sheet `{SHEET}`",
|
|
|
+ f"- 月份:{'、'.join(MONTHS)}|提取单元格映射 {len(have)} 列", "",
|
|
|
+ "| 渠道 | 指标 | 未列出品类差额(元) | 占比 | 判定 |", "|---|---|---:|---:|---|"]
|
|
|
+ for ch, m, gap, pct in audit:
|
|
|
+ if gap is None:
|
|
|
+ lines.append(f"| {ch} | {m} | - | - | ⚠️ 结构异常 |")
|
|
|
+ else:
|
|
|
+ flag = "⚠️需确认" if pct > THRESHOLD else "OK"
|
|
|
+ lines.append(f"| {ch} | {m} | {gap:,.0f} | {pct:.1f}% | {flag} |")
|
|
|
+ lines += ["", "> 差额 = 全店行 − 已列品类合计,即源表未单列的小品类(如牙刷等)。",
|
|
|
+ "> 总量一律取「全店」行;品类行仅用于结构与环比,不用于推总量。"]
|
|
|
+ with open(OUT_MD, "w", encoding="utf-8") as f:
|
|
|
+ f.write("\n".join(lines))
|
|
|
+
|
|
|
+ print("\n".join(lines[7:]))
|
|
|
+ print(f"\n✅ 数据 → {OUT_JSON}\n✅ 审计 → {OUT_MD}")
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ main()
|