"""観察シートの記録（LINE・メールで届いた文）を取り込み、試算の前提の表に入れる値の案を出す（2026-10-31）

使い方:
    python3 field_import.py <field のフォルダ> <届いた文.txt> [<届いた文.txt> ...]   # 取り込んで、案を作り直す
    python3 field_import.py <field のフォルダ>                                         # 案だけ作り直す

field のフォルダは iCloud の サービスロボット導入シミュレーション/field/（店のデータは GitHub に入れない）。
- raw/                  届いた文の原文（取り込んだときに写す。直さない）
- observations.csv      1行1回の記録（作業の時間・ロボットの往復）。同じ店・同じ日・同じ時刻・同じ作業の行は二重に入れない
- store_conditions.csv  1通の記録ごとの、その店の条件（スタッフ・卓の数・時間帯・ようす・来店のペースなど）。今は記録だけ
- proposal.md           前提の表に入れる値の案と、今の値との差（毎回作り直す）

決まり（2026-10-31 エイイチと決めた）:
- 1つの値は、測った回数の中央値で入れる
- 店ごとに15回に届くまでは「参考」
- 標準の値（assumptions.json）を置き換える候補にするのは、2店以上・合わせて15回以上そろったとき。
  自動では書き換えない。案と差を見て、エイイチが承認してから表を直す（basis を「現場」、n に回数、source に店の数と期間。店の名前は書かない）
- 店ごとの値で試算を回すのは、今はしない（記録だけ）
- 席へ案内は 2026-10-31 にシートの区切りを「席に着いて説明を始めたら〜テーブルを離れたら」に変えた（歩く時間を含まない＝guide_s と同じ定義）。
  それより前（2026-10-31 16:08 より前）の案内の記録は、歩く時間を含むので案に使わない
"""
import csv
import json
import os
import re
import shutil
import statistics
import sys
from datetime import date, datetime

HERE = os.path.dirname(os.path.abspath(__file__))
GOAL = 15          # 店ごとに「参考」を外す回数（シートの目安と同じ）
MIN_STORES = 2     # 標準の値の候補にする店の数
GUIDE_FROM = datetime(2026, 10, 31, 16, 8)   # これより前の案内の記録は、前の区切り（歩く時間を含む）。シートの直しが公開された時刻

# シートの作業の名前 → 前提の表の行（どれも中央値の秒をそのまま入れる）
TASK_TO_KEY = {
    "会計": "pay_s",
    "片付け": "clear_s",
    "テーブルを拭く": "wipe_s",
    "席へ案内": "guide_s",
    "呼び出し": "call_s",
    "料理を置く": "staff_place_s",
}
ROBOT_TASK = "ロボットの往復"
# ロボットの往復 → 前提の表の行（往復のうち、1回以上止まった・人が動かした往復の割合。試算も「1往復で起きる確率」）
ROBOT_TO_KEY = {"stops": "pause_rate", "helps": "stuck_rate"}

OBS_COLS = ["store", "date", "time", "kind", "sec", "stops", "helps", "raw_file"]
COND_COLS = ["store", "date", "purpose", "time_of_day", "tags", "staff", "robots", "tables",
             "arrived", "left", "arrive_per_h", "left_per_h", "turnover_per_h", "stay_min", "occ0", "note", "raw_file"]


def parse(text):
    """届いた文を読む。読めない行は飛ばす"""
    lines = [l.rstrip() for l in text.replace("\r\n", "\n").split("\n")]
    if not lines or "観察シートの記録" not in lines[0]:
        raise ValueError("観察シートの記録の文ではない（1行目が「観察シートの記録」でない）")
    info = {"store": "", "date": "", "purpose": "", "time_of_day": "", "tags": "", "staff": "", "robots": "",
            "tables": "", "note": "", "arrived": "", "left": "", "arrive_per_h": "", "left_per_h": "",
            "turnover_per_h": "", "stay_min": "", "occ0": ""}
    recs, sec = [], None
    for l in lines[1:]:
        if l.startswith("---"):
            sec = l
            continue
        if sec and "1回ずつの記録" in sec:
            m = re.match(r"(\d\d:\d\d:\d\d)\s+(\S+)\s+([\d.]+)秒(?:（止まった(\d+)回・人が動かした(\d+)回）)?", l)
            if m:
                recs.append({"time": m[1], "kind": m[2], "sec": float(m[3]),
                             "stops": m[4] or "", "helps": m[5] or ""})
            continue
        if sec:
            continue
        if l.startswith("日付："):
            y, mo, d = re.findall(r"\d+", l)[:3]
            info["date"] = f"{int(y):04d}-{int(mo):02d}-{int(d):02d}"
        elif l.startswith("目的："):
            info["purpose"] = l[3:]
        elif l.startswith("お店："):
            m = re.match(r"お店：(.*?)　スタッフ(\d+)人(?:　ロボット(\d+)台)?", l)
            if m:
                info["store"] = "" if m[1] == "（未入力）" else m[1].strip()
                info["staff"], info["robots"] = m[2], m[3] or ""
        elif l.startswith("卓の数："):
            info["tables"] = (re.findall(r"\d+", l) or [""])[0]
        elif l.startswith("時間帯："):
            a = l[4:].split("　", 1)
            info["time_of_day"], info["tags"] = a[0], (a[1] if len(a) > 1 else "")
        elif l.startswith("メモ："):
            info["note"] = l[3:]
        elif l.startswith("出入り："):
            n = re.findall(r"(\d+)組", l)
            if len(n) >= 2:
                info["arrived"], info["left"] = n[0], n[1]
            m = re.search(r"数えはじめに(\d+)卓", l)
            info["occ0"] = m[1] if m else ""
        elif l.startswith("1時間あたり："):
            n = re.findall(r"約(\d+)組", l)
            if len(n) >= 2:
                info["arrive_per_h"], info["left_per_h"] = n[0], n[1]
        elif l.startswith("回転率："):
            info["turnover_per_h"] = (re.findall(r"[\d.]+", l) or [""])[0]
        elif l.startswith("滞在時間の目安："):
            info["stay_min"] = (re.findall(r"\d+", l) or [""])[0]
    if not info["date"]:
        raise ValueError("日付の行がない")
    if not info["store"]:
        info["store"] = "名前なし_" + info["date"]
    return info, recs


def read_csv(path, cols):
    if not os.path.exists(path):
        return []
    with open(path, encoding="utf-8-sig", newline="") as f:
        return list(csv.DictReader(f))


def write_csv(path, cols, rows):
    with open(path + ".tmp", "w", encoding="utf-8-sig", newline="") as f:
        w = csv.DictWriter(f, fieldnames=cols)
        w.writeheader()
        w.writerows(rows)
    os.replace(path + ".tmp", path)


def take(field, src):
    text = open(src, encoding="utf-8").read()
    info, recs = parse(text)
    os.makedirs(os.path.join(field, "raw"), exist_ok=True)
    base = f"{info['date']}_{re.sub(r'[^0-9A-Za-zぁ-んァ-ヶ一-龠ー_]', '', info['store'])}"
    raw_name, i = base + ".txt", 1
    while os.path.exists(os.path.join(field, "raw", raw_name)):
        if open(os.path.join(field, "raw", raw_name), encoding="utf-8").read() == text:
            break      # 同じ文はもう取り込んである
        i += 1
        raw_name = f"{base}_{i}.txt"
    else:
        shutil.copyfile(src, os.path.join(field, "raw", raw_name))

    obs_path = os.path.join(field, "observations.csv")
    obs = read_csv(obs_path, OBS_COLS)
    seen = {(r["store"], r["date"], r["time"], r["kind"]) for r in obs}
    added = 0
    for r in recs:
        k = (info["store"], info["date"], r["time"], r["kind"])
        if k in seen:
            continue
        seen.add(k)
        obs.append({"store": info["store"], "date": info["date"], "time": r["time"], "kind": r["kind"],
                    "sec": r["sec"], "stops": r["stops"], "helps": r["helps"], "raw_file": raw_name})
        added += 1
    write_csv(obs_path, OBS_COLS, obs)

    cond_path = os.path.join(field, "store_conditions.csv")
    conds = [c for c in read_csv(cond_path, COND_COLS) if c["raw_file"] != raw_name]
    conds.append({**{c: info.get(c, "") for c in COND_COLS}, "raw_file": raw_name})
    write_csv(cond_path, COND_COLS, conds)
    print(f"{src}: {info['store']} {info['date']} → 記録 {len(recs)} 回（新しく {added} 回）・原文 raw/{raw_name}")


def fmt(v):
    return "—" if v is None else (f"{v:.3f}".rstrip("0").rstrip(".") if isinstance(v, float) else str(v))


def proposal(field):
    A = json.load(open(os.path.join(HERE, "assumptions.json"), encoding="utf-8"))
    obs = read_csv(os.path.join(field, "observations.csv"), OBS_COLS)
    L = ["# 前提の表に入れる値の案（観察シートの記録から）", "",
         f"作った日：{date.today().isoformat()}　記録 {len(obs)} 回", "",
         f"- 店ごとに {GOAL} 回に届くまでは「参考」。標準の値の候補は、{MIN_STORES}店以上・合わせて {GOAL} 回以上",
         "- 表はまだ書き換えていない。候補を表に入れるかはエイイチが決める（basis を「現場」、n に回数、source に店の数と期間。店の名前は書かない）",
         "- 中央値で比べる。席へ案内は 2026-10-31 16:08 より前の記録を使わない（歩く時間を含む前の区切り）", ""]

    def rows_for(kind):
        out = [r for r in obs if r["kind"] == kind]
        if kind == "席へ案内":
            out = [r for r in out if datetime.fromisoformat(f"{r['date']}T{r['time']}") >= GUIDE_FROM]
        return out

    L += ["## 作業の時間（秒）", "",
          "| 行 | シートの作業 | 今の値（根拠） | 全体の中央値 | 店の数 | 回数 | 判定 | 店ごと（中央値・回数） |",
          "|---|---|---|---|---|---|---|---|"]
    for task, key in TASK_TO_KEY.items():
        rs = rows_for(task)
        cur = A["common"][key]
        by = {}
        for r in rs:
            by.setdefault(r["store"], []).append(float(r["sec"]))
        allv = [float(r["sec"]) for r in rs]
        med = statistics.median(allv) if allv else None
        judge = "候補" if len(by) >= MIN_STORES and len(allv) >= GOAL else ("参考" if allv else "記録なし")
        per = "、".join(f"{s} {fmt(statistics.median(v))}秒・{len(v)}回{'' if len(v) >= GOAL else '（参考）'}" for s, v in by.items())
        L.append(f"| `{key}` | {task} | {cur['value']}（{cur['basis']}） | {fmt(med)} | {len(by)} | {len(allv)} | {judge} | {per or '—'} |")

    L += ["", "## ロボット（往復のうち、起きた往復の割合）", "",
          "| 行 | 数え方 | 今の値（根拠） | 全体 | 店の数 | 往復の回数 | 判定 | 店ごと |", "|---|---|---|---|---|---|---|---|"]
    rs = [r for r in obs if r["kind"] == ROBOT_TASK]
    for f, key in ROBOT_TO_KEY.items():
        cur = A["common"][key]
        by = {}
        for r in rs:
            by.setdefault(r["store"], []).append(1 if int(r[f] or 0) > 0 else 0)
        allv = [x for v in by.values() for x in v]
        rate = sum(allv) / len(allv) if allv else None
        judge = "候補" if len(by) >= MIN_STORES and len(allv) >= GOAL else ("参考" if allv else "記録なし")
        name = "1回以上止まった往復" if f == "stops" else "人が動かした往復"
        per = "、".join(f"{s} {fmt(sum(v) / len(v))}・{len(v)}往復" for s, v in by.items())
        L.append(f"| `{key}` | {name} | {cur['value']}（{cur['basis']}） | {fmt(rate)} | {len(by)} | {len(allv)} | {judge} | {per or '—'} |")
    secs = [float(r["sec"]) for r in rs]
    L += ["", f"ロボットの往復の時間（答え合わせ用・表には入れない）：中央値 {fmt(statistics.median(secs)) if secs else '—'} 秒・{len(secs)} 往復"]

    conds = read_csv(os.path.join(field, "store_conditions.csv"), COND_COLS)
    L += ["", "## 店の条件（今は記録だけ。店ごとの値で試算は回さない）", "",
          "| 店 | 日付 | 時間帯・ようす | スタッフ | ロボット | 卓 | 来店／時 | 回転率／時 | 滞在の目安（分） |", "|---|---|---|---|---|---|---|---|---|"]
    for c in conds:
        L.append(f"| {c['store']} | {c['date']} | {c['time_of_day']} {c['tags']} | {c['staff']} | {c['robots'] or '—'} | {c['tables'] or '—'} "
                 f"| {c['arrive_per_h'] or '—'} | {c['turnover_per_h'] or '—'} | {c['stay_min'] or '—'} |")
    open(os.path.join(field, "proposal.md"), "w", encoding="utf-8").write("\n".join(L) + "\n")
    print("proposal.md を作り直した")


if __name__ == "__main__":
    field = sys.argv[1]
    os.makedirs(field, exist_ok=True)
    for src in sys.argv[2:]:
        take(field, src)
    proposal(field)
