初始发布: 21 个 skills (Claude Code / Codex / DSH)

This commit is contained in:
2026-08-14 01:51:44 +08:00
commit ea0857bedb
129 changed files with 35566 additions and 0 deletions
+533
View File
@@ -0,0 +1,533 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""大梦 by 可能实验室 — 月度营收分析引擎(按月参数化)
用法: python3 build_analysis.py <月度账务目录> [YYYY-MM] # 默认 2026-05
生成 大梦可能实验室_{N}月营收分析_西湖店vs滨江店.xlsx13 sheets+ _analysis_summary.json
并打印工资所需的部门/班次/部门×班次业绩三元组。
⚠️ 跑本脚本前先跑 make_menu_lib.py 生成当月菜品库,否则新上 SKU 会落到关键词兜底。
数据源(同目录):
POS 店内订单明细 (菜品明细 / 订单明细 / 优惠明细 sheets)
全渠道订单明细 (含 餐段)
菜品库 (SKU→基础分类)
团购收益(美团大梦) + 新版收益(点评/可能实验室)
"""
import openpyxl, warnings, glob, sys, re
from collections import defaultdict
warnings.filterwarnings('ignore')
import sys as _sys, calendar as _cal
# 用法: python3 build_analysis.py <月度账务目录> [YYYY-MM]
BASE = (_sys.argv[1] if len(_sys.argv) > 1 else ".").rstrip("/")
MONTH = _sys.argv[2] if len(_sys.argv) > 2 else "2026-05"
_y,_m = int(MONTH[:4]), int(MONTH[5:7])
DAYS = _cal.monthrange(_y,_m)[1]
# ===================== 部门归口规则(菜品一级分类)=====================
def dept_of_category(primary, secondary, store):
p = (primary or "").strip()
pl = p.lower()
s = (secondary or "").strip()
if store == "西湖":
if p in ["小吃","主食","零食"] or pl.startswith("brunch"):
return "厨房"
if p in ["咖啡","甜品"] or p in ["茶饮Tea","茶饮tea"]:
return "咖啡"
if p == "软饮":
return "咖啡" if any(k in s for k in ["可尔必思","海盐荔枝"]) else "调酒"
if p.startswith("精酿"):
return "精酿"
if p in ["鸡尾酒","纯饮","纯饮酒"]:
return "调酒"
else: # 滨江
if p in ["肉肉肉","小吃","主食","零食"] or pl.startswith("brunch"):
return "厨房"
if p in ["咖啡","甜品点心"] or p in ["茶饮Tea","茶饮tea"]:
return "咖啡"
if p == "无咖无醇":
return "调酒" if "无醇鸡尾酒" in s else "咖啡"
if p.startswith("精酿") or p in ["瓶罐精酿","瓶装精酿"]:
return "精酿"
if p in ["鸡尾酒","纯饮酒","纯饮"]:
return "调酒"
return None # 其他/团购套餐/特惠套餐/加料 → 需 SKU 级处理或跳过
# 周边/服务(非部门,单列)
def is_peripheral(name):
n=str(name)
return any(k in n for k in ["扑克","点歌","雨伞","毛毯","游戏卡牌","桌游","充电","寄存"])
# POS 端团购套餐壳(收入计平台侧,POS 侧多为 0,跳过避免重复)
def is_teamgou_shell(name):
return "美团团购" in str(name) or "团购套餐" in str(name) or "打卡套餐" in str(name)
# 「其他」类 SKU 按菜品名关键词推断部门(复刻 4 月 SKU重归类思路)
# 注意:酒类关键词优先(避免"果酒优格奶昔"等被咖啡词误捕)
def dept_by_name(name, store):
n = str(name)
# —— 精酿(啤酒/西打/果酒/气泡酒/品牌)优先 ——
# 酒头/畅饮票=扎啤生啤(精酿),与菜品库同类SKU一致
if any(k in n for k in ["IPA","Lager","Stout","Ale","拉格","精酿","世涛","酸啤","古斯","Gose","西打","啤酒","札幌","健力士","三宝乐","制乐场","制乐厂","沙坡尾","气泡实验室","做梦去吧","滇麻","果酒","气泡酒","酒头","畅饮"]):
return "精酿"
# —— 调酒(烈酒/鸡尾酒/特调)——
if any(k in n for k in ["特调","鸡尾酒","威士忌","金酒","朗姆","龙舌兰","伏特加","僵尸","Zombie","Negroni","内格罗尼","Margarita","玛格丽特","Old Fashion","古典","SHOT","纯饮","清酒","葡萄酒","红酒","白葡萄","金刚芭比","混四喜"]):
return "调酒"
# —— 咖啡/茶/无醇饮品 ——
if any(k in n for k in ["美式","拿铁","咖啡","冷萃","澳白","Dirty","卡布","摩卡","瑰夏","耶加","曼特宁","葡萄成熟","优格","冰淇淋","奶昔","波旁","庄园"]):
return "咖啡"
if any(k in n for k in ["","龙井","乌龙","普洱","大梦冰茶","果茶"]):
return "咖啡"
# —— 厨房 ——
if any(k in n for k in ["拼盘","小食","沙拉","Tacos","吐司","","焗饭","意面","","","牛肉","","披萨","brunch","早餐","三明治","汉堡","面包","可颂","煮蛋"]):
return "厨房"
return None
# ===================== 团购项目 → 部门 =====================
def teamgou_dept_split(name):
"""返回 {dept: ratio} 或 None(=代金券跳过)"""
n = str(name)
if "代金券" in n:
return None # 跳过,POS 已计入
# 拆分套餐(先匹配,避免被"厨房/咖啡"单部门误判)
if "单人轻食" in n or "温馨时光" in n:
return {"厨房":0.6, "咖啡":0.4}
if "营养满溢" in n or ("南瓜沙拉" in n and "Tacos" in n):
return {"厨房":0.7, "咖啡":0.3}
if "豪华烤肉拼盘" in n:
return {"厨房":0.7, "调酒":0.3}
if "香菜" in n and ("葡萄酒" in n or "Tacos" in n):
return {"厨房":0.5, "调酒":0.5}
# 单部门
if any(k in n for k in ["咖啡任选","经典咖啡","美式","下午茶","白日梦"]):
return {"咖啡":1.0}
if "精酿" in n or "盲盒" in n:
return {"精酿":1.0}
if any(k in n for k in ["鸡尾酒","SHOT","小酌","HappyHour"]):
# HappyHour 精酿 已被上面拦截;这里是鸡尾酒/SHOT
if "精酿" in n: return {"精酿":1.0}
return {"调酒":1.0}
return {"未拆分":1.0}
# ===================== 读取工具 =====================
def find_header(ws, key):
for i, r in enumerate(ws.iter_rows(values_only=True), start=1):
if r and any(c == key for c in r if c is not None):
return i, [str(c).strip() if c is not None else '' for c in r]
return None, None
def g(path_glob):
fs = glob.glob(f"{BASE}/{path_glob}")
if not fs: sys.exit(f"缺少文件: {path_glob}")
return fs[0]
STORES = {"西湖":"西湖", "滨江":"滨江"}
# ===================== 1) 菜品库: SKU/名称 → 分类 =====================
def load_menu(store):
wb = openpyxl.load_workbook(g(f"大梦_可能实验室_{store}店_菜品库_*.xlsx"), data_only=True)
ws = wb["菜品"]; hr,hdr = find_header(ws,"菜品编码(SPUID")
ci_name=hdr.index("菜品名称"); ci_cat=hdr.index("基础分类")
name2cat={}
for i,r in enumerate(ws.iter_rows(values_only=True),start=1):
if i<=hr: continue
if r[ci_name] and r[ci_cat]:
parts=str(r[ci_cat]).split("/")
name2cat[str(r[ci_name]).strip()]=(parts[0], parts[1] if len(parts)>1 else "")
return name2cat
# ===================== 2) 全渠道: 订单号 → 餐段 =====================
def load_shift(store):
wb=openpyxl.load_workbook(g(f"大梦可能实验室({store}店)_全渠道订单明细_*.xlsx"),data_only=True)
ws=wb.active; hr,hdr=find_header(ws,"营业日期")
ci_o=hdr.index("订单号"); ci_s=hdr.index("餐段")
ci_pay=hdr.index("顾客实付"); ci_amt=hdr.index("订单金额")
o2shift={}; shift_rows=[]
for i,r in enumerate(ws.iter_rows(values_only=True),start=1):
if i<=hr: continue
if r[ci_o]:
sh=str(r[ci_s]) if r[ci_s] else None
o2shift[str(r[ci_o])]=sh
shift_rows.append((sh, float(r[ci_pay] or 0), float(r[ci_amt] or 0)))
return o2shift, shift_rows
# ===================== 3) POS 订单明细 (顾客应付/服务费/优惠/每日) =====================
def load_orders(store):
wb=openpyxl.load_workbook(g(f"大梦_可能实验室_{store}店__店内订单明细*.xlsx"),data_only=True)
ws=wb["订单明细"]; hr,hdr=find_header(ws,"营业日期")
idx={k:hdr.index(k) for k in ["营业日期","订单号","订单金额(元)","顾客应付(元)","订单优惠(元)","菜品收入(元)","服务费收入(元)"]}
orders=[]
for i,r in enumerate(ws.iter_rows(values_only=True),start=1):
if i<=hr: continue
no=r[idx["订单号"]]
if not no or str(no).strip()=="--": continue # 跳过幽灵汇总行
if str(r[idx["营业日期"]]).strip()=="--": continue
orders.append({
"date":str(r[idx["营业日期"]]),
"order":str(r[idx["订单号"]]),
"amount":float(r[idx["订单金额(元)"]] or 0),
"payable":float(r[idx["顾客应付(元)"]] or 0),
"discount":float(r[idx["订单优惠(元)"]] or 0),
"dish_rev":float(r[idx["菜品收入(元)"]] or 0),
"service":float(r[idx["服务费收入(元)"]] or 0),
})
# 优惠明细(赠菜)
ws2=wb["优惠明细"]; hr2,hdr2=find_header(ws2,"营业日期")
gifts=[]
if hr2:
gi={k:(hdr2.index(k) if k in hdr2 else None) for k in ["营业日期","折扣优惠类型","折扣优惠名称","金额(¥)","折扣金额(元)","优惠金额(元)"]}
amt_col = gi["金额(¥)"] or gi["折扣金额(元)"] or gi["优惠金额(元)"]
for i,r in enumerate(ws2.iter_rows(values_only=True),start=1):
if i<=hr2: continue
typ=str(r[gi["折扣优惠类型"]]) if gi["折扣优惠类型"] is not None and r[gi["折扣优惠类型"]] else ""
if "赠菜" in typ:
gifts.append({"date":str(r[gi["营业日期"]]), "amt":float(r[amt_col] or 0) if amt_col is not None else 0})
# 支付方式分布
ws3=wb["支付明细"]; hr3,hdr3=find_header(ws3,"支付方式") if find_header(ws3,"支付方式")[0] else (None,None)
pays=defaultdict(lambda:[0,0.0])
if hr3:
pi_way=hdr3.index("支付方式")
pi_amt=None
for cand in ["支付金额(元)","实收金额(元)","金额(元)","支付金额(¥)"]:
if cand in hdr3: pi_amt=hdr3.index(cand); break
for i,r in enumerate(ws3.iter_rows(values_only=True),start=1):
if i<=hr3: continue
if r[pi_way]:
pays[str(r[pi_way])][0]+=1
pays[str(r[pi_way])][1]+=float(r[pi_amt] or 0) if pi_amt is not None else 0
return orders, gifts, dict(pays)
# ===================== 4) POS 菜品明细 → 部门/分类/班次 =====================
def load_dishes(store, name2cat, o2shift):
wb=openpyxl.load_workbook(g(f"大梦_可能实验室_{store}店__店内订单明细*.xlsx"),data_only=True)
ws=wb["菜品明细"]; hr,hdr=find_header(ws,"订单编号")
ci_o=hdr.index("订单编号"); ci_name=hdr.index("菜品名称")
ci_qty=hdr.index("销售数量"); ci_amt=hdr.index("金额合计(元)")
ci_disc=hdr.index("菜品优惠(元)"); ci_rev=hdr.index("菜品收入(元)")
dept_pos=defaultdict(float) # dept -> 菜品收入
cross=defaultdict(float) # (dept,shift) -> 菜品收入
cat1=defaultdict(lambda:[0,0.0,0.0,0.0]) # primary -> [qty, 原价, 优惠, 收入]
cat2=defaultdict(lambda:[set(),0,0.0,0.0,0.0]) # basecat -> [skus,qty,原价,优惠,收入]
other_skus=defaultdict(lambda:[0,0.0]) # name -> [qty, rev] (其他/未归类)
unmatched=0
for i,r in enumerate(ws.iter_rows(values_only=True),start=1):
if i<=hr: continue
name=str(r[ci_name]).strip() if r[ci_name] else None
if not name: continue
rev=float(r[ci_rev] or 0); amt=float(r[ci_amt] or 0)
disc=float(r[ci_disc] or 0); qty=float(r[ci_qty] or 0)
cat=name2cat.get(name)
primary = cat[0] if cat else "(无菜品库)"
secondary = cat[1] if cat else ""
basecat = f"{primary}/{secondary}" if secondary else primary
cat1[primary][0]+=qty; cat1[primary][1]+=amt; cat1[primary][2]+=disc; cat1[primary][3]+=rev
cat2[basecat][0].add(name); cat2[basecat][1]+=qty; cat2[basecat][2]+=amt; cat2[basecat][3]+=disc; cat2[basecat][4]+=rev
if is_teamgou_shell(name) or is_peripheral(name):
other_skus[name][0]+=qty; other_skus[name][1]+=rev
continue
dept = dept_of_category(primary, secondary, store)
if dept is None:
dept = dept_by_name(name, store)
if dept is None:
other_skus[name][0]+=qty; other_skus[name][1]+=rev
unmatched+=1
continue
dept_pos[dept]+=rev
sh=o2shift.get(str(r[ci_o]))
if sh in ("白班","晚班"):
cross[(dept,sh)]+=rev
return dept_pos, cross, cat1, cat2, other_skus, unmatched
# ===================== 5) 团购 → 部门 =====================
def load_teamgou():
# 美团大梦
rows=[] # (store, brand, name, cnt, price, settle)
wb=openpyxl.load_workbook(g("42323734_团购收益明细_*.xlsx"),data_only=True)
ws=wb["收益明细表"]; data=list(ws.iter_rows(values_only=True))
hdr=[str(c).strip() if c else '' for c in data[1]]
ci_store=hdr.index("消费门店"); ci_pkg=hdr.index("套餐名")
ci_total=hdr.index("总收入(元)")
ci_settle=hdr.index("结算价(总收入-美团点评技术服务费-商家营销费用-消费后退-其他调整)(元)")
ag=defaultdict(lambda:[0,0.0,0.0])
for r in data[2:]:
if not r[ci_pkg]: continue
store="西湖" if "西湖" in str(r[ci_store]) else "滨江"
k=(store,"大梦",str(r[ci_pkg]))
ag[k][0]+=1; ag[k][1]+=float(r[ci_total] or 0); ag[k][2]+=float(r[ci_settle] or 0)
# 点评可能实验室
wb2=openpyxl.load_workbook(g("新版收益明细_*团购_*.xlsx"),data_only=True)
ws2=wb2["收益明细"]; data2=list(ws2.iter_rows(values_only=True))
hdr2=[str(c).strip() if c else '' for c in data2[0]]
ci_store2=hdr2.index("美团门店名称"); ci_pkg2=hdr2.index("项目名称")
ci_price2=hdr2.index("售价(美团售价)"); ci_merch2=hdr2.index("商家应得")
for r in data2[1:]:
if not r[ci_pkg2]: continue
store="西湖" if "西湖" in str(r[ci_store2]) else "滨江"
k=(store,"可能实验室",str(r[ci_pkg2]))
ag[k][0]+=1; ag[k][1]+=float(r[ci_price2] or 0); ag[k][2]+=float(r[ci_merch2] or 0)
# 拆部门(用结算/商家应得)
dept_tg=defaultdict(lambda: defaultdict(float)) # store -> dept -> settle
voucher=defaultdict(lambda:[0,0.0,0.0]) # store -> [cnt,price,settle]
detail=[]
for (store,brand,name),(c,p,s) in sorted(ag.items()):
split=teamgou_dept_split(name)
if split is None:
voucher[store][0]+=c; voucher[store][1]+=p; voucher[store][2]+=s
detail.append((brand,store,name,c,p,s,"⚠️代金券跳过",""))
continue
rule="; ".join(f"{d} {int(r*100)}%" for d,r in split.items())
for d,ratio in split.items():
dept_tg[store][d]+=s*ratio
detail.append((brand,store,name,c,p,s,rule,rule))
return dept_tg, voucher, detail
# ===================== 主流程 =====================
def main():
result={}
menus={st:load_menu(st) for st in STORES}
shifts={st:load_shift(st) for st in STORES}
dept_pos={}; cross={}; cat1={}; cat2={}; others={}; unmatched={}
orders={}; gifts={}; pays={}
for st in STORES:
o2shift=shifts[st][0]
dept_pos[st],cross[st],cat1[st],cat2[st],others[st],unmatched[st]=load_dishes(st,menus[st],o2shift)
orders[st],gifts[st],pays[st]=load_orders(st)
# 每日营收
daily={}
for st in STORES:
dd=defaultdict(lambda:[0,0.0,0.0,0.0]) # date->[orders,amount,payable,discount]
for o in orders[st]:
d=o["date"]
dd[d][0]+=1; dd[d][1]+=o["amount"]; dd[d][2]+=o["payable"]; dd[d][3]+=o["discount"]
gd=defaultdict(lambda:[0,0.0])
for gft in gifts[st]:
gd[gft["date"]][0]+=1; gd[gft["date"]][1]+=gft["amt"]
daily[st]={d:(dd[d],gd.get(d,[0,0.0])) for d in sorted(dd)}
dept_tg, voucher, tg_detail = load_teamgou()
# ---- 部门收入合计 ----
DEPTS=["厨房","咖啡","精酿","调酒"]
print("="*72)
print(f" 大梦 {_m} 月营收分析 — 部门收入(POS菜品 + 团购套餐结算)")
print("="*72)
print(f" {'部门':<6}{'滨江POS':>11}{'滨江团购':>10}{'滨江合计':>11}{'西湖POS':>11}{'西湖团购':>10}{'西湖合计':>11}")
dept_total={}
for d in DEPTS:
bp=dept_pos["滨江"].get(d,0); bt=dept_tg["滨江"].get(d,0); bc=bp+bt
xp=dept_pos["西湖"].get(d,0); xt=dept_tg["西湖"].get(d,0); xc=xp+xt
dept_total[d]={"滨江":bc,"西湖":xc}
print(f" {d:<6}{bp:>11.2f}{bt:>10.2f}{bc:>11.2f}{xp:>11.2f}{xt:>10.2f}{xc:>11.2f}")
# ---- 班次营收 ----
shift_sum=defaultdict(lambda:[0,0.0,0.0]) # (store,shift)->[orders, amount, payable]
for st in STORES:
for sh,pay,amt in shifts[st][1]:
if sh in ("白班","晚班"):
shift_sum[(st,sh)][0]+=1
shift_sum[(st,sh)][1]+=amt
shift_sum[(st,sh)][2]+=pay
print("\n 班次营收(顾客实付):")
for st in STORES:
for sh in ["白班","晚班"]:
o,a,p=shift_sum[(st,sh)]
print(f" {st}{sh}: 订单{o:>5} 实付{p:>11.2f} 客单{p/o if o else 0:>7.2f}")
# ---- 部门×班次(按部门合计比例校正)----
print("\n 部门×班次(校正到部门合计):")
cross_scaled={}
for st in STORES:
for d in DEPTS:
raw_sum=sum(cross[st].get((d,sh),0) for sh in ["白班","晚班"])
target=dept_total[d][st]
for sh in ["白班","晚班"]:
raw=cross[st].get((d,sh),0)
cross_scaled[(st,d,sh)]= round(raw/raw_sum*target) if raw_sum>0 else 0
for st in STORES:
line=f" {st}: "+" ".join(f"{d}(白{cross_scaled[(st,d,'白班')]}/晚{cross_scaled[(st,d,'晚班')]})" for d in DEPTS)
print(line)
# ---- 12 员工 三元组 ----
EMP=[
("蔡逸丰","西湖","精酿","晚班"),("何简","西湖","厨房","白班"),("宋群喜","西湖","咖啡","白班"),
("胡舒","西湖","调酒","晚班"),("郭思儒","西湖","咖啡","白班"),("秦天","西湖","厨房","晚班"),
("李想","滨江","调酒","晚班"),("王瑛胤","滨江","咖啡","白班"),("刘润祥","滨江","厨房","晚班"),
("朱秋风","滨江","精酿","晚班"),("叶磊","滨江","厨房","白班"),("尹志艳","滨江","厨房","中班"),
]
BJ_KITCHEN={"刘润祥","叶磊","尹志艳"}
print("\n"+"="*72)
print(" 12 员工 业绩三元组 (部门业绩 / 班次业绩 / 部门×班次业绩)")
print("="*72)
emp_out=[]
for name,st,dept,sh in EMP:
dr=dept_total.get(dept,{}).get(st,0)
sr=shift_sum.get((st,sh),[0,0,0])[2] if sh in ("白班","晚班") else 0
cr=cross_scaled.get((st,dept,sh),0)
if name in BJ_KITCHEN: sr=0; cr=0
if sh=="中班": sr=0; cr=0
emp_out.append((name,st,dept,sh,dr,sr,cr))
print(f" {name:<6}{st}{dept}/{sh:<3} 部门{dr:>11.2f} 班次{sr:>11.2f} 部门×班次{cr:>9}")
# 存盘供 workflow 校验 / 写表
import json
summary={
"month":MONTH,
"dept_pos":{st:dict(dept_pos[st]) for st in STORES},
"dept_tg":{st:dict(dept_tg[st]) for st in STORES},
"dept_total":dept_total,
"shift_sum":{f"{st}|{sh}":shift_sum[(st,sh)] for st in STORES for sh in ["白班","晚班"]},
"cross_scaled":{f"{st}|{d}|{sh}":cross_scaled[(st,d,sh)] for st in STORES for d in DEPTS for sh in ["白班","晚班"]},
"voucher":{st:voucher[st] for st in STORES},
"unmatched":unmatched,
"others":{st:dict(others[st]) for st in STORES},
"employees":emp_out,
"orders_count":{st:len(orders[st]) for st in STORES},
"pos_payable":{st:round(sum(o["payable"] for o in orders[st]),2) for st in STORES},
"pos_dishrev":{st:round(sum(o["dish_rev"] for o in orders[st]),2) for st in STORES},
}
with open(f"{BASE}/_analysis_summary.json","w") as f:
json.dump(summary,f,ensure_ascii=False,indent=2,default=str)
print(f"\n 未归类菜品笔数: 西湖={unmatched['西湖']} 滨江={unmatched['滨江']}")
print(f" POS订单数: 西湖={len(orders['西湖'])} 滨江={len(orders['滨江'])}")
write_xlsx(summary, dept_total, dept_pos, dept_tg, shift_sum, cross_scaled,
cat1, cat2, daily, pays, voucher, tg_detail, DEPTS, EMP, BJ_KITCHEN)
print(f"\n 汇总已存: {BASE}/_analysis_summary.json")
print(f" 分析表已存: {BASE}/大梦可能实验室_{_m}月营收分析_西湖店vs滨江店.xlsx")
return summary
def write_xlsx(s, dept_total, dept_pos, dept_tg, shift_sum, cross_scaled,
cat1, cat2, daily, pays, voucher, tg_detail, DEPTS, EMP, BJ_KITCHEN):
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
wb=Workbook(); wb.remove(wb.active)
H=Font(bold=True); TITLE=Font(bold=True,size=13)
GREEN=PatternFill("solid",fgColor="E2EFDA"); BLUE=PatternFill("solid",fgColor="DDEBF7")
HEADER=PatternFill("solid",fgColor="44546A"); HW=Font(bold=True,color="FFFFFF")
def sheet(name): return wb.create_sheet(name)
def hdr(ws,row,cols,fill=True):
for j,c in enumerate(cols,1):
cell=ws.cell(row=row,column=j,value=c)
if fill: cell.fill=HEADER; cell.font=HW
# —— 总览 ——
ws=sheet("总览"); ws["A1"]=f"大梦·可能实验室 — {_y}{_m}月营收总览(西湖店 vs 滨江店)"; ws["A1"].font=TITLE
ws["A2"]=f"区间 {_y}/{_m:02d}/01{_m:02d}/{DAYS} | 数据源:POS店内订单明细 + 美团团购收益 + 点评/可能实验室收益"
r=4; ws.cell(r,1,"指标").font=H; ws.cell(r,2,"滨江店").font=H; ws.cell(r,3,"西湖店").font=H; ws.cell(r,4,"两店合计").font=H
bj_dep=sum(dept_total[d]["滨江"] for d in DEPTS); xh_dep=sum(dept_total[d]["西湖"] for d in DEPTS)
rows=[
("4部门收入合计(POS+团购)", bj_dep, xh_dep),
("POS菜品收入", sum(dept_pos["滨江"].values()), sum(dept_pos["西湖"].values())),
("团购套餐结算", sum(dept_tg["滨江"].values()), sum(dept_tg["西湖"].values())),
("白班实付", shift_sum[("滨江","白班")][2], shift_sum[("西湖","白班")][2]),
("晚班实付", shift_sum[("滨江","晚班")][2], shift_sum[("西湖","晚班")][2]),
("POS订单数", s["orders_count"]["滨江"], s["orders_count"]["西湖"]),
]
for i,(k,b,x) in enumerate(rows):
rr=r+1+i; ws.cell(rr,1,k); ws.cell(rr,2,round(b,2)); ws.cell(rr,3,round(x,2)); ws.cell(rr,4,round(b+x,2))
# —— 部门收入 ——
ws=sheet("部门收入"); ws["A1"]="负责部门收入 — POS菜品收入 + 团购套餐结算(代金券不重算)"; ws["A1"].font=TITLE
hdr(ws,3,["部门","滨江_POS","滨江_团购","滨江_合计","西湖_POS","西湖_团购","西湖_合计","两店合计"])
for i,d in enumerate(DEPTS):
rr=4+i; bp=dept_pos["滨江"].get(d,0); bt=dept_tg["滨江"].get(d,0); xp=dept_pos["西湖"].get(d,0); xt=dept_tg["西湖"].get(d,0)
for j,v in enumerate([d,round(bp,2),round(bt,2),round(bp+bt,2),round(xp,2),round(xt,2),round(xp+xt,2),round(bp+bt+xp+xt,2)],1):
ws.cell(rr,j,v)
tot_r=4+len(DEPTS)
ws.cell(tot_r,1,"合计").font=H
for j,col in enumerate(["滨江_POS","滨江_团购","滨江_合计","西湖_POS","西湖_团购","西湖_合计","两店合计"],2):
ws.cell(tot_r,j,round(sum(ws.cell(4+i,j).value for i in range(len(DEPTS))),2)).font=H
# 占比块
ws.cell(tot_r+2,1,"② 4部门占比").font=H
hdr(ws,tot_r+3,["部门","滨江合计","西湖合计","两店合计","滨江占比","西湖占比"])
for i,d in enumerate(sorted(DEPTS,key=lambda x:-(dept_total[x]['滨江']+dept_total[x]['西湖']))):
rr=tot_r+4+i; bc=dept_total[d]["滨江"]; xc=dept_total[d]["西湖"]
ws.cell(rr,1,d); ws.cell(rr,2,round(bc,2)); ws.cell(rr,3,round(xc,2)); ws.cell(rr,4,round(bc+xc,2))
ws.cell(rr,5,round(bc/bj_dep,4)); ws.cell(rr,6,round(xc/xh_dep,4))
# —— 团购→部门归口 ——
ws=sheet("团购→部门归口"); ws["A1"]="团购平台项目 → 部门归口明细(结算/商家应得口径)"; ws["A1"].font=TITLE
hdr(ws,3,["品牌","门店","项目名称","笔数","售价","结算/应得","归口规则"])
for i,(brand,store,name,c,p,sv,rule,_) in enumerate(tg_detail):
rr=4+i
for j,v in enumerate([brand,store+"",name,c,round(p,2),round(sv,2),rule],1): ws.cell(rr,j,v)
base=4+len(tg_detail)+1
ws.cell(base,1,"② 团购套餐→部门 汇总(剔除代金券)").font=H
hdr(ws,base+1,["门店","部门","金额"])
rr=base+2
for st in ["滨江","西湖"]:
for d in DEPTS:
v=dept_tg[st].get(d,0)
if v: ws.cell(rr,1,st+""); ws.cell(rr,2,d); ws.cell(rr,3,round(v,2)); rr+=1
ws.cell(rr,1,"③ 代金券(跳过,未重算)").font=H; rr+=1
hdr(ws,rr,["门店","笔数","售价","结算"]); rr+=1
for st in ["滨江","西湖"]:
v=voucher[st]; ws.cell(rr,1,st+""); ws.cell(rr,2,v[0]); ws.cell(rr,3,round(v[1],2)); ws.cell(rr,4,round(v[2],2)); rr+=1
# —— 班次营收 ——
ws=sheet("班次营收"); ws["A1"]="班次营收对比 — 白班 vs 晚班(顾客实付)"; ws["A1"].font=TITLE
hdr(ws,3,["门店","餐段","订单数","订单金额(原价)","顾客实付","日均订单","日均实付","客单价(实付)"])
rr=4
for st in ["滨江","西湖"]:
for sh in ["白班","晚班"]:
o,a,p=shift_sum[(st,sh)]
for j,v in enumerate([st+"",sh,o,round(a,2),round(p,2),round(o/DAYS,1),round(p/DAYS,1),round(p/o if o else 0,2)],1): ws.cell(rr,j,v); rr+=0
rr+=1
# —— 部门×班次 ——
ws=sheet("部门x班次"); ws["A1"]="部门 × 班次 营收(校正到部门合计)"; ws["A1"].font=TITLE
hdr(ws,3,["门店","部门","白班","晚班","合计"])
rr=4
for st in ["滨江","西湖"]:
for d in DEPTS:
wv=cross_scaled[(st,d,"白班")]; nv=cross_scaled[(st,d,"晚班")]
for j,v in enumerate([st+"",d,wv,nv,wv+nv],1): ws.cell(rr,j,v)
rr+=1
# —— 品类营收(一级) ——
ws=sheet("品类营收(一级)"); ws["A1"]="品类营收 — 一级分类 西湖 vs 滨江"; ws["A1"].font=TITLE
hdr(ws,3,["一级分类","滨江_件数","滨江_收入","西湖_件数","西湖_收入"])
allcat=sorted(set(cat1["滨江"])|set(cat1["西湖"]), key=lambda c:-(cat1['滨江'].get(c,[0,0,0,0])[3]+cat1['西湖'].get(c,[0,0,0,0])[3]))
for i,c in enumerate(allcat):
rr=4+i; b=cat1["滨江"].get(c,[0,0,0,0]); x=cat1["西湖"].get(c,[0,0,0,0])
for j,v in enumerate([c,int(b[0]),round(b[3],2),int(x[0]),round(x[3],2)],1): ws.cell(rr,j,v)
# —— 品类营收(二级) ——
ws=sheet("品类营收(二级)"); ws["A1"]="品类营收 — 二级分类(基础分类) 各店Top"; ws["A1"].font=TITLE
r0=3
for st in ["滨江","西湖"]:
ws.cell(r0,1,f"{st}").font=H; r0+=1
hdr(ws,r0,["基础分类","SKU数","销售件数","菜品收入"]); r0+=1
top=sorted(cat2[st].items(), key=lambda x:-x[1][4])[:30]
for c,v in top:
ws.cell(r0,1,c); ws.cell(r0,2,len(v[0])); ws.cell(r0,3,int(v[1])); ws.cell(r0,4,round(v[4],2)); r0+=1
r0+=1
# —— 每日营收 ——
for st in ["滨江","西湖"]:
ws=sheet(f"{st}店_每日营收"); ws["A1"]=f"{st}店 — {_y}{_m}月每日营收"; ws["A1"].font=TITLE
hdr(ws,3,["日期","订单数","订单金额(原价)","顾客应付","订单优惠","赠菜笔数","赠菜金额"])
rr=4
for d,(dd,gd) in daily[st].items():
for j,v in enumerate([d,dd[0],round(dd[1],2),round(dd[2],2),round(dd[3],2),gd[0],round(gd[1],2)],1): ws.cell(rr,j,v)
rr+=1
# —— 支付方式分布 ——
ws=sheet("支付方式分布"); ws["A1"]="支付方式分布(POS端)"; ws["A1"].font=TITLE
r0=3
for st in ["滨江","西湖"]:
ws.cell(r0,1,f"{st}").font=H; r0+=1
hdr(ws,r0,["支付方式","笔数","支付金额"]); r0+=1
for way,(c,amt) in sorted(pays[st].items(), key=lambda x:-x[1][1]):
ws.cell(r0,1,way); ws.cell(r0,2,c); ws.cell(r0,3,round(amt,2)); r0+=1
r0+=1
# —— 12员工业绩(工资交接)——
ws=sheet("12员工业绩"); ws["A1"]="12 员工业绩三元组(→ 工资表V2 cols 19/20/21"; ws["A1"].font=TITLE
hdr(ws,3,["姓名","归属","部门","班次","部门业绩","班次业绩","部门×班次业绩"])
for i,(name,st,dept,sh,dr,sr,cr) in enumerate(s["employees"]):
rr=4+i
for j,v in enumerate([name,st+"",dept,sh,round(dr,2),round(sr,2),cr],1): ws.cell(rr,j,v)
fill=GREEN if st=="西湖" else BLUE
for j in range(1,8): ws.cell(rr,j).fill=fill
# —— 深度分析 / 改进建议 占位(workflow 填充)——
ws=sheet("深度分析"); ws["A1"]="深度分析 — 异常项与关键洞察(见正文)"; ws["A1"].font=TITLE
ws=sheet("改进建议"); ws["A1"]=f"基于{_m}月数据的改进建议(见正文)"; ws["A1"].font=TITLE
for ws in wb.worksheets:
ws.column_dimensions["A"].width=26
for col in "BCDEFGH": ws.column_dimensions[col].width=14
wb.save(f"{BASE}/大梦可能实验室_{_m}月营收分析_西湖店vs滨江店.xlsx")
if __name__=="__main__":
main()