#!/usr/bin/env python3 """compare_nights.py — LOG384 の比べ(busycheck・すれ違い入と切・v3 と v4) service_sim/unity/ の中で動かす(台帳と各回の summary.json を読むだけ。何も書かない)。 python3 compare_nights.py 順位の比べ(U)は「左が右より大きい組の数」。n=5 と 8 なら両側5%で U<=6 か U>=34、 n=10 と 10 なら U<=23 か U>=77、n=20 と 19 ならおよそ U<=120 か U>=260 が目安。 """ import csv, json, os, statistics as st ROWS = list(csv.DictReader(open('unity_ledger_v1.csv', encoding='utf-8'))) BY = {r['run_folder']: r for r in ROWS} def summary(folder): try: d = json.load(open(os.path.join(folder, 'summary.json'), encoding='utf-8')) except (OSError, ValueError): return {} flat = {} def walk(x): for k, v in x.items(): if isinstance(v, dict): walk(v) else: flat.setdefault(k, v) walk(d) return flat def U(a, b): return sum((x > y) + 0.5 * (x == y) for x in a for y in b) def med(v): return st.median(v) if v else float('nan') def show(title, a_rows, b_rows, a_name, b_name, metrics): print('== %s(%s %d回 / %s %d回)' % (title, a_name, len(a_rows), b_name, len(b_rows))) for key, label, src in metrics: if src == 'ledger': a = [float(r[key]) for r in a_rows] b = [float(r[key]) for r in b_rows] else: a = [float(summary(r['run_folder']).get(key) or 0) for r in a_rows] b = [float(summary(r['run_folder']).get(key) or 0) for r in b_rows] print(' %-24s %s 中央 %9.3f(%.3f〜%.3f) %s 中央 %9.3f(%.3f〜%.3f) U(%s>%s)=%.1f/%d' % (label, a_name, med(a), min(a), max(a), b_name, med(b), min(b), max(b), a_name, b_name, U(a, b), len(a) * len(b))) LEDGER = [('stuck_count', '動けなくなった', 'ledger'), ('gave_up', '人のあきらめ', 'ledger'), ('entry_wait_mean', '入店待ち(分)', 'ledger'), ('robot_share', 'ロボットが運んだ割合', 'ledger'), ('rescues', '助けに行った', 'ledger'), ('late_staff_dishes', '店員が遅れて運んだ皿', 'ledger')] # 1. busycheck(③1台・種1) b1 = [r for r in ROWS if '_busy1_' in r['run_folder']] b4 = [r for r in ROWS if '_busy4_' in r['run_folder']] show('busycheck ③1台・種1', b1, b4, '1本ずつ', '4本同時', LEDGER) # 2. すれ違い入と切(③2台・2夜分、全員が帰った回だけ) def p1r2(prefix, on): out = [] for s in range(10): f = '%s_fam_p1_r2_s%d%s' % (prefix, s, '_passon' if on else '') r = BY.get(f) if r and r['everyone_left'] == '1': out.append(r) return out off = p1r2('20261027_night_cs21', False) + p1r2('20261028_night_cs25', False) on = p1r2('20261027_night_cs21', True) + p1r2('20261028_night_cs25', True) show('すれ違い ③2台・2夜', off, on, '切', '入', LEDGER + [ ('face_stop_max_s', '向かい合って止まった最長(秒)', 'summary'), ('face_stop_count', '向かい合って止まった回数', 'summary'), ('timeouts', '往復の時間切れ', 'ledger')]) # 3. v3 と v4(条件ごと) def night(prefix, on): g = {} for r in ROWS: f = r['run_folder'] if f.startswith(prefix) and f.endswith('_passon') == on: g.setdefault('%s_r%s' % (r['store'], r['robots']), []).append(r) return g v3 = night('20261027_night_cs21_', False) v4 = night('20261029_night_cs26_', True) for k in sorted(v4): show('v3 と v4 ' + k, v3[k], v4[k], 'v3', 'v4', [ ('entry_wait_mean', '入店待ち(分)', 'ledger'), ('dish_wait_mean', '料理の待ち(分)', 'ledger'), ('stuck_count', '動けなくなった', 'ledger'), ('robot_share', 'ロボットが運んだ割合', 'ledger'), ('gave_up', '人のあきらめ', 'ledger'), ('warped', '瞬間移動', 'ledger'), ('timeouts', '往復の時間切れ', 'ledger'), ('face_stop_max_s', '向かい合って止まった最長(秒)', 'summary')])