88 lines
3.3 KiB
Python
88 lines
3.3 KiB
Python
#!/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 <目录> <YYYY-MM>")
|