"""試算(haizen_sim.py)の条件と店の回し方の値を、Unity が読む JSON に書き出す(2026-10-22。v2.8 で2026-10-25 にレジの数を足した)。 「何について正か」(unity_design_v0.md の 0 章)で、条件と店の回し方の決まり・時間・数字の定義はサイトが正。 Unity はこの JSON を読み、値を手で写さない(LOG359 のずれ9「値の手写し」をなくすため)。 使い方: python3 export_params.py results.json <出力先> (出力先は p2m-site の public/sim/serving-robot-params.json) - common:店に共通の値。role が "unity_measures" の値は Unity で測ってサイトに戻すための仮の値なので、Unity は読まない - stores:Unity で回す店(fam_p1・fam_p5・fam_p2・fam_p3)の値。registers はレジの数(v2.8) - unity_conditions:Unity で回す条件(店ごとに3つ。③⑦④⑤で12条件)と、同じ条件の試算の結果(40回の平均)。Unity の店はレジの内側に店員が1人だけなので、レジは1つ """ import json import sys import haizen_sim as h res_path, out = sys.argv[1], sys.argv[2] R = json.load(open(res_path, encoding="utf-8")) gen = {g["generation"] for g in json.load(open(h.LAYOUT_DIR + "/summary.json", encoding="utf-8"))} assert len(gen) == 1, gen ROLE = { "robot_speed": "unity_measures", "pause_rate": "unity_measures", "pause_s": "unity_measures", "stuck_rate": "unity_measures", "hop_m": "site_only", # 試算の近似(1往復で複数卓を回るときの寄り道)。Unity は実際の道を走るので使わない "sustain": "site_only", "warmup_min": "site_only", "util_sample_s": "site_only", "model": "site_only", } NOTE = { "unity_measures": "Unity で測ってサイトに戻すための仮の値。Unity は読まない", "site_only": "試算の中だけの設定。Unity は使わない", "site_rule": "サイトが正。Unity はこの値を使う", } common = {k: {"value": v, "role": ROLE.get(k, "site_rule")} for k, v in h.COMMON.items()} UNITY_STORES = ["fam_p1", "fam_p5", "fam_p2", "fam_p3"] # ④は2026-10-27に追加(スタッフ2人・28組/時)。⑤は2026-10-28に追加(スタッフ4人・46組/時) UNITY_REGISTERS = 1 stores = {} for k in UNITY_STORES: s = dict(h.STORES[k]) stores[k] = s conds = [] rows = {h.key(r): r for r in R["rows"]} for k in UNITY_STORES: s = h.STORES[k] staff = s["staff"][1] # 真ん中の人数 arr = s["arrivals"][1] # 普通 for robots in (0, 1, 2): c = {"store": k, "staff": staff, "robots": robots, "arrivals_per_h": arr, "mode": "serve", "trays": 3 if robots else 1, "fragile_by": "any", # ロボット0台の試算は trays=1 で持っている(使い方・段の数は関係ない) "registers": UNITY_REGISTERS} r = rows[h.key(c)] conds.append({"condition": c, "site_result": {m: r[m] for m in r if m not in c}}) D = { "meta": { "schema_version": "serving-robot-params/0.4", "model": h.COMMON["model"], "reps": h.REPS, "ng_min": h.NG_MIN, "layout_generation": gen.pop(), "source": "p2m-site の public/sim/serving-robot.html と同じ試算(haizen_sim.py)と結果(results.json)", "roles": NOTE, "time_window": "空の店から始め、peak_min 分だけ選んだペースで来客し、全員が帰るまで回す(max_extra_min で打ち切り)", "registers": "会計はレジ1つにつき1組ずつ(pay_s 秒)。食べ終えた組は卓に座ったまま順番を待つ。Unity の条件はすべてレジ1つ(v2.8)", "metrics": { "entry_wait_mean": "入店待ち:店に来てから案内されて卓に着くまで(分)", "full_wait_mean": "そのうち満席で待った時間(分)", "staff_wait_mean": "そのうち席は空いているのに片付け・案内が追いつかず待った時間(分)。ng_min 以上なら「店が回らない」", "dish_wait_mean": "料理ができてから卓に届くまで(分)", "dish_wait_p90": "同じく上位10%の境目(分)", "cold_share": "cold_min 分を超えて届いた皿の割合", "turn_mean": "客が帰ってから卓が使えるまで(分)", "walk_km_per_staff": "スタッフ1人が歩いた距離(km)", "staff_util": "スタッフの手がふさがっている割合", "robot_util": "ロボットがふさがっている割合", "robot_share": "ロボットが運んだ皿の割合", "rescues": "立ち往生でスタッフが動かしに行った回数", "pauses": "一時停止の回数", "groups": "入れた組の数", "pay_wait_mean": "食べ終えてから会計が始まるまで(分)。レジの順番とスタッフの手が空くのを待つ(v2.8)", }, }, "common": common, # 2026-10-27 エイイチと決めた回し方の決まり(案B)。試算の数字には効かない(試算の料理は卓の区別がなく、受け渡し口で積む時間も0秒で順番待ちがないため)。 # 受け渡し口でのロボットどうしのぶつかりは通路で起きることなので Unity が測る。Unity はこの値を使う(切り替えで入れ、まず④で確かめる) "unity_rules": { # 2026-10-28 エイイチと決めた:待機場所へ戻るを標準に。案B(受け渡し口は1台ずつ・同じ卓は1台)は切に戻す # (④・③⑦の夜で、待機場所が受け渡し口から1.5m以内にあるため2台が互いを待ち、往復が減ったため。待機場所との距離の決め方を考え直すまで使わない) "standby_return": {"value": True, "role": "site_rule", "note": "往復を終えたロボットは、受け渡し口の点で止まらず、店の形の RobotStandby(1台に1つ)へ下がって待つ"}, "pass_one_robot": {"value": False, "role": "site_rule", "note": "(切)受け渡し口の前に入れるロボットは1台だけ。前の1台が出発点から pass_clear_m 離れるまで、次の1台は待機場所で待つ"}, "pass_clear_m": {"value": 1.5, "role": "site_rule", "note": "仮の値(pass_one_robot が切のあいだは使わない)"}, "same_table_one_robot": {"value": False, "role": "site_rule", "note": "(切)その卓へ向かっているロボットがいる間は、その卓の料理を別のロボットに積まない"}, }, "stores": stores, "unity_conditions": conds, } json.dump(D, open(out, "w", encoding="utf-8"), ensure_ascii=False, indent=1) print(out, len(conds), "conditions / generation", D["meta"]["layout_generation"]) for c in conds: print(" ", c["condition"], {m: c["site_result"].get(m) for m in ("entry_wait_mean", "staff_wait_mean", "dish_wait_mean", "pay_wait_mean")})