# サイトの試算(SimPy)と Unity の3Dシミュレーションの数字を、同じ条件で並べる。 # 入力:KIOXIA の台帳 unity_ledger_v1.csv(Unity の値と、同じ条件の試算の値 site_* が1行に入っている) # 対象:直近の2つの夜(20261106_busy・20261107_staff)。どちらも6店 × 条件 × 種10 import csv, sys, statistics as st from collections import defaultdict LEDGER = sys.argv[1] if len(sys.argv) > 1 else 'unity_ledger_v1.csv' NIGHTS = ('20261106', '20261107') STORE = {'fam_p0': '②定番の郊外型', 'fam_p1': '③効率重視・入口が角', 'fam_p2': '④効率重視・小さめの店', 'fam_p3': '⑤ゆったり・入口が正面', 'fam_p4': '⑥ゆったり・入口が横', 'fam_p5': '⑦効率重視・席を詰めた店'} rows = [r for r in csv.DictReader(open(LEDGER, encoding='utf-8')) if r['run_folder'].startswith(NIGHTS) and r['status'] == 'completed'] f = lambda r, k: float(r[k]) if r[k] not in ('', None) else None # 条件ごとに種10本を平均する g = defaultdict(list) for r in rows: key = (r['run_folder'][:8], r['store'], int(r['staff']), int(r['robots']), int(r['arrivals_per_h'])) g[key].append(r) def mean(rs, k): v = [f(r, k) for r in rs if f(r, k) is not None] return st.mean(v) if v else None print(f'行数 {len(rows)}・条件 {len(g)}(種ごとの平均で比べる)') print() print('■ 条件ごとの平均(分)。U=Unity・S=サイトの試算') print('夜,店,スタッフ,ロボット,来店/時,種,入店待ちU,入店待ちS,料理の待ちU,料理の待ちS,会計待ちU,会計待ちS,動けなくなった/往復') for key in sorted(g): rs = g[key] trips = sum(f(r, 'trips') or 0 for r in rs); stuck = sum(f(r, 'stuck_count') or 0 for r in rs) vals = [mean(rs, k) for k in ('entry_wait_mean', 'site_entry_wait_mean', 'dish_wait_mean', 'site_dish_wait_mean', 'pay_wait_mean', 'site_pay_wait_mean')] print(','.join([key[0], STORE[key[1]], str(key[2]), str(key[3]), str(key[4]), str(len(rs))] + ['' if v is None else f'{v:.2f}' for v in vals] + [f'{stuck/trips:.3f}' if trips else ''])) # 人だけ(ロボット0台)の条件で、Unity が試算の何倍か print() print('■ ロボット0台の条件で、Unity ÷ 試算(入店待ちが試算で0.5分以上の条件だけ)') ratios = defaultdict(list) for key, rs in g.items(): if key[3] != 0: continue u, s = mean(rs, 'entry_wait_mean'), mean(rs, 'site_entry_wait_mean') if u is not None and s and s >= 0.5: ratios[key[1]].append(u / s) for k in sorted(ratios): v = ratios[k]; print(f'{STORE[k]}:{len(v)}条件・中央値 {st.median(v):.2f} 倍({min(v):.2f}〜{max(v):.2f})') # 0台 → 2台で入店待ちが増えるか減るか、向きがそろうか print() print('■ 0台 → 2台で、入店待ちが減るか増えるかの向き(差が0.1分未満は「変わらない」)') def sign(d): return 0 if abs(d) < 0.1 else (1 if d > 0 else -1) same = total = 0; diff = [] for key, rs in g.items(): if key[3] != 0: continue k2 = (key[0], key[1], key[2], 2, key[4]) if k2 not in g: continue du = mean(g[k2], 'entry_wait_mean') - mean(rs, 'entry_wait_mean') ds = mean(g[k2], 'site_entry_wait_mean') - mean(rs, 'site_entry_wait_mean') total += 1 if sign(du) == sign(ds): same += 1 else: diff.append((key, round(du, 2), round(ds, 2))) print(f'向きが同じ {same} / {total} 組') for key, du, ds in sorted(diff): print(f' 違う:{key[0]} {STORE[key[1]]} スタッフ{key[2]} 来店{key[4]}/時 Unity {du:+.2f} 分・試算 {ds:+.2f} 分') # ロボットが動けなくなった(60秒)割合を店ごとに print() print('■ ロボットありの回で、動けなくなった(60秒)回数 ÷ 往復の回数(店ごと)') acc = defaultdict(lambda: [0.0, 0.0]) for r in rows: if int(r['robots']) == 0: continue acc[r['store']][0] += f(r, 'stuck_count') or 0; acc[r['store']][1] += f(r, 'trips') or 0 T = [0, 0] for k in sorted(acc): s, t = acc[k]; T[0] += s; T[1] += t print(f'{STORE[k]}:{s:.0f} / {t:.0f} = {s/t:.1%}') print(f'6店まとめて:{T[0]:.0f} / {T[1]:.0f} = {T[0]/T[1]:.1%}')