#!/usr/bin/env python3 """从「菜品销售明细」导出反推菜品库(6月起使用) 用法: python3 make_menu_lib.py <月度账务目录> 背景:build_analysis.py 依赖菜品库做部门归类,但本地菜品库是旧月份快照, 当月新上的 SKU 不在库里会掉进关键词兜底、容易归错。 菜品销售明细自带「菜品大类/菜品小类」= POS 系统里的真实归类,用它生成菜品库覆盖率 100%。 输出: 大梦_可能实验室_{店}店_菜品库_自销售明细生成_{YYYYMM}.xlsx (文件名符合 build_analysis.py 的 glob 模式,会被自动读到) """ import openpyxl, glob, sys, os, re from collections import Counter, defaultdict BASE = (sys.argv[1] if len(sys.argv) > 1 else ".").rstrip("/") def build(store): fs = glob.glob(f"{BASE}/*{store}店__菜品销售明细*.xlsx") if not fs: print(f" [{store}] 未找到菜品销售明细,跳过") return wb = openpyxl.load_workbook(fs[0], data_only=True) ws = wb["已销售"] # 表头在第 3 行 hr = None for r in range(1, 8): row = [ws.cell(r, c).value for c in range(1, ws.max_column + 1)] if any(v and "菜品大类" in str(v) for v in row): hr = r H = [str(v).strip() if v else "" for v in row] break if hr is None: print(f" [{store}] 找不到含「菜品大类」的表头行,跳过") return ci_nm = H.index("菜品名称") + 1 ci_d = H.index("菜品大类") + 1 ci_x = H.index("菜品小类") + 1 # 同名多类时取众数(如"深烘拿铁"既有 咖啡/经典 也有 经典咖啡) name2cats = defaultdict(Counter) ym = None ci_date = H.index("营业日期") + 1 if "营业日期" in H else None for r in range(hr + 1, ws.max_row + 1): nm = ws.cell(r, ci_nm).value if not nm: continue d = ws.cell(r, ci_d).value x = ws.cell(r, ci_x).value if d: name2cats[str(nm).strip()][(str(d).strip(), str(x).strip() if x else "")] += 1 if ym is None and ci_date: dv = str(ws.cell(r, ci_date).value or "") m = re.search(r"(\d{4})[/-](\d{2})", dv) if m: ym = m.group(1) + m.group(2) wb.close() conflicts = {n: c for n, c in name2cats.items() if len(c) > 1} out = openpyxl.Workbook() ws2 = out.active ws2.title = "菜品" ws2.append(["菜品编码(SPUID)", "菜品名称", "基础分类"]) for n, c in sorted(name2cats.items()): (d, x), _ = c.most_common(1)[0] ws2.append(["", n, f"{d}/{x}" if x else d]) fn = f"{BASE}/大梦_可能实验室_{store}店_菜品库_自销售明细生成_{ym or 'latest'}.xlsx" out.save(fn) cats = Counter() for n, c in name2cats.items(): cats[c.most_common(1)[0][0][0]] += 1 print(f" [{store}] {len(name2cats)} 个 SKU → {os.path.basename(fn)}") print(f" 大类分布: {dict(cats.most_common(8))}") if conflicts: print(f" 同名多类 {len(conflicts)} 个(已取众数,正常现象): " + ", ".join(list(conflicts)[:4])) if __name__ == "__main__": print(f"从菜品销售明细生成菜品库 — {BASE}") for st in ["滨江", "西湖"]: build(st) print("完成。接着跑: python3 build_analysis.py <目录> ")