"""混んでいるときの「席は空いているのに待った」のずれを、スタッフの時間の内訳で比べる（2026-10-30）

Unity：夜 20261106_busy の各回の timeline.csv（60秒ごとに、仕事ごとのスタッフの人数）と groups.csv、台帳の待ちの列
試算：haizen_sim.py（v2.8）を同じ条件で 40 回（試算と同じ種 0〜39）回し、スタッフの手がふさがった時間を仕事ごとに分けて数える
      （試算のコードは変えない。手がふさがった時間を足す所で、どの仕事かを見分けて別に足すだけ）

使い方: python3 staff_breakdown.py <haizen_sim.py のフォルダ> <service_sim/unity のフォルダ> <出力フォルダ>
"""
import csv, glob, json, os, statistics, sys, collections

SIM_DIR, UNITY_DIR, OUT = sys.argv[1], sys.argv[2], sys.argv[3]
sys.path.insert(0, SIM_DIR)
import haizen_sim as h

JOBS = ["guide", "serve", "call", "pay", "clear", "rescue"]
JA = {"guide": "案内", "serve": "配膳", "call": "呼び出し", "pay": "会計", "clear": "片付け", "rescue": "ロボット救出"}

# ---- 試算：仕事ごとの手がふさがった時間
class S(h.Store):
    def __init__(self, *a, **k):
        object.__setattr__(self, "by_job", collections.Counter())
        super().__init__(*a, **k)

    def __setattr__(self, name, val):
        if name == "staff_busy_s" and hasattr(self, "staff_busy_s"):
            import inspect
            fn = inspect.currentframe().f_back.f_code.co_name
            add = val - self.staff_busy_s
            if fn == "group":
                job = "guide"
            elif fn == "_staff_carry":
                job = "serve"
            elif fn == "bus":
                job = "clear"
            elif fn == "staff_job":
                f = inspect.currentframe().f_back
                work = f.f_locals.get("work_s")
                job = "pay" if work == self.p["pay_s"] and f.f_locals.get("walk_m") == 0 else (
                      "call" if work == self.p["call_s"] else "clear")
            elif fn == "maybe_stop":
                job = "rescue"
            else:
                job = "other:" + fn
            self.by_job[job] += add
        object.__setattr__(self, name, val)

def site_run(cond, seed):
    store = h.STORES[cond["store"]]
    p = {**h.COMMON, **store, **cond}
    env = h.simpy.Environment()
    s = S(env, h.random.Random(seed), p)
    env.process(s.arrivals())
    env.run()
    n = len(s.entry_waits)
    return {"groups": n, "end_min": env.now / 60,
            "per_group_min": {j: s.by_job.get(j, 0) / 60 / n for j in JOBS},
            "util": s.staff_busy_s / (p["staff"] * env.now),
            "blocked": statistics.mean(s.blocked_waits), "guide_wait": statistics.mean(s.guide_waits),
            "entry": statistics.mean(s.entry_waits), "other": {k: v for k, v in s.by_job.items() if k.startswith("other")}}

# ---- Unity：夜の各回
def unity_run(folder, staff_n):
    tl = list(csv.DictReader(open(os.path.join(folder, "timeline.csv"), encoding="utf-8")))
    gr = [g for g in csv.DictReader(open(os.path.join(folder, "groups.csv"), encoding="utf-8")) if g["state"] == "Done"]
    dt = 60.0
    n = len(gr)
    sec = {j: sum(float(r.get("staff_" + j, 0) or 0) for r in tl) * dt for j in JOBS}
    idle = sum(float(r["staff_idle"]) for r in tl) * dt
    busy = sum(sec.values())
    dirty_untaken = statistics.mean(float(r["tables_dirty_untaken"]) for r in tl)
    return {"groups": n, "per_group_min": {j: sec[j] / 60 / n for j in JOBS},
            "util": busy / (busy + idle) if busy + idle else 0, "dirty_untaken_mean": dirty_untaken,
            "end_min": float(tl[-1]["t_s"]) / 60}

ledger = {r["run_folder"]: r for r in csv.DictReader(open(os.path.join(UNITY_DIR, "unity_ledger_v1.csv"), encoding="utf-8"))}

def med(xs):
    return statistics.median(xs) if xs else float("nan")

params = json.load(open(os.path.join(SIM_DIR, "serving-robot-params.json"), encoding="utf-8"))
rows = []
for c in params["unity_conditions"][18:]:
    cond = c["condition"]
    if cond["arrivals_per_h"] != h.STORES[cond["store"]]["arrivals"][2]:
        continue   # 混んでいるだけ
    pat = os.path.join(UNITY_DIR, f"20261106_night_a{cond['arrivals_per_h']}_cs44_{cond['store']}_r{cond['robots']}_s*_passon_sbret")
    us = []
    for f in sorted(glob.glob(pat)):
        u = unity_run(f, cond["staff"])
        L = ledger.get(os.path.basename(f), {})
        u["blocked"] = float(L.get("blocked_wait_mean") or "nan")
        u["staff_wait"] = float(L.get("staff_wait_mean") or "nan")
        u["entry"] = float(L.get("entry_wait_mean") or "nan")
        us.append(u)
    ss = [site_run(cond, seed) for seed in range(h.REPS)]
    row = {"store": cond["store"], "label": h.STORES[cond["store"]]["label"], "staff": cond["staff"],
           "robots": cond["robots"], "arrivals": cond["arrivals_per_h"], "n_unity": len(us)}
    for side, xs in (("unity", us), ("site", ss)):
        row[side] = {
            "groups": med([x["groups"] for x in xs]),
            "util": med([x["util"] for x in xs]),
            "per_group_min": {j: med([x["per_group_min"][j] for x in xs]) for j in JOBS},
            "total_per_group_min": med([sum(x["per_group_min"].values()) for x in xs]),
            "entry": med([x["entry"] for x in xs]),
            "blocked": med([x["blocked"] for x in xs]),
        }
    row["unity"]["staff_wait"] = med([x["staff_wait"] for x in us])
    row["unity"]["dirty_untaken_mean"] = med([x["dirty_untaken_mean"] for x in us])
    row["site"]["guide_wait"] = med([x["guide_wait"] for x in ss])
    row["site_other"] = sorted({k for x in ss for k in x["other"]})
    rows.append(row)
    print(row["label"], cond["robots"], "台 done", flush=True)

os.makedirs(OUT, exist_ok=True)
json.dump(rows, open(os.path.join(OUT, "staff_breakdown.json"), "w", encoding="utf-8"), ensure_ascii=False, indent=1)
with open(os.path.join(OUT, "staff_breakdown.txt"), "w", encoding="utf-8") as fo:
    fo.write("混んでいるとき：1組あたりのスタッフの手がふさがった時間（分、中央値。Unity 種10個／試算 40回）\n")
    fo.write("店 台数 | 組数 U/S | 稼働率 U/S | " + " | ".join(JA[j] + " U/S" for j in JOBS) + " | 合計 U/S | 入店待ち U/S | 片付け待ち U/S\n")
    for r in rows:
        u, s = r["unity"], r["site"]
        cells = [f"{u['per_group_min'][j]:.2f}/{s['per_group_min'][j]:.2f}" for j in JOBS]
        fo.write(f"{r['label']} {r['robots']}台 | {u['groups']:.0f}/{s['groups']:.0f} | {u['util']:.2f}/{s['util']:.2f} | "
                 + " | ".join(cells) + f" | {u['total_per_group_min']:.2f}/{s['total_per_group_min']:.2f} | "
                 f"{u['entry']:.2f}/{s['entry']:.2f} | {u['blocked']:.2f}/{s['blocked']:.2f}\n")
print("wrote", OUT)
