"""試算(haizen_sim.py)の条件と店の回し方の値を、Unity が読む JSON に書き出す(2026-10-22)。 「何について正か」(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)の値 - unity_conditions:Unity で回す6条件と、同じ条件の試算の結果(40回の平均) """ 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"] 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 で持っている(使い方・段の数は関係ない) 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.1", "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 で打ち切り)", "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": "入れた組の数", }, }, "common": common, "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")})