# 配膳ロボットが止まった場所を、店の平面図の上に重ねる(Unity の夜の種10個ぶん) import sys, csv, json, collections sys.path.insert(0, '/home/claude/p2m-site/tools/sim_layouts') import matplotlib; matplotlib.use('Agg') import matplotlib.pyplot as plt from matplotlib import font_manager from matplotlib.patches import Rectangle for f in font_manager.findSystemFonts(): if 'NotoSansCJK-Regular' in f: font_manager.fontManager.addfont(f) plt.rcParams['font.family'] = 'Noto Sans CJK JP' import famires_v3 as F SRC = sys.argv[1] LAY = {'fam_p2': 'p2_efficient_narrow', 'fam_p4': 'p4_relaxed_side'} NAME = {'fam_p2': '④ 効率重視・小さめの店', 'fam_p4': '⑥ ゆったり・入口が横の店'} rows = list(csv.DictReader(open(SRC))) def base(ax, L): try: F.draw(ax, L, [], {}, {}) except Exception: pass for t in L['tables']: x0, y0, x1, y1 = F.fp(t) ax.add_patch(Rectangle((x0, y0), x1-x0, y1-y0, fc='#e9ecef', ec='#868e96', lw=0.6, zorder=3)) def panel(ax, L, sub, title, mx): base(ax, L) stuck = [r for r in sub if r['resolved'] == '動けなくなった'] # 止まっていた時間の合計を 0.5m のマスで集める cell = collections.Counter() for r in sub: cell[(round(float(r['x'])*2)/2, round(float(r['z'])*2)/2)] += float(r['duration_s']) n0 = len({r['seed'] for r in sub}) or 1 for (x, z), v in cell.items(): ax.add_patch(Rectangle((x-0.25, z-0.25), 0.5, 0.5, fc='#f08c00', alpha=min(0.85, 0.08 + 0.8*(v/n0)/mx), ec='none', zorder=6)) ax.scatter([float(r['x']) for r in stuck], [float(r['z']) for r in stuck], s=10, c='#c92a2a', zorder=7, linewidths=0) n = len({r['seed'] for r in sub}) or 1 ax.set_title(f"{title}\n動けなくなった {len(stuck)/n:.0f}回(ピーク2時間あたり)・止まっていた合計 {sum(float(r['duration_s']) for r in sub)/n/60:.0f}分", fontsize=11) def fig(label, store, out): L = json.load(open(f'/home/claude/p2m-site/public/sim/layouts/{LAY[store]}.json'))['layout'] f, axs = plt.subplots(1, 2, figsize=(14, 6.4)) subs = {rb: [r for r in rows if r['label'] == label and r['store'] == store and r['robots'] == rb] for rb in ['1', '2']} mx = 1 for sub in subs.values(): c = collections.Counter(); n0 = len({r['seed'] for r in sub}) or 1 for r in sub: c[(round(float(r['x'])*2)/2, round(float(r['z'])*2)/2)] += float(r['duration_s']) / n0 mx = max([mx] + list(c.values())) for ax, rb in zip(axs, ['1', '2']): panel(ax, L, subs[rb], f'ロボット{rb}台', mx) f.suptitle(f'{NAME[store]}・混んでいるとき:配膳ロボットが止まった場所(Unity・10回の平均)', fontsize=13, weight='bold') f.text(0.5, 0.02, 'だいだいの濃さ=そのマスで止まっていた時間の合計 赤い点=動けなくなり、スタッフが動かしに行った場所', ha='center', fontsize=10, color='#495057') f.tight_layout(rect=(0, 0.04, 1, 0.95)); f.savefig(out, dpi=130); print(out) fig('a36', 'fam_p2', 'stopmap_p2_crowded.png') fig('a49', 'fam_p4', 'stopmap_p4_crowded.png') # ④と⑥を同じ物差しで並べる(混んでいるとき・2台) def pair(out): f, axs = plt.subplots(1, 2, figsize=(14, 6.4)) sets = [('a36', 'fam_p2'), ('a49', 'fam_p4')] subs = [[r for r in rows if r['label'] == l and r['store'] == s and r['robots'] == '2'] for l, s in sets] mx = 1 for sub in subs: c = collections.Counter(); n0 = len({r['seed'] for r in sub}) or 1 for r in sub: c[(round(float(r['x'])*2)/2, round(float(r['z'])*2)/2)] += float(r['duration_s']) / n0 mx = max([mx] + list(c.values())) for ax, (l, s), sub in zip(axs, sets, subs): L = json.load(open(f'/home/claude/p2m-site/public/sim/layouts/{LAY[s]}.json'))['layout'] panel(ax, L, sub, NAME[s], mx) f.suptitle('混んでいるとき・ロボット2台:配膳ロボットが止まった場所(Unity・10回の平均)', fontsize=13, weight='bold') f.text(0.5, 0.02, 'だいだいの濃さ=そのマスで止まっていた時間の合計(左右で同じ物差し) 赤い点=動けなくなり、スタッフが動かしに行った場所', ha='center', fontsize=10, color='#495057') f.tight_layout(rect=(0, 0.04, 1, 0.95)); f.savefig(out, dpi=130); print(out) pair('stopmap_p2_vs_p4_crowded_r2.png') for l, s in [('a36','fam_p2'),('a49','fam_p4')]: st = [r for r in rows if r['label']==l and r['store']==s and r['robots']=='2' and r['resolved']=='動けなくなった'] print(s, collections.Counter(r['cause'] for r in st).most_common())