---
title: "サービスロボット導入シミュレーション Log#26 | 配膳ロボットの試算を、店のスマホで測って確かめる"
slug: check-serving-robot-sim-with-phone
date: 2026-10-31
day: 414
meta_description: "配膳ロボットの試算の前提（会計90秒・片付け40秒など）は、店の人がスマホで作業ごとの時間を測り、入ってきた組と会計した組を数えれば確かめられる。そのための観察シートを作り直し、Unityのスタッフには入口番と持ち場を足した。"
tags: [サービスロボット導入シミュレーション, 配膳ロボット, 飲食店, ファミレス, 観察シート, Unity, シミュレーション, UI設計]
series: "導入シミュレーション"
cover: "/media/LOG414_cover.jpg"
---

配膳ロボットの試算が置いている前提（会計は1回90秒、片付けは40秒、ロボットが動けなくなるのは1往復あたり1%など）は、店の人がスマホで作業ごとの時間を測り、入ってきた組と会計した組を数えれば確かめられます。そのための観察シートを、ファミレスの店員さんが片手で迷わず使える形に作り直しました。同じ日に、Unity で再現した3Dのファミレスのスタッフの立ち位置も、実際の店に近づけました。

このシリーズでは、配膳ロボットを入れたファミレスの待ち時間を、サイトの試算（計算で出す待ち時間）と、Unity の3Dシミュレーション（店を建てて人とロボットを歩かせるもの）の2つで調べています。どちらも、元になる数字は今のところ仮の値です。**仮の値のままでは、試算がどれだけ店に合っているかを言えません。** 店で測った数字を入れる入り口が要ります。

ところが、前に作った観察シートは、ロボットが止まった場所と原因を細かく選ぶ形で、1画面に設定と説明がぎっしり並んでいました。店で使うのは、ロボットに詳しい人ではなく、ホールで働くおじさん・おばさんです。忙しい時間にスマホで押すなら、1画面に1つのことだけを出し、押し間違えてもすぐ戻せる形でなければ使われません。

| やったこと | なぜ | 結果 |
|---|---|---|
| Unity のスタッフに「入口番」と「持ち場」を入れた | 手が空いたスタッフが店の奥の壁ぞいに一列に固まり、お客さんが入ってきても気づけない並びだった | 手が空いた人のうち1人はレジ内で入口を見る。ほかの人はパントリーのまわりに1.8m以上離れて待つ |
| 比較動画の素材66本を、新しい立ち位置で撮り直す依頼を出した | 前の夜に撮った素材は、スタッフが固まっていたころの動き | 今夜、依頼箱から Claude Code が撮る |
| サイトの試算と Unity の数字を、同じ条件の72組で並べ直した | 試算を Unity に合わせて直すか、判断するため | 人だけの店では Unity の入店待ちが試算の約1.2〜1.8倍。ロボットが動けなくなる割合は、試算の1%に対して Unity は13.1% |
| 観察シートを、店の人がスマホで使う形に作り直した | 前のシートは、店の人が使うには設定と説明が多すぎた | 目的を選ぶ → お店のこと → 作業を選ぶ → スタート・ストップ、の1画面1つの流れ。送り先は店長やロボットの担当の方 |
| 前のシートは別の住所に残し、サイトを新しいシートに切り替えた | 前の版で残した記録も見られるようにするため | 前の版は検索に出ない設定（noindex）で残した |

下の図で、観察シートの項目を押すと、測った数字が試算のどの前提に入るかと、今の前提の値が出ます。

【図解】LOG414_sheet_to_assumptions.html

配膳ロボットを自分の店に入れたら何人で回るかは、このシリーズの試算をもとにした別のページで試せます。

【バナー】serving-robot-one-less-staff


## 作業ログ ― Unity のスタッフに、入口番と持ち場を入れた

### サイトの試算・Unity・依頼箱・入口番・持ち場は、このシリーズで何をしているか

| 言葉 | 何か | このシリーズでの使い方 |
|---|---|---|
| サイトの試算 | Python の [SimPy](https://simpy.readthedocs.io/)（出来事を時刻の順に進める、待ち行列のシミュレーションの道具）で書いた計算 | [試算のページ](https://pixelstomotors.com/sim/serving-robot.html) の数字。このシリーズでは、数字の正はこちら |
| Unity | ゲームなどを作る3Dの開発環境（[Unity](https://unity.com/ja)） | サイトの店の形どおりに店を建て、スタッフ・お客さん・ロボットを歩かせて、止まる回数や待ち時間を測る |
| 依頼箱 | Claude が書いた依頼を、Mac の見張り係が [Claude Code](https://claude.com/claude-code)（コードを書いて動かす AI）に自動で渡す仕組み | Unity を動かす作業はすべてここから頼む。依頼の番号（依頼41など）はその通し番号 |
| 入口番 | 手が空いたスタッフのうち1人が、レジ内で入口を見ながら待つ決まり | 全員が仕事中なら、入口にだれもいなくてよい（お客さん優先） |
| 持ち場（StaffPost） | 入口番以外の手が空いたスタッフが待つ点。店の形のデータに書く | パントリーのまわりに、1.8m以上離して最大4点を置く |
| 種 | 乱数の出発点。同じ種なら同じお客さんの来方になる | 前と後を比べるときは、同じ店・同じ条件・同じ種で回す |
| 台帳 | Unity の回ごとの記録を1行ずつ足していく表（`unity_ledger_v1.csv`） | 試算の同じ条件の値も同じ行に入っている。あとの比べで使う |

店の番号と形は次のとおりです。② 定番の郊外型（昔働いていたファミレスを記憶から起こした店）、③ 効率重視・入口が角、④ 効率重視・小さめの店、⑤ ゆったり・入口が正面、⑥ ゆったり・入口が横、⑦ 効率重視・席を詰めた店。

### 手が空いたスタッフが、パントリーの壁ぞいに一列に固まっていた

比較動画の素材を見ていて、エイイチが気づいたことです。

> 店舗の奥にスタッフが固まっていますが、実際に全員が奥に配置してしまうとお客さんが入ってきた時にわかりません。必ず1人は入り口近くに配置してください（忙しいと入り口あたりにスタッフがいない時もあるますがそれはお客様優先ですので勘違いしないように注意）

最初、Claude は「1人は入口、ほかは店が見渡せる場所」と受け取り、客席を分けて担当する点を作る案を出しました。エイイチの説明はこうでした。

> 店員はパントリー近くにいることが多いよ（色々準備したり片付けや掃除、配膳準備をするため。その中で散らばっているの意味ね）

そこで、持ち場はパントリーのまわりに散らす形にしました。④だけは、2番の点を入口に近い側へ寄せました（エイイチの指示「④の2番の人は入り口に近い方がいいね」）。パントリーから測る幅を0.5mから0.6mに広げると、④の2番だけが入口寄りの点に変わり、ほかの5店の点は変わりませんでした。

【画像】LOG414_01_staff_before.png 前の動き（依頼38の素材）。③の店をスタッフ4人・ロボット2台で回した Unity の真上の1コマ（店の時刻900秒）。青いスタッフ4人が、パントリーの壁ぞいに一列に並んで待っている

### スタッフの持ち場を作る staff_post_points（unity_export.py）

店の形は、サイトの `tools/sim_layouts/unity_export.py` で作り、Unity はそれを読むだけです（サイトが正）。持ち場の点もここで作りました。候補は0.25mおきの点で、人が立てて、何かに寄っていて、ロボットのふだんの道をふさがない所です。入口・レジ・待合・受け渡し口・トイレ・ドリンクバー・ロボットの待機場所からは1.2m以上離します。パントリーから0.6m以内の候補を、受け渡し口に近い順に見て、すでに選んだ点から1.8m以上離れたものを選びます。

```python
diff --git a/tools/sim_layouts/unity_export.py b/tools/sim_layouts/unity_export.py
index e320ba7..323f459 100644
--- a/tools/sim_layouts/unity_export.py
+++ b/tools/sim_layouts/unity_export.py
@@ -8,7 +8,7 @@
 - floor：床を敷く矩形（建物の外を除く）
 - aisles：細い通路（ボックス席の列と列の間。幅・区画・両端の入口と受け渡し口の前からの道のり）
 - tables：卓ごとの部品（天板・ベンチ・椅子・座る位置・席に着くために立つ点）と、止まる位置・通る細い通路
-- places：場所の点（受け渡し口の前・下げ膳の返却・パントリー・入口・待合・レジ・レジ内・トイレ・ドリンクバー・待機場所・スタッフの待つ点）
+- places：場所の点（受け渡し口の前・下げ膳の返却・パントリー・入口・待合・レジ・レジ内・トイレ・ドリンクバー・待機場所・スタッフの待つ点・スタッフの持ち場）
 
 規則（寸法は famires_v3.FURNITURE_TYPES と同じ）
 - 細い通路：同じ向きのボックス席がすき間なく並んだ「列」を見つけ、隣り合う2つの列のすき間（0.5m以上、1.5m以下）を細い通路とする。
@@ -409,6 +409,70 @@ def staff_standby_points(L, free, aisles):
     return pts[:STAFF_STANDBY]
 
 
+STAFF_POST = 4           # スタッフの持ち場の数（入口番を除く。図面のある店で選べる人数の最大 5 − 1）
+POST_BAND = 0.6          # パントリー（と接する壁）からこの距離以内
+POST_GAP = 1.8           # 持ち場どうしをこれ以上離す
+POST_AVOID = 1.2         # 入口・レジ・待合・受け渡し口・トイレ・ドリンクバー・待機場所からこれ以上離す
+POST_STEP = 0.25         # 候補を探すマス目の間隔
+
+
+def staff_post_points(L, grid, aisles, P, standby_slots):
+    """スタッフの持ち場（2026-10-31 エイイチと決めた）。
+    店員は準備・片付け・掃除・配膳の準備のためパントリーの近くにいることが多いが、1か所に固まらず散らばっている。
+    1人は入口が見えるよう、レジ内（RegisterBack）で待つ（入口番。ここでは作らない）。ほかの人はこの点で待つ。
+    - 候補：POST_STEP おきの点で、人が立てて（ロボットの余裕 0.22m以上）何かに寄っていて（0.6m以下）、
+      余裕が増える向きへ 0.75m 先にロボットのふだんの道（余裕 0.5m 以上）が残る所。細い通路・会計の列（0.3m 外まで）には置かない。
+      入口・レジ・待合・受け渡し口・トイレ・ドリンクバー・待機場所の点から POST_AVOID 以上離す
+    - パントリー（と接する壁を含めた外形）から POST_BAND 以内の候補を、受け渡し口に近い順に見て、
+      すでに選んだ点から POST_GAP 以上離れたものを選ぶ（STAFF_POST まで）
+    - 使い方（Unity）：入口番の1人を除いた人数ぶん、1 から順に使う。手が空いた人は、使う点のうち空いている一番近い所へ"""
+    pan = [r["rect"] for r in L["rooms"] if r["label"] == "パントリー"]
+    if not pan:
+        return []
+    x0, y0, x1, y1 = pan[0]
+    for w in L["walls"]:
+        r = w["rect"]
+        if touch_segment_simple(r, (x0, y0, x1, y1)):
+            x0, y0, x1, y1 = min(x0, r[0]), min(y0, r[1]), max(x1, r[2]), max(y1, r[3])
+    W, D = L["outer"]["w"], L["outer"]["d"]
+
+    def clr(x, y):
+        cy, cx = grid.cell(x, y)
+        if not (0 <= cy < grid.ny and 0 <= cx < grid.nx):
+            return -1.0
+        return float(grid.clear[cy, cx])
+    avoid = [p["point"] for p in P if p["kind"] in ("Entrance", "Register", "RegisterQueue", "RegisterBack", "Waiting", "FoodPass",
+                                                     "Toilet", "DrinkBarQueue", "DrinkBarStand")]
+    avoid += [[(s[0] + s[2]) / 2, (s[1] + s[3]) / 2] for s in standby_slots]
+    keep = L.get("robot_keepout", [])
+    fp = L["start"]
+    cands = []
+    for x in np.arange(POST_STEP, W, POST_STEP):
+        for y in np.arange(POST_STEP, D, POST_STEP):
+            if math.hypot(max(x0 - x, 0, x - x1), max(y0 - y, 0, y - y1)) > POST_BAND:
+                continue
+            c = clr(x, y)
+            if not (0.22 <= c <= 0.6) or _in_aisle(aisles, x, y):
+                continue
+            if any(k[0] - 0.3 <= x <= k[2] + 0.3 and k[1] - 0.3 <= y <= k[3] + 0.3 for k in keep):
+                continue
+            if any(math.hypot(x - a[0], y - a[1]) < POST_AVOID for a in avoid):
+                continue
+            gx, gy = clr(x + 0.1, y) - clr(x - 0.1, y), clr(x, y + 0.1) - clr(x, y - 0.1)
+            g = math.hypot(gx, gy)
+            if g < 1e-6 or clr(x + gx / g * 0.75, y + gy / g * 0.75) < 0.5:
+                continue
+            cands.append((float(x), float(y)))
+    cands.sort(key=lambda p: (round(math.hypot(p[0] - fp[0], p[1] - fp[1]), 6), p[0], p[1]))
+    out = []
+    for p in cands:
+        if all(math.hypot(p[0] - q[0], p[1] - q[1]) >= POST_GAP for q in out):
+            out.append(p)
+        if len(out) == STAFF_POST:
+            break
+    return [[r2(x), r2(y)] for x, y in out]
+
+
 def touch_segment_simple(a, b):
     """2つの矩形が接しているか重なっているか"""
     return not (a[2] < b[0] - 1e-6 or b[2] < a[0] - 1e-6 or a[3] < b[1] - 1e-6 or b[3] < a[1] - 1e-6)
@@ -507,6 +571,9 @@ def places(L, grid, standby_slots, aisles):
     # スタッフが待つ点：受け渡し口の前の横（待機場所と反対の側）に、パントリーの壁ぞいへ 0.6m おき（仮の規則）
     for i, pt in enumerate(staff_standby_points(L, free, aisles)):
         P.append(dict(kind="StaffStandby", label=f"スタッフが待つ点 {i + 1}", point=pt))
+    # スタッフの持ち場（2026-10-31 エイイチ）：パントリーのまわりに散らした点。入口番はレジ内（RegisterBack）
+    for i, pt in enumerate(staff_post_points(L, grid, aisles, P, standby_slots)):
+        P.append(dict(kind="StaffPost", label=f"スタッフの持ち場 {i + 1}", point=pt))
     for i, s in enumerate(standby_slots):
         P.append(dict(kind="RobotStandby", label=f"ロボットの待機場所 {i + 1}", rect=rect(*s), point=[r2((s[0] + s[2]) / 2), r2((s[1] + s[3]) / 2)]))
     return P
```

できた点は次のとおりです（単位は m。原点は店の外壁の内側の左下の角）。ファイル名の p0 は②、p1 は③、p2 は④、p3 は⑤、p4 は⑥、p5 は⑦の店です。卓・卓までの距離・ロボットが止まる位置・地図の数字は、6店とも前と同じです。店の形の版は `08db8a0d067c` から `9b8dae7ebf8c` に変わりました。

```text
p0_memory レジ内（入口番） [11.05, 3.3] 持ち場 (9.5, 5.5) (9.0, 7.25) (9.25, 9.25) (9.25, 11.25)
p1_efficient_corner レジ内（入口番） [4.45, 1.65] 持ち場 (14.0, 6.0) (13.75, 8.0) (13.75, 10.0) (13.75, 12.0)
p2_efficient_narrow レジ内（入口番） [13.25, 1.65] 持ち場 (9.75, 4.5) (9.75, 9.0) (11.25, 10.25)
p3_relaxed_center レジ内（入口番） [10.8, 1.55] 持ち場 (11.25, 9.0) (9.25, 9.0) (17.5, 9.0) (7.25, 9.0)
p4_relaxed_side レジ内（入口番） [4.75, 1.25] 持ち場 (13.0, 6.0) (12.5, 7.75) (12.75, 9.75) (12.75, 11.75)
p5_crammed レジ内（入口番） [4.45, 1.65] 持ち場 (14.0, 6.0) (13.5, 7.75) (13.75, 9.75) (13.75, 11.75)
```

### 試算の決まりの表（serving-robot-params.json）に足した entrance_keeper と staff_posts

Unity は、回し方の決まりもサイトの `serving-robot-params.json` の `unity_rules` から読みます。入口番（`entrance_keeper`）と持ち場（`staff_posts`）をここに足しました。試算はスタッフの立ち位置を持たないので、試算の数字は変わりません。3回のコミットの差分です。

```diff
# commit 1449063
diff --git a/public/sim/serving-robot-params.json b/public/sim/serving-robot-params.json
index be8f2cd..f00ce12 100644
--- a/public/sim/serving-robot-params.json
+++ b/public/sim/serving-robot-params.json
@@ -160,6 +160,11 @@
    "value": false,
    "role": "site_rule",
    "note": "（切）その卓へ向かっているロボットがいる間は、その卓の料理を別のロボットに積まない"
+  },
+  "entrance_keeper": {
+   "value": true,
+   "role": "site_rule",
+   "note": "手が空いているスタッフがいるときは、そのうち1人がレジ内（店の形の RegisterBack）で待つ。お客さんが入ってきたときに気づけるようにするため（2026-10-31 エイイチ）。忙しくて全員が仕事中なら入口に誰もいなくてよい（お客さん優先。わざわざ入口へ戻すために仕事を止めない）。手が空いたスタッフは、レジ内にだれもいない（向かっている人もいない）ときだけレジ内へ行き、ほかは今までどおりスタッフが待つ点へ。レジ内で待つ人も、ほかの人と同じように仕事を取る。試算（haizen_sim）はスタッフの立ち位置を持たないので、数字は変わらない"
   }
  },
  "stores": {
# commit 0dfcc5a
diff --git a/public/sim/serving-robot-params.json b/public/sim/serving-robot-params.json
index f00ce12..a1d9412 100644
--- a/public/sim/serving-robot-params.json
+++ b/public/sim/serving-robot-params.json
@@ -4,7 +4,7 @@
   "model": "v2.9",
   "reps": 40,
   "ng_min": 10,
-  "layout_generation": "08db8a0d067c",
+  "layout_generation": "9b8dae7ebf8c",
   "source": "p2m-site の public/sim/serving-robot.html と同じ試算（haizen_sim.py）と結果（results.json）",
   "roles": {
    "unity_measures": "Unity で測ってサイトに戻すための仮の値。Unity は読まない",
@@ -164,7 +164,12 @@
   "entrance_keeper": {
    "value": true,
    "role": "site_rule",
-   "note": "手が空いているスタッフがいるときは、そのうち1人がレジ内（店の形の RegisterBack）で待つ。お客さんが入ってきたときに気づけるようにするため（2026-10-31 エイイチ）。忙しくて全員が仕事中なら入口に誰もいなくてよい（お客さん優先。わざわざ入口へ戻すために仕事を止めない）。手が空いたスタッフは、レジ内にだれもいない（向かっている人もいない）ときだけレジ内へ行き、ほかは今までどおりスタッフが待つ点へ。レジ内で待つ人も、ほかの人と同じように仕事を取る。試算（haizen_sim）はスタッフの立ち位置を持たないので、数字は変わらない"
+   "note": "手が空いているスタッフがいるときは、そのうち1人がレジ内（店の形の RegisterBack）で待つ（入口番）。お客さんが入ってきたときに気づけるようにするため（2026-10-31 エイイチ）。忙しくて全員が仕事中なら入口にだれもいなくてよい（お客さん優先。入口へ戻すために仕事を止めない）。手が空いたスタッフは、レジ内にだれもいない（向かっている人もいない）ときだけレジ内へ行き、ほかは staff_posts の点へ。入口番も、ほかの人と同じように仕事を取る。試算（haizen_sim）はスタッフの立ち位置を持たないので、数字は変わらない"
+  },
+  "staff_posts": {
+   "value": true,
+   "role": "site_rule",
+   "note": "入口番以外の手が空いたスタッフは、店の形の StaffPost（スタッフの持ち場。パントリーのまわりに1.8m以上離して散らした点。受け渡し口に近い順に番号）で待つ（2026-10-31 エイイチ：店員は準備・片付け・掃除・配膳の準備のためパントリーの近くにいることが多いが、1か所に固まらず散らばっている）。入口番の1人を除いた人数ぶん、1 から順に使い、手が空いた人は使う点のうち空いている一番近い所へ行く。点が足りないときは、今までの StaffStandby を使う。切のときは今までどおり StaffStandby（パントリーの壁ぞいに0.6mおき）。試算の数字は変わらない"
   }
  },
  "stores": {
# commit 5cbfa59
diff --git a/public/sim/serving-robot-params.json b/public/sim/serving-robot-params.json
index a1d9412..946a08d 100644
--- a/public/sim/serving-robot-params.json
+++ b/public/sim/serving-robot-params.json
@@ -164,7 +164,7 @@
   "entrance_keeper": {
    "value": true,
    "role": "site_rule",
-   "note": "手が空いているスタッフがいるときは、そのうち1人がレジ内（店の形の RegisterBack）で待つ（入口番）。お客さんが入ってきたときに気づけるようにするため（2026-10-31 エイイチ）。忙しくて全員が仕事中なら入口にだれもいなくてよい（お客さん優先。入口へ戻すために仕事を止めない）。手が空いたスタッフは、レジ内にだれもいない（向かっている人もいない）ときだけレジ内へ行き、ほかは staff_posts の点へ。入口番も、ほかの人と同じように仕事を取る。試算（haizen_sim）はスタッフの立ち位置を持たないので、数字は変わらない"
+   "note": "手が空いているスタッフがいるときは、そのうち1人がレジ内（店の形の RegisterBack）で待つ（入口番）。お客さんが入ってきたときに気づけるようにするため（2026-10-31 エイイチ）。忙しくて全員が仕事中なら入口にだれもいなくてよい（お客さん優先。入口へ戻すために仕事を止めない）。手が空いたスタッフは、レジ内にだれもいない（向かっている人もいない）ときだけレジ内へ行き、ほかは staff_posts の点へ。会計中のスタッフは、レジ内にいて入口が見えるので、入口番に数える（2026-10-31 エイイチ）。レジ内は1人ぶんなので、会計の人がいる間、手が空いた人は持ち場で待つ。実際の現場では、会計中の人が入口を兼ねる場合と、ほかのスタッフが案内する場合はケースバイケース（案内の仕事は、今までどおり手が空いた人が取る）。入口番も、ほかの人と同じように仕事を取る。試算（haizen_sim）はスタッフの立ち位置を持たないので、数字は変わらない"
   },
   "staff_posts": {
    "value": true,
```

### 依頼41「入口番とスタッフの持ち場を入れる」の全文

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 180
---
# 依頼：入口番とスタッフの持ち場を入れ、③と④を1本ずつ撮って確かめる（2026-10-31 昼）

エイイチの指摘（2026-10-31）：Unity では手が空いたスタッフが店の奥（パントリーの壁ぞい）に固まっている。実際は、
- 全員が奥にいると、お客さんが入ってきたときに分からない。**手が空いた人のうち1人は必ず入口が見える所にいる**（入口番）。ただし忙しくて全員が仕事中なら、入口にだれもいなくてよい（お客さん優先。入口へ戻すために仕事を止めない）
- ほかの人は、準備・片付け・掃除・配膳の準備のため**パントリーの近くにいることが多いが、1か所に固まらず散らばっている**

サイト（正）は直して公開済み（p2m-site main 0dfcc5a）：
- 店の形：6店に `places` の `StaffPost`（スタッフの持ち場 1〜4。④は1〜3）を足した。版 08db8a0d067c → **9b8dae7ebf8c**。卓・距離・止まる位置・地図の数字は6店とも前と同じ（変わったのは generation・rules の文・StaffPost だけ）
- params：`meta.layout_generation` を 9b8dae7ebf8c に。`unity_rules` に `entrance_keeper`（true）と `staff_posts`（true）を足した。中身は note を読むこと。試算の数字は変わっていない

## 始める前に確かめること（違えば回さずに報告して終わる）

1. Unity エディタが起動していない
2. /Volumes/KIOXIA がマウントされている。台帳の鍵ファイル unity_ledger_v1.lock がない
3. ServiceSim の保留が 0
4. 夜の作業（night_runner）が動いていない

## やること

1. 公開版の params と6店の unity json・map を、いつもの手順でプロジェクトに写す。`unity_rules`・`meta.layout_generation`・`places` の StaffPost 以外が今の写しと同じかを確かめて報告に書く
2. **入口番**と**持ち場**を切り替えで入れる。既定は params の `unity_rules` の値（どちらも入）＝これからの標準
   - `-ssEntranceKeeper on|off`・`-ssStaffPosts on|off`、make_queue / night_runner / rec_runner の `--entrance-keeper on|off`・`--staff-posts on|off` で回ごとに上書きできるようにする（待機場所へ戻る・案B と同じ作り）。run_info.json に書く。出力フォルダ名の末尾は、入のとき `_ek`・`_post`（名前の付け方は今の `_passon_sbret` に続ける）。台帳の列は増やさない
   - 入口番：手が空いたスタッフは、レジ内（RegisterBack）にだれもいない（向かっている人もいない）ときだけレジ内へ行って待つ。ほかの人は持ち場へ。入口番も、ほかの人と同じように仕事を取る（入口番だから仕事を取らない、はしない）。仕事を終えた人は、レジ内が空いていればまずレジ内へ
   - 持ち場：入口番の1人を除いた人数ぶん、StaffPost の 1 から順に使う。手が空いた人は、使う点のうち空いている一番近い所へ行く。点が足りないとき（④でスタッフが5人など）は今までの StaffStandby を使う
   - 切のときは、今までどおり（StaffStandby に受け渡し口に近い順）
   - キッチンのモニターに、入口番がだれかと、各スタッフの持ち場の番号が分かる表示を足す
3. check-in して、画面なしの実行ファイルを作り直す（保留 0）
4. 確かめの録画を2本撮る。依頼38と同じ録画のしくみ（rec_runner・幅2560・0.5秒ごと・15倍速）、すれ違い 入・待機場所へ戻る 入・案B 切、入口番 入・持ち場 入。名札は `postcheck`、記録は night/20261031_postcheck/
   - ③（fam_p1）普通・真ん中の人数（スタッフ4人）・ロボット2台・種7
   - ④（fam_p2）普通・真ん中の人数（スタッフ2人）・ロボット2台・種1
5. 4 の2本の数字を、昨夜のストック（night/20261030_rec の同じ店・同じ条件・同じ種）と並べる

## 変えないもの

- 店の回し方のほかの決まり・人とロボットの動き・速さ・物理の設定・すれ違いの作り
- 台帳の列（59列）と今までの行、昨夜のストック（20261030_rec）と基準データ

## 報告

依頼箱の結果に加えて、claude_code_reports に 20261031_staff_posts.txt として書く（場所：~/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity/claude_code_reports/）。

- 始める前の確かめ4つの結果、1 の写しの比べ
- 入れた cs 番号・build_id と保留の有無・変えたファイル・作りで決めたところ
- 4 の2本の動画のパスと、5 の比べ：入店待ち・そのうち席は空いているのに待った時間・料理の待ち・会計待ち・入れた組の数・30秒以上の止まり・動けなくなった（60秒）・ロボット救出・スタッフ1人が歩いた距離
- 4 の2本で、手が空いたスタッフがレジ内にいた時間の割合と、だれも入口番がいなかった時間の割合（全員が仕事中だった時間とそうでない時間に分けて）
- 全員が帰らなかった回・失敗した回があればその名前
- 決めてほしいこと（なければ「なし」）
```

### 依頼41 の結果（依頼箱の報告）

```markdown
# 結果：20261031_41_入口番とスタッフの持ち場を入れる

- 状態：完了
- 始めた時刻：2026-10-31 09:54
- 終わった時刻：2026-10-31 10:27（33分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 8f6da939-a9ba-4604-b3fa-168b0c332a87`

## Claude Code の報告

**やったこと**
- 始める前の確かめ4つは、すべて合格でした。Unity エディタは起動しておらず、KIOXIA はマウント済みで鍵ファイルもなし、保留 0（cs:49）、night_runner も動いていませんでした。
- 公開版（版 9b8dae7ebf8c）の params と、6店の JSON・地図・summary.json を写しました。今の写しと項目ごとに比べ、違うのは依頼にある所だけでした（版の番号、rules の1行、places の StaffPost、unity_rules の entrance_keeper と staff_posts）。卓・止まる位置・地図の数字・試算は同じです。控えは unity/site_copy_9b8dae7ebf8c/ に置きました。
- 入口番と持ち場を、切り替えで入れました。
  - 既定は params の値で、どちらも入です。回ごとの上書きは、実行ファイルの `-ssEntranceKeeper`・`-ssStaffPosts` と、make_queue・night_runner・rec_runner の `--entrance-keeper`・`--staff-posts` でできます。
  - 入の回は、出力フォルダ名の末尾に `_ek`・`_post` が付きます。入切は run_info.json に書きます。
  - キッチンのモニターに、入口番がだれかと持ち場の番号の行を足しました。
  - どちらも切の回は、今までと同じ動きです。台帳の列は 59 列のままです。
- check-in は cs:50 と cs:51 の2つに分かれました。1ファイルで試したときに PeopleNav.cs だけが先に入ってしまったためで、コメントは直してあります。
- 実行ファイルは、画面なし用と録画用の両方を作り直しました（build_id cs51_20261031_1008、保留 0）。
- 録画を2本撮りました。2本とも最後まで回り、全員が帰りました。失敗した回・全員が帰らなかった回はありません。
  - ③：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261031_postcheck_cs51_fam_p1_r2_s7_passon_sbret_ek_post/recordings/20261031_postcheck_cs51_fam_p1_r2_s7_passon_sbret_ek_post_whole.mp4
  - ④：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261031_postcheck_cs51_fam_p2_r2_s1_passon_sbret_ek_post/recordings/20261031_postcheck_cs51_fam_p2_r2_s1_passon_sbret_ek_post_whole.mp4
- ③の店の時刻 900 秒の1コマを見ました。1人がレジ内、3人がパントリーの壁ぞいに約 2m おきに散っていて、前のように固まってはいませんでした。

**結果**（昨夜のストック → 今回。待ちは分。種1つずつなので、差の理由は調べていません）

| | ③（スタッフ4人・種7） | ④（スタッフ2人・種1） |
|---|---|---|
| 入店待ち | 3.54 → 2.82 | 21.95 → 27.08 |
| そのうち席は空いているのに待った | 1.51 → 0.81 | 19.98 → 21.52 |
| 料理の待ち | 1.85 → 2.05 | 2.89 → 4.11 |
| 会計待ち | 7.93 → 10.64 | 4.56 → 5.72 |
| 入れた組の数 | 81 → 81 | 54 → 54 |
| 30秒以上の止まり | 22 → 40 | 80 → 56 |
| 動けなくなった（60秒） | 12 → 31 | 66 → 48 |
| ロボット救出 | 12 → 31 | 69 → 48 |
| スタッフ1人が歩いた距離（km） | 2.69 → 2.71 | 2.64 → 2.66 |

入口番について（回し始めから全員が帰るまで。閉店後も含みます）：

| | ③ | ④ |
|---|---|---|
| 全員が仕事中だった時間の割合 | 36.8% | 82.2% |
| 手が空いた人がいる時間のうち、手が空いたスタッフがレジ内にいた | 36.2% | 79.6% |
| 手が空いた人がいる時間のうち、だれも入口番がいなかった | 63.8% | 20.4% |
| 　そのうち、会計の人も含めてレジ内にだれもいなかった | 9.1% | 6.0% |
| 全員が仕事中の時間のうち、入口番がいなかった（決まりどおり） | 100% | 100% |

③で入口番がいない時間が多いのは、会計の人がレジ内を使っているあいだ、入口番が入れない作りにしたためです。その時間もレジ内には会計の人がいます。

台帳は 1458 → 1460 行で、追記だけです。先頭 1458 行の md5 は退避と同じです。報告は claude_code_reports/20261031_staff_posts.txt に書きました。

**変えたファイル**
- 写し：Assets/ServiceSim/Experiment/serving-robot-params.json と、Store/Layouts/ の6店の JSON・地図・summary.json
- コード：StorePlan.cs・StoreBuilder.cs・PeopleTargets.cs・PeopleSpawner.cs・StaffAgent.cs・PeopleNav.cs・ServiceOps.cs・SimParams.cs・ExperimentRunner.cs・HeadlessRun.cs・TripLogger.cs・KitchenMonitor.cs
- エディタ：ServiceSimStageB.cs・ServiceSimSiteRun.cs（エディタで回すときのフォルダ名にも `_ek`・`_post`。依頼の範囲の少し外ですが、名前をそろえるために足しました）
- 道具：tools/make_queue.py・tools/night_runner.py
- そのほか：night/20261031_postcheck/ に rec_runner.py を写しました（中身は昨夜と同じ）。台帳の控えを archive/unity_ledger_v1_pre_postcheck_20261031/ に置きました。

**決めてほしいこと**
1. 会計の人がレジ内にいるあいだは、入口番は入らず持ち場へ下がる作りにしました（レジ内は1人ぶんのため）。会計の人を「入口が見える人」に数えてよいですか。数えないなら、入口番の立つ点をレジ内とは別に決める必要があります。
2. ③で動けなくなった（60秒）が 12 → 31 回に増え、④で入店待ちが 5.1 分延びました。入口番・持ち場の入と切を、種 0〜9 で並べて回しますか。
3. judge_night.py は入口番・持ち場を条件に入れていないので、それなしの基準と比べています。条件に足しますか。
4. 残ったものを消すか残すか決めてください。KIOXIA の確かめの途中の記録（20261031_smoketest41_…_stopped）、Builds/rec4_compiletest、tools/__pycache__ の night_runner の pyc です。
5. check-in が cs:50（PeopleNav.cs だけ）と cs:51 に分かれました。このままでよいですか。
```

【画像】LOG414_02_staff_after.png 入口番と持ち場を入れたあと（依頼41の確かめの1本）。同じ③の店・同じ条件・同じ種の、店の時刻900秒の1コマ。青いスタッフは、パントリーの壁ぞいに1人、左下のレジのまわりに3人。一列には並んでいない

③の店では、手が空いた人がいる時間のうち63.8%で「入口番がいない」と数えられました。理由は、会計の人がレジ内を使っている間、入口番が入れない作りにしたためです。Claude Code は、会計の人を「入口が見える人」に数えてよいかを聞いてきました。

### 会計中のスタッフも入口番に数える

> 実際の現場では、会計中の人が入り口を兼ねる場合と他のスタッフが案内するのケースバイケースです。

エイイチの答えで、会計中の人も入口番に数えることにしました。動きは変えず、数え方だけを変えています。

- ねらい：入口が見える人がいた時間を、現場の感覚に合う形で数える
- 変わったこと：③で「手が空いた人がいる時間のうち、入口番がいた」が36.2%から90.9%に、④で79.6%から94.0%になった（依頼42の報告。今ある記録から出した値で、多めに出る）
- 見込める効果：今夜の66本から、入口番がいた割合を正しい数え方で比べられる。値は依頼43の報告で確かめる

### 依頼42「入口番の数え方と判定の条件と片付け」の全文

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 90
---
# 依頼：入口番の数え方を直し、判定に入口番・持ち場の条件を足し、試しの残りを片付ける（2026-10-31 昼）

前の報告（20261031_staff_posts.txt）の「決めてほしいこと」へのエイイチの答え：
1. **会計中の人を入口番に数える**（レジ内にいて入口が見えるため）。動き（会計の人がいる間、手が空いた人は持ち場で待つ）は今のまま。実際の現場では、会計の人が入口を兼ねるか、ほかの人が案内するかはケースバイケース。サイトの params の entrance_keeper の説明も直した（p2m-site 5cbfa59。値・ほかの項目は変わっていない）
2. 今夜は予定どおり66本を撮り直す（別の依頼43。この依頼では回さない）
3. 判定（judge_night.py）に入口番・持ち場の入切を条件として足す
4. 試しの残り（smoketest41 の途中の記録・Builds/rec4_compiletest・tools/__pycache__ の night_runner の pyc）は消す
5. cs:50 と cs:51 に分かれたのはこのままでよい

## 始める前に確かめること（違えば回さずに報告して終わる）

1. Unity エディタが起動していない・night_runner／rec_runner が動いていない
2. /Volumes/KIOXIA がマウントされている。unity_ledger_v1.lock がない
3. ServiceSim の保留が 0（cs:51 head）

## やること

1. 公開版の params（5cbfa59）をプロジェクトに写す。entrance_keeper の note 以外が今の写しと同じかを確かめる
2. summary.json の staff_places の数え方：「入口番がいた」を、手が空いた人がレジ内にいる時間＋会計中の人がレジ内にいる時間にする（今の「だれかがレジ内にいた」に当たるもの）。前の数え方の値も消さずに残す（名前を分ける）。動きは変えない
3. judge_night.py：入口番（entrance_keeper）と持ち場（staff_posts）の入切を、待機場所へ戻る・案B と同じように条件に足す。run_info.json に記録がない前の回は「切・切」として扱う。基準データ（v5 など）の決め方・判定の決まりそのものは変えない
4. 3 の確かめ：night/20261031_postcheck/ を判定し直し、③の回が「入・入」の条件として扱われる（基準のない条件になる）ことを見る。今までの夜（20261102 など）の判定結果が変わらないことも1つ確かめる
5. 4 の残りを消す（KIOXIA の 20261031_smoketest41_…_stopped/・プロジェクトの Builds/rec4_compiletest・tools/__pycache__ の night_runner の pyc）。台帳にその回の行がないことも確かめる
6. check-in して、画面なしの実行ファイル（Builds/headless）と録画用の実行ファイルを作り直す（保留 0）。今夜の依頼43はこの実行ファイルで撮る

## 変えないもの

- 人とロボットの動き・店の回し方・速さ・物理の設定
- 台帳の列（59列）と今までの行、昨夜のストック（20261030_rec）と基準データ、確かめの2本（20261031_postcheck）の出力

## 報告

依頼箱の結果に加えて、claude_code_reports に 20261031_keeper_count.txt として書く。
- 始める前の確かめ・1 の比べ
- cs 番号・build_id・保留・変えたファイル
- 確かめの2本（③④）の、新しい数え方での「入口番がいた／いなかった」の割合（手が空いた人がいる時間・全員が仕事中の時間に分けて）
- 4 の判定の結果、5 で消したもの
- 決めてほしいこと（なければ「なし」）
```

### 依頼42 の結果（依頼箱の報告）

```markdown
# 結果：20261031_42_入口番の数え方と判定の条件と片付け

- 状態：完了
- 始めた時刻：2026-10-31 10:32
- 終わった時刻：2026-10-31 10:40（8分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume e49739a6-bfc2-419b-986a-e359b7b6e4f5`
- 止められた操作：1件（いちばん下に一覧）

## Claude Code の報告

**やったこと**
- 始める前の確かめを3つ行い、すべて合格でした。Unity エディタは起動しておらず（Unity Hub だけ）、night_runner・rec_runner も動いていませんでした。KIOXIA はマウントされ、lock はなく、保留は 0（cs:51 head）でした。
- 公開版の params（p2m-site 5cbfa59）をプロジェクトに写しました。今の写しとの違いは、entrance_keeper の note だけでした。
- summary.json の staff_places に、新しい数え方の「入口番がいた」秒数を keeper_present_busy_s・keeper_present_free_s という名前で足しました。手が空いた人か会計中の人がレジ内にいた秒数を、全員が仕事中の時間とそうでない時間に分けて数えます。前の数え方の値は消さずに残し、動きは変えていません。
- judge_night.py の条件に、入口番と持ち場の入切を足しました。run_info.json に記録がない前の回は「切」として扱います。判定の決まりと基準データの既定（v5）は変えていません。
- 確かめの2本（③④）を判定し直しました。前の判定の結果は judge_before42.md・judge_before42.csv として同じフォルダに残しています。今までの夜の判定が変わらないことも確かめました。
- 試しの残り3つを消し、cs:52 で check-in しました。そのあと、画面なしの実行ファイルと録画用の実行ファイルを作り直しました。

**結果**
- 判定し直した結果
  - ③④とも「入口番入・持ち場入」の条件として扱われ、2本とも基準なしになりました（③は正常から基準なしに変わりました）。
  - 20261102 は、保存の判定と同じ基準（v4）で判定し直すと judge.csv がバイトまで同じでした。今の基準（v5）でも、前の版と新しい版の結果がバイトまで同じ（正常 60）でした。
  - 20261107_staff は、前の版・新しい版・保存の3つが同じでした（基準なし 360）。
- 新しい数え方での入口番の割合（下の表）
  - この2本は cs:51 で撮ったため、新しい項目がありません。回すと台帳に行が増えるので、この依頼では回していません。
  - そのため、今ある「だれかがレジ内にいた」秒数から出しました。会計以外の仕事中の人がレジ内を通った時間が入るぶん、「いた」は多めの値です。新しい項目での値は、今夜の依頼43から出ます。

| | ③ | ④ |
|---|---|---|
| 手が空いた人がいる時間のうち、入口番がいた | 90.9%（いなかった 9.1%） | 94.0%（いなかった 6.0%） |
| 　（前の数え方では） | 36.2% | 79.6% |
| 全員が仕事中の時間のうち、入口番がいた | 69.8%（いなかった 30.2%） | 38.6%（いなかった 61.4%） |
| 回した時間全体で、入口番がいた | 83.1% | 48.5% |

- 消したもの
  - KIOXIA の 20261031_smoketest41_…_stopped/（16 MB）
  - プロジェクトの Builds/rec4_compiletest（59 MB）
  - tools/__pycache__ の night_runner の pyc
  - 台帳には、消した回の行が 0 行でした（消す前に確かめました）。
  - 途中で私が作ってしまった judge_night の pyc も消しました。
- check-in と実行ファイル
  - check-in は cs:52 です。check-in の前にコンパイルを確かめ、error CS は 0 でした。
  - 画面なしの実行ファイル Builds/headless は build_id cs52_20261031_1038 です。
  - 録画用の実行ファイル Builds/rec6_keeper52 は build_id cs52_20261031_1039 です。続けて作ったので末尾の分が1つずれていますが、中身は同じ cs:52 です。
  - どちらも Success で、保留は 0 です。今夜の依頼43はこの2つで撮ります。
- 台帳は 1460 行・59 列のままで、md5 も始める前と同じです。

**変えたファイル**
- 同じ check-in（cs:52）に入れた5つ（場所は Assets/ServiceSim の下と tools）
  - Experiment/serving-robot-params.json
  - Service/ServiceOps.cs
  - Experiment/ExperimentRunner.cs
  - Experiment/TripLogger.cs
  - tools/judge_night.py
- KIOXIA：night/20261031_postcheck/ の judge.md・judge.csv（書き直し）と、judge_before42.md・judge_before42.csv（前の判定の控え。足したもの）
- 報告：claude_code_reports/20261031_keeper_count.txt

**決めてほしいこと**
なし

## 止められた操作

- {"tool_name": "Bash", "tool_use_id": "toolu_015vqQB2YFrocsXNiKxDhAdS", "tool_input": {"command": "cd /Volumes/KIOXIA/r2r2r-data/service_sim/unity && rm -rf ./20261031_smoketest41_cs49_fam_p2_r2_s1_passon_sbret_ek_post_stopped && rm -rf /Users/shimazueiichi/Robotics_UX/ServiceSim/Builds/rec4_compiletest && rm /Users/shimazueiichi/Robotics_UX/ServiceSim/tools/__pycache__/night_runner.cpython-312.pyc; ls -d *smoketest41* 2>&1; ls /Users/shimazueiichi/Robotics_UX/ServiceSim/Builds /Users/shimazueiic
```

### 依頼43「比較動画の素材を入口番と持ち場ありで撮り直す」の全文

前の夜に撮った比較動画の素材66本は、スタッフが固まっていたころの動きです。比較動画をこのまま作ると、現実と違う並び方を見せることになるので、撮り直してから作ることにしました（エイイチの判断「直してから撮り直す」）。依頼43は今夜、依頼42の結果を確かめてから依頼箱へ入れます。

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 720
---
# 依頼：比較動画の素材66本を、入口番と持ち場ありで撮り直す（夜・2026-10-31）

エイイチと決めたこと：昨夜のストック（night/20261030_rec・依頼38）は、手が空いたスタッフが店の奥に固まる前の動きで撮った。入口番（レジ内）と持ち場（パントリーのまわりに散らす）を入れたので（依頼41・42、版 9b8dae7ebf8c）、比較動画の素材を同じ形で撮り直す。比較動画はこの新しい素材から作る。
**エイイチは寝ている。人に聞かないと進めない所に当たったら、その作業は止めて報告に書き、ほかの作業は続ける。**

## 始める前に確かめること（違えば回さずに報告して終わる）

1. Unity エディタが起動していない・ほかの night_runner／rec_runner が動いていない
2. /Volumes/KIOXIA がマウントされている。unity_ledger_v1.lock がない。台帳を archive/unity_ledger_v1_prerec_20261031/ に控え、行数と md5 を報告に書く
3. ServiceSim の保留が 0。依頼42の check-in のあとに作り直した実行ファイル（画面なし・録画用）で、build_info の pending_changes が空
4. プロジェクトの params の unity_rules が entrance_keeper=true・staff_posts=true・standby_return=true・pass_one_robot=false・same_table_one_robot=false。店の形の版が 9b8dae7ebf8c

## 撮る回（6店 × 11条件 × 種1つ = 66 回）

依頼38（night/20261030_rec/README.md）と**同じ条件・同じ種・同じ順・同じ映し方**で撮る。違うのは、入口番 入・持ち場 入（今の標準）と、実行ファイル・cs だけ。
- 種：③ 7・⑦ 7・④ 1・⑤ 1・⑥ 1・② 8（昨夜と同じ。選び直さない）
- 条件の順：普通・真ん中の人数（0・1・2台）→ スタッフが少ない（0・2台）→ 混んでいる（0・2台）→ スタッフが多い（0・2台）→ 空いている（0・2台）。店の順は ③⑦④⑤⑥②
- すれ違い 入・待機場所へ戻る 入・案B 切・入口番 入・持ち場 入
- 映し方：真上・全体（外寸に0.8mの余白）・幅2560・0.5秒ごと・30コマ/秒（15倍速）・回の最初から全員が帰るまで
- 回し方：night/20261030_rec/rec_runner.py と同じやり方（必要なら night/20261031_rec/ に写して使う）。名札は `rec`、夜の名前は 20261031。記録は night/20261031_rec/
- 同時に回す本数は2本（昨夜と同じ）
- 時刻で止めず、並べた回が全部終わるまで回す。途中で止まっても同じ回を回し直さない（そのまま報告する）
- 回している間は、ほかの作業をしない。Unity を開かない
- **Mac をスリープさせない**こと（昨夜は回し終わったあと、依頼箱の見張り係が止まっていた。回し終わったら、スリープの設定を戻す前に報告と一覧を書き終える）

## 素材の一覧

night/20261031_rec/ に、昨夜と同じ形の `rec_index.csv` と `README.md` を置く。rec_index.csv の列は昨夜と同じにし、最後に「入口番がいた割合（手が空いた人がいる時間のうち・依頼42の数え方）」の列を足す。

## 変えないもの

- 店の回し方・人とロボットの動き・速さ・物理の設定・実行ファイルの中身
- 台帳の列と今までの行。昨夜のストック（20261030_rec）とその動画

## 報告

依頼箱の結果に加えて、claude_code_reports に 20261031_rec_stock_v2.txt として書く。
- 始める前の確かめ4つ・台帳の控えの md5
- cs 番号・build_id
- 66 回の結果（completed の数・失敗・止まった回・最長の実時間・夜全体の始まりと終わり）
- 動画の合計の大きさ
- 台帳の行数と、先頭の行の md5 が控えと同じか
- rec_index.csv の全文
- 昨夜（20261030_rec）の同じ店・同じ条件の回との比べ（入店待ち・席は空いているのに待った・料理の待ち・会計待ち・動けなくなった）。66組を表で。原因は調べなくてよい
```

- ねらい：比較動画に映るスタッフの動きを、実際のファミレスの待ち方に合わせる
- 変わったこと：手が空いた人は、1人がレジ内、ほかはパントリーのまわりに散って待つ
- 見込める効果：店の人が動画を見て「うちと違う」と感じる所が減る。66本の数字が前の夜とどう違うかは、依頼43の報告で確かめる


## 作業ログ ― サイトの試算と Unity のずれを見直した

### 試算と Unity を、同じ条件の72組で並べた compare_site_unity.py

エイイチの問いは「サイトの試算は Unity のシミュレーションから見て妥当か」でした。台帳には、Unity の回ごとの値と、同じ条件の試算の値が1行に入っています。直近の2つの夜（6店 × 条件 × 種10、合わせて720回・72条件）を、条件ごとに種10本の平均にして並べました。台帳は KIOXIA の `service_sim/unity/unity_ledger_v1.csv` を写したものです。

```python
# サイトの試算（SimPy）と Unity の3Dシミュレーションの数字を、同じ条件で並べる。
# 入力：KIOXIA の台帳 unity_ledger_v1.csv（Unity の値と、同じ条件の試算の値 site_* が1行に入っている）
# 対象：直近の2つの夜（20261106_busy・20261107_staff）。どちらも6店 × 条件 × 種10
import csv, sys, statistics as st
from collections import defaultdict

LEDGER = sys.argv[1] if len(sys.argv) > 1 else 'unity_ledger_v1.csv'
NIGHTS = ('20261106', '20261107')
STORE = {'fam_p0': '②定番の郊外型', 'fam_p1': '③効率重視・入口が角', 'fam_p2': '④効率重視・小さめの店',
         'fam_p3': '⑤ゆったり・入口が正面', 'fam_p4': '⑥ゆったり・入口が横', 'fam_p5': '⑦効率重視・席を詰めた店'}

rows = [r for r in csv.DictReader(open(LEDGER, encoding='utf-8'))
        if r['run_folder'].startswith(NIGHTS) and r['status'] == 'completed']
f = lambda r, k: float(r[k]) if r[k] not in ('', None) else None

# 条件ごとに種10本を平均する
g = defaultdict(list)
for r in rows:
    key = (r['run_folder'][:8], r['store'], int(r['staff']), int(r['robots']), int(r['arrivals_per_h']))
    g[key].append(r)

def mean(rs, k):
    v = [f(r, k) for r in rs if f(r, k) is not None]
    return st.mean(v) if v else None

print(f'行数 {len(rows)}・条件 {len(g)}（種ごとの平均で比べる）')
print()
print('■ 条件ごとの平均（分）。U=Unity・S=サイトの試算')
print('夜,店,スタッフ,ロボット,来店/時,種,入店待ちU,入店待ちS,料理の待ちU,料理の待ちS,会計待ちU,会計待ちS,動けなくなった/往復')
for key in sorted(g):
    rs = g[key]
    trips = sum(f(r, 'trips') or 0 for r in rs); stuck = sum(f(r, 'stuck_count') or 0 for r in rs)
    vals = [mean(rs, k) for k in ('entry_wait_mean', 'site_entry_wait_mean', 'dish_wait_mean', 'site_dish_wait_mean', 'pay_wait_mean', 'site_pay_wait_mean')]
    print(','.join([key[0], STORE[key[1]], str(key[2]), str(key[3]), str(key[4]), str(len(rs))] +
                   ['' if v is None else f'{v:.2f}' for v in vals] + [f'{stuck/trips:.3f}' if trips else '']))

# 人だけ（ロボット0台）の条件で、Unity が試算の何倍か
print()
print('■ ロボット0台の条件で、Unity ÷ 試算（入店待ちが試算で0.5分以上の条件だけ）')
ratios = defaultdict(list)
for key, rs in g.items():
    if key[3] != 0: continue
    u, s = mean(rs, 'entry_wait_mean'), mean(rs, 'site_entry_wait_mean')
    if u is not None and s and s >= 0.5: ratios[key[1]].append(u / s)
for k in sorted(ratios):
    v = ratios[k]; print(f'{STORE[k]}：{len(v)}条件・中央値 {st.median(v):.2f} 倍（{min(v):.2f}〜{max(v):.2f}）')

# 0台 → 2台で入店待ちが増えるか減るか、向きがそろうか
print()
print('■ 0台 → 2台で、入店待ちが減るか増えるかの向き（差が0.1分未満は「変わらない」）')
def sign(d): return 0 if abs(d) < 0.1 else (1 if d > 0 else -1)
same = total = 0; diff = []
for key, rs in g.items():
    if key[3] != 0: continue
    k2 = (key[0], key[1], key[2], 2, key[4])
    if k2 not in g: continue
    du = mean(g[k2], 'entry_wait_mean') - mean(rs, 'entry_wait_mean')
    ds = mean(g[k2], 'site_entry_wait_mean') - mean(rs, 'site_entry_wait_mean')
    total += 1
    if sign(du) == sign(ds): same += 1
    else: diff.append((key, round(du, 2), round(ds, 2)))
print(f'向きが同じ {same} / {total} 組')
for key, du, ds in sorted(diff):
    print(f'  違う：{key[0]} {STORE[key[1]]} スタッフ{key[2]} 来店{key[4]}/時　Unity {du:+.2f} 分・試算 {ds:+.2f} 分')

# ロボットが動けなくなった（60秒）割合を店ごとに
print()
print('■ ロボットありの回で、動けなくなった（60秒）回数 ÷ 往復の回数（店ごと）')
acc = defaultdict(lambda: [0.0, 0.0])
for r in rows:
    if int(r['robots']) == 0: continue
    acc[r['store']][0] += f(r, 'stuck_count') or 0; acc[r['store']][1] += f(r, 'trips') or 0
T = [0, 0]
for k in sorted(acc):
    s, t = acc[k]; T[0] += s; T[1] += t
    print(f'{STORE[k]}：{s:.0f} / {t:.0f} = {s/t:.1%}')
print(f'6店まとめて：{T[0]:.0f} / {T[1]:.0f} = {T[0]/T[1]:.1%}')
```

```text
$ python3 compare_site_unity.py unity_ledger_v1.csv
行数 720・条件 72（種ごとの平均で比べる）

■ 条件ごとの平均（分）。U=Unity・S=サイトの試算
夜,店,スタッフ,ロボット,来店/時,種,入店待ちU,入店待ちS,料理の待ちU,料理の待ちS,会計待ちU,会計待ちS,動けなくなった/往復
20261106,②定番の郊外型,2,0,14,10,1.54,0.43,0.72,0.38,0.82,0.82,
20261106,②定番の郊外型,2,0,24,10,11.69,9.98,1.02,0.72,1.44,2.02,
20261106,②定番の郊外型,2,1,14,10,1.38,0.34,1.05,0.73,0.76,0.63,0.019
20261106,②定番の郊外型,2,1,24,10,11.17,7.39,1.91,1.01,1.59,1.56,0.189
20261106,②定番の郊外型,2,2,14,10,1.48,0.43,0.99,0.37,0.99,0.57,0.186
20261106,②定番の郊外型,2,2,24,10,10.99,6.95,1.68,0.43,1.60,1.63,0.225
20261106,③効率重視・入口が角,4,0,30,10,1.41,0.40,0.70,0.30,2.60,2.49,
20261106,③効率重視・入口が角,4,0,54,10,19.55,13.03,0.99,0.42,5.56,15.44,
20261106,③効率重視・入口が角,4,1,30,10,1.46,0.25,2.04,1.61,3.01,2.35,0.074
20261106,③効率重視・入口が角,4,1,54,10,17.45,11.95,2.60,2.01,7.41,12.05,0.096
20261106,③効率重視・入口が角,4,2,30,10,1.44,0.23,1.50,0.74,3.11,2.20,0.094
20261106,③効率重視・入口が角,4,2,54,10,17.24,11.10,1.93,1.01,8.36,11.45,0.099
20261106,④効率重視・小さめの店,2,0,20,10,2.61,0.91,1.13,0.68,1.55,2.23,
20261106,④効率重視・小さめの店,2,0,36,10,32.86,23.93,1.28,0.92,2.19,5.34,
20261106,④効率重視・小さめの店,2,1,20,10,3.14,0.44,2.12,1.02,2.15,1.44,0.206
20261106,④効率重視・小さめの店,2,1,36,10,33.55,16.09,2.23,1.31,2.86,7.04,0.221
20261106,④効率重視・小さめの店,2,2,20,10,5.70,0.58,2.91,0.42,3.03,1.93,0.573
20261106,④効率重視・小さめの店,2,2,36,10,45.77,14.69,3.05,0.49,4.40,7.34,0.526
20261106,⑤ゆったり・入口が正面,4,0,33,10,1.45,0.45,0.82,0.44,3.58,5.60,
20261106,⑤ゆったり・入口が正面,4,0,59,10,24.90,19.38,1.09,0.56,6.99,22.88,
20261106,⑤ゆったり・入口が正面,4,1,33,10,1.63,0.38,2.30,1.86,3.76,3.89,0.050
20261106,⑤ゆったり・入口が正面,4,1,59,10,23.46,17.63,2.66,2.16,7.91,17.83,0.047
20261106,⑤ゆったり・入口が正面,4,2,33,10,1.65,0.31,1.73,0.94,4.21,3.27,0.073
20261106,⑤ゆったり・入口が正面,4,2,59,10,23.62,12.69,1.96,1.24,9.67,16.99,0.038
20261106,⑥ゆったり・入口が横,3,0,27,10,1.85,0.82,0.97,0.56,2.07,4.16,
20261106,⑥ゆったり・入口が横,3,0,49,10,29.84,21.37,1.16,0.80,3.68,16.74,
20261106,⑥ゆったり・入口が横,3,1,27,10,1.48,0.33,1.68,1.55,2.18,1.97,0.024
20261106,⑥ゆったり・入口が横,3,1,49,10,21.60,13.62,2.09,2.06,4.00,12.41,0.019
20261106,⑥ゆったり・入口が横,3,2,27,10,1.41,0.32,0.93,0.65,1.94,1.89,0.038
20261106,⑥ゆったり・入口が横,3,2,49,10,20.60,12.40,1.44,0.87,4.14,12.46,0.056
20261106,⑦効率重視・席を詰めた店,4,0,30,10,1.37,0.28,0.72,0.30,2.58,2.55,
20261106,⑦効率重視・席を詰めた店,4,0,54,10,19.92,12.33,1.02,0.44,5.55,15.33,
20261106,⑦効率重視・席を詰めた店,4,1,30,10,1.47,0.23,1.98,1.65,3.04,2.23,0.067
20261106,⑦効率重視・席を詰めた店,4,1,54,10,17.32,13.74,2.52,2.02,6.98,12.25,0.062
20261106,⑦効率重視・席を詰めた店,4,2,30,10,1.50,0.22,1.76,0.73,3.23,2.07,0.111
20261106,⑦効率重視・席を詰めた店,4,2,54,10,17.02,10.99,1.77,1.03,7.48,11.84,0.096
20261107,②定番の郊外型,1,0,19,10,25.64,24.45,2.31,2.06,2.66,2.26,
20261107,②定番の郊外型,1,1,19,10,17.88,13.55,1.40,0.78,2.08,1.81,0.105
20261107,②定番の郊外型,1,2,19,10,19.17,13.45,1.42,0.37,2.44,1.77,0.217
20261107,②定番の郊外型,3,0,19,10,2.38,1.02,0.48,0.23,0.89,0.46,
20261107,②定番の郊外型,3,1,19,10,2.62,1.24,1.41,0.92,0.92,0.62,0.048
20261107,②定番の郊外型,3,2,19,10,2.51,0.99,1.35,0.41,1.19,0.57,0.227
20261107,③効率重視・入口が角,3,0,42,10,16.48,10.64,1.29,0.88,3.54,3.17,
20261107,③効率重視・入口が角,3,1,42,10,12.52,5.62,2.32,2.14,4.14,4.01,0.049
20261107,③効率重視・入口が角,3,2,42,10,12.66,4.00,1.71,0.92,4.25,4.95,0.101
20261107,③効率重視・入口が角,5,0,42,10,3.24,1.04,0.57,0.27,9.07,5.53,
20261107,③効率重視・入口が角,5,1,42,10,4.25,1.08,2.21,1.87,9.74,6.09,0.075
20261107,③効率重視・入口が角,5,2,42,10,4.12,0.92,1.65,0.96,10.07,6.33,0.109
20261107,④効率重視・小さめの店,1,0,28,10,60.28,52.32,4.55,4.50,6.30,5.04,
20261107,④効率重視・小さめの店,1,1,28,10,50.07,33.09,1.73,1.00,4.37,2.93,0.111
20261107,④効率重視・小さめの店,1,2,28,10,70.02,34.94,3.37,0.41,8.17,2.88,0.451
20261107,④効率重視・小さめの店,3,0,28,10,3.81,2.41,0.82,0.47,1.95,1.31,
20261107,④効率重視・小さめの店,3,1,28,10,6.04,1.28,2.37,1.33,2.31,1.19,0.277
20261107,④効率重視・小さめの店,3,2,28,10,12.91,1.04,3.58,0.50,3.59,1.20,0.787
20261107,⑤ゆったり・入口が正面,3,0,46,10,22.17,19.17,1.35,1.12,4.32,4.09,
20261107,⑤ゆったり・入口が正面,3,1,46,10,17.13,11.09,2.57,2.32,4.72,5.08,0.034
20261107,⑤ゆったり・入口が正面,3,2,46,10,19.70,8.61,2.41,1.09,5.90,6.15,0.098
20261107,⑤ゆったり・入口が正面,5,0,46,10,3.83,2.34,0.73,0.41,11.60,10.00,
20261107,⑤ゆったり・入口が正面,5,1,46,10,4.89,2.58,2.56,2.05,12.58,9.95,0.062
20261107,⑤ゆったり・入口が正面,5,2,46,10,5.56,2.54,2.06,1.24,11.37,10.22,0.111
20261107,⑥ゆったり・入口が横,2,0,38,10,31.78,29.82,1.97,1.72,3.70,2.87,
20261107,⑥ゆったり・入口が横,2,1,38,10,19.63,15.29,1.73,1.85,3.16,2.52,0.006
20261107,⑥ゆったり・入口が横,2,2,38,10,20.28,14.48,1.37,0.70,3.32,2.53,0.071
20261107,⑥ゆったり・入口が横,4,0,38,10,3.69,1.64,0.81,0.51,3.70,3.47,
20261107,⑥ゆったり・入口が横,4,1,38,10,3.05,1.33,2.00,1.84,4.62,3.47,0.022
20261107,⑥ゆったり・入口が横,4,2,38,10,2.92,0.71,1.30,0.87,5.32,3.44,0.041
20261107,⑦効率重視・席を詰めた店,3,0,42,10,16.66,12.02,1.30,0.90,3.55,3.35,
20261107,⑦効率重視・席を詰めた店,3,1,42,10,13.36,6.80,2.38,2.20,4.16,3.77,0.063
20261107,⑦効率重視・席を詰めた店,3,2,42,10,11.58,4.15,1.95,0.94,4.25,4.62,0.065
20261107,⑦効率重視・席を詰めた店,5,0,42,10,3.18,1.44,0.59,0.27,8.77,6.46,
20261107,⑦効率重視・席を詰めた店,5,1,42,10,3.86,1.67,2.22,1.91,9.36,6.29,0.069
20261107,⑦効率重視・席を詰めた店,5,2,42,10,4.31,1.34,1.61,0.99,9.42,6.49,0.095

■ ロボット0台の条件で、Unity ÷ 試算（入店待ちが試算で0.5分以上の条件だけ）
②定番の郊外型：3条件・中央値 1.17 倍（1.05〜2.33）
③効率重視・入口が角：3条件・中央値 1.55 倍（1.50〜3.11）
④効率重視・小さめの店：4条件・中央値 1.48 倍（1.15〜2.86）
⑤ゆったり・入口が正面：3条件・中央値 1.28 倍（1.16〜1.64）
⑥ゆったり・入口が横：4条件・中央値 1.82 倍（1.07〜2.27）
⑦効率重視・席を詰めた店：3条件・中央値 1.62 倍（1.39〜2.22）

■ 0台 → 2台で、入店待ちが減るか増えるかの向き（差が0.1分未満は「変わらない」）
向きが同じ 14 / 24 組
  違う：20261106 ③効率重視・入口が角 スタッフ4 来店30/時　Unity +0.03 分・試算 -0.17 分
  違う：20261106 ④効率重視・小さめの店 スタッフ2 来店20/時　Unity +3.09 分・試算 -0.33 分
  違う：20261106 ④効率重視・小さめの店 スタッフ2 来店36/時　Unity +12.91 分・試算 -9.25 分
  違う：20261106 ⑤ゆったり・入口が正面 スタッフ4 来店33/時　Unity +0.20 分・試算 -0.14 分
  違う：20261106 ⑦効率重視・席を詰めた店 スタッフ4 来店30/時　Unity +0.13 分・試算 -0.06 分
  違う：20261107 ②定番の郊外型 スタッフ3 来店19/時　Unity +0.14 分・試算 -0.03 分
  違う：20261107 ③効率重視・入口が角 スタッフ5 来店42/時　Unity +0.88 分・試算 -0.12 分
  違う：20261107 ④効率重視・小さめの店 スタッフ1 来店28/時　Unity +9.75 分・試算 -17.38 分
  違う：20261107 ④効率重視・小さめの店 スタッフ3 来店28/時　Unity +9.10 分・試算 -1.37 分
  違う：20261107 ⑦効率重視・席を詰めた店 スタッフ5 来店42/時　Unity +1.13 分・試算 -0.09 分

■ ロボットありの回で、動けなくなった（60秒）回数 ÷ 往復の回数（店ごと）
②定番の郊外型：951 / 5812 = 16.4%
③効率重視・入口が角：752 / 8247 = 9.1%
④効率重視・小さめの店：2862 / 7193 = 39.8%
⑤ゆったり・入口が正面：559 / 8405 = 6.7%
⑥ゆったり・入口が横：360 / 9197 = 3.9%
⑦効率重視・席を詰めた店：670 / 8251 = 8.1%
6店まとめて：6154 / 47105 = 13.1%
```

### 人だけの店なら、Unity の入店待ちは試算の約1.2〜1.8倍

ロボット0台の条件では、店ごとの中央値で Unity の入店待ちが試算の1.17〜1.82倍でした。Unity では、スタッフが通路で人とすれ違い、お客さんが席に着くまでに歩きます。試算はその歩く時間を細かく持たないので、Unity のほうが長くなるのは自然です。人の側の差は、店の形による違いはあっても、倍率で言える大きさに収まっています。

### ロボットが動けなくなる割合は、試算の1%に対して Unity は13.1%

違いが大きいのはロボットの側です。試算はロボットが動けなくなる（立ち往生する）割合を1往復あたり0.01で置いています。Unity でロボットが60秒止まったまま動けなくなった回数を往復の回数で割ると、6店まとめて13.1%、いちばん狭い④では39.8%でした。0台から2台に増やしたときに入店待ちが減るか増えるかの向きも、24組のうち10組で試算と Unity が逆でした。逆になった組は、ほとんどが「試算は減る・Unity は増える」で、④に大きな差が集まっています。

ただし、Unity の「60秒止まった」と、試算の「立ち往生」が同じものかは、まだ決めていません。Unity では、人とすれ違うために少し待つだけの止まりも、長引けば60秒を超えます。今は試算の値を変えず、どちらに合わせるかは現場で測った数字を見てから決めることにしました。

### 現場の数字を入れる土台は、半分まで

エイイチの次の問いは「将来現場のデータが集まってきた時に、正確に素早くシミュレーションを更新するための土壌はできていますか？」でした。

今の作りでは、試算の前提はすべて `serving-robot-params.json` の1か所にあり、値ごとに「サイトで決めた（site_rule）」「Unity で測って戻す（unity_measures）」の印が付いています。Unity もこのファイルを読むので、値を1つ直せば試算と Unity の両方に届きます。ここまではできています。

足りないのは、値ごとに「この数字はどこから来たか（仮に置いた・Unity で測った・どの店で何回測った）」を持つ欄と、店で測った記録を前提の値へ移す手順です。エイイチは、測れる項目を次のように整理しました。

> 現場のデータを集める時事細かに全てを計測することは困難です。…実際に計測できそうなのは席数と席と席の幅や通路の間隔、スタッフの人数、各タスクの計測などになるかと思います。

そこで、測る項目と、それが入る前提を表にしました。観察シートの作り直しは、この表の C・D・E を店で集めるためのものです。

| 分け | 店で測るもの | 入る前提（serving-robot-params.json） | 今の値 |
|---|---|---|---|
| A 店の形 | 卓の数・席の配置・通路の幅 | 店の形のデータ（layouts）・stores の tables | 6店の図面から作った値 |
| B 回し方 | スタッフの人数・時間帯 | stores の staff・arrivals | 店ごとに3通り |
| C 作業の時間 | 会計・片付け・テーブルを拭く・席へ案内・呼び出し・料理を置く | common の pay_s・clear_s・wipe_s・guide_s・call_s・staff_place_s | 90秒・40秒・40秒・45秒・30秒・10秒 |
| D ロボット | 往復の時間・止まった回数・人が動かした回数 | common の pause_rate・stuck_rate（Unity で測る値） | 往復の30%で止まる・1%で人が動かす |
| E 答え合わせ | 入ってきた組・会計した組 | 試算の回転率と滞在時間（前提ではなく、試算の出力と比べる） | — |

店の写真や図面から A を読み取る案も話しました。奥行きを測るセンサーの付いたスマホと間取りのアプリなら、スマホで撮影するだけで部屋の寸法が取れるものがあります。ただ、店の写真にはお客さんが写ります。お客さんのいない時間に撮れば済みますが、その時間には作業を測れません。写真や動画は、シートでは扱わないことにしました（後の節）。


## 作業ログ ― 観察シートを、店の人がスマホで迷わず使える形に作り直した

### 前の観察シートは、店の人が使うには設定と説明が多すぎた

前の観察シートは、ロボットの出発・卓に着いた・止まった・戻ったを押し、止まった場所と原因を選ぶ形でした。記録する人の立場（客席から・店のスタッフ・導入や保守の担当）で選択肢が切り替わります。1画面目から説明と設定がぎっしり並んでいます。

【画像】LOG414_12_sheet_v1.png 前の観察シート（今は /sim/serving-robot-observation-v1 に残している）をスマホの幅で開いた1画面目。説明の文、記録する人の立場・店・ロボットの機種の選択肢が並ぶ

エイイチの方針はこうでした。

> 前提として、店舗のおじさん、おばさんたちが観察シートを使うことを想定してください。観察シートはスマホで使う可能性が高いです。…大事なのは詳細な画面設計よりもわかりやすさや、手順を迷わせない工夫が大事だと思います。必要ない情報は画面の中に見せない、計測作業が失敗しても、すぐに元に戻してやり直せる簡素なUI/UX。など考慮して考え直してみてください。必要ないくつかページを提示して比較させてください。

その前に、観察シートを使う人を3つに分けていました。ロボットの導入を考えている人、ロボット導入後の改善策を検討したい人、ロボットはないが店の回し方を試算したい人です。新しいシートは、この3つを入り口で選ぶところから始めます。

### 試作3案を並べて、A を元に B のカードの並びにした

3案をスマホの幅で作り、並べて見てもらいました。A は1つずつ案内する形（何を測るかを選び、スタート・ストップを押す）。B は作業のボタンを押すだけの形（始まったら押し、終わったら同じボタンをもう一度押す）。C は押してから選ぶ形（始まったら大きな丸を押し、何の作業かは終わってから選ぶ）です。

【画像】LOG414_11_sheet_protos.png 観察シートの試作3案をスマホの幅で並べたもの。左から A（何を測りますか？で作業を選ぶ）、B（作業のボタンを押すだけ）、C（始まったら丸を押し、作業は後で選ぶ）

> Aをベースに カードの並ぶ順はBの見た目かな。あと回数の数字が大きい方がいいね

そのあと、スマホで実際に押してもらいながら、次の直しを重ねました。

- ストップウォッチとカードを、画面の縦の真ん中に置いた（上に寄っていた）
- 文字を大きくし、カードを細い線から影のある形にした
- 「記録を見る」を、カードのすぐ下のアイコン付きの丸いボタンにした（画面の一番下では遠かった）
- すべての画面に「最初に戻る」を置いた（「目的やお店を変える」より伝わる言葉にした）
- 入り口の目的のカードの上下の余白を44pxに増やした（押しやすくするため）
- お店のことに、時間帯（開いた時刻から自動で選ぶ）と今日のようす（いくつでも選ぶ＋自由に書く欄）を足した
- 「全部消す」は、1回押すと5秒から数字が減り、もう一度押すと消える。押さなければ元に戻る。消した直後も取り消せる

【画像】LOG414_03_sheet_entry.png 新しい観察シートの入り口。何のために測りますか？の下に、記録はこのスマホの中にだけ残ることを書き、目的のカードを3枚並べた

【画像】LOG414_04_sheet_store.png お店のことの画面。お店の名前・ホールのスタッフの人数・卓の数・時間帯・動いているロボット・今日のようす。わからなければそのまま進める

【画像】LOG414_05_sheet_menu.png 何を測りますか？の画面。会計・片付け・テーブルを拭く・席へ案内・呼び出し・料理を置くの6枚のカードに回数を大きく出し、ロボットを入れている店ではロボットの往復とお客さんの出入りのカードが続く

【画像】LOG414_06_sheet_run.png 測っている画面。作業の名前・経過の時間・いつストップを押すか（レジを離れたら）を縦の真ん中に出し、赤いストップのボタンと「やめる（記録しない）」を置いた

【画像】LOG414_07_sheet_done.png 測り終わった画面。記録したことと、目安の回数まであと何回かを出し、もう一度測る・ほかの作業を測る・まちがえたので消すを並べた

- ねらい：店の人が、説明を読まずに1回目を測り終えられるようにする
- 変わったこと：1画面目に並んでいた説明と設定を、目的・お店のこと・作業を選ぶ・測るの4つの画面に分け、1画面に1つのことだけを出した
- 見込める効果：測り始めるまでの迷いが減る。実際に店で使えたかは、店で使ってもらったあとの記録の数で確かめる（今は見込み）

### 食べている時間は測らず、入ってきた組と会計した組から回転率を出す

最初の試作には、卓ごとの食べている時間を測る項目がありました。エイイチの判断で外しました。

> 観測シートでどこまでの情報を入力するかだね。食べる時間は人たちによって全然違うし、計測もかなり大変（待ち時間がかなり必要）だからそのコストを払えないんじゃないかな？だから入店の客数とレジで大体わかるように設計した方がいいね。お店によってその辺りから回転率とか出そうだし

代わりに「お客さんの出入り」の画面を足しました。入ってきたら・会計を終えたら押すだけです。数えはじめにお客さんがいる卓の数を入れておくと、回転率（1時間に1卓が何組入れ替わるか）と、滞在時間の目安が出ます。滞在時間の目安は、店にいる組の数の平均を、帰っていくペースで割って出します（待ち行列で使う [リトルの法則](https://ja.wikipedia.org/wiki/%E3%83%AA%E3%83%88%E3%83%AB%E3%81%AE%E6%B3%95%E5%89%87)。店に平均20組いて、1時間に30組帰るなら、1組は平均40分いる）。

【画像】LOG414_08_sheet_arrive.png お客さんの出入りの画面。数えはじめにお客さんがいる卓の数を入れ、入ってきた・会計したの数を大きく出す。押した直後は下に「取り消す」の帯が出る

### 写真と動画はシートで扱わず、送る記録は人が読める文にした

店の写真や動画を添付できるようにする案も考えました。エイイチの判断です。

> 店舗の人にお客さんを映らないようにと伝えるのは難しいかな。人のいない店舗だと計測できないし。写真や動画はやはりやめておくか。添付できるようにだけしておいて責任はスタッフや店舗、業者に考えてもらった方が安全かな。

シートは写真を受け取らず、送る画面に「写真や図面があれば、LINE やメールで一緒に送れます。写っている人の扱いは、お店の決まりに合わせてください。」と書くだけにしました。

送り方も変えました。最初は記録を文字ごとコピーする形で、エイイチから「記録を送る だけど、コピペするってこと？わかりにくいよ」と言われ、「LINEで送る」「メールで送る」の2つのボタンにしました。送る文に入れていた取り込み用のデータ（JSON）も「メールの内容これでもいいの？コードみたいのついてるけど？」と言われて外し、人が読める行だけにしました。下は、試しの記録（店で測ったものではありません）で作った送る文です。

```text
観察シートの記録
日付：2026/10/31
目的：ロボットを入れていて、改善したい
お店：〇〇店　スタッフ4人　ロボット2台
卓の数：約35卓
時間帯：昼（ランチ）　混んでいる
会計：だいたい57秒（2回）
片付け：だいたい1分35秒（1回）
ロボットの往復：だいたい2分28秒（1回）
出入り：入ってきた3組・会計した2組（数えはじめに20卓にお客さん）
1時間あたり：来店 約7組・会計 約5組
回転率：0.1回転／時
滞在時間の目安：約263分

--- 1回ずつの記録 ---
12:05:10　会計　52秒
12:09:40　会計　61秒
12:12:02　片付け　95秒
12:15:30　ロボットの往復　148秒（止まった1回・人が動かした0回）

--- 入ってきた時刻 ---
12:00:00、12:10:00、12:20:00

--- 会計した時刻 ---
12:05:00、12:25:00
```

試しの記録は25分で入ってきた3組・会計した2組なので、回転率と滞在時間の目安は意味のある値になっていません。数える時間が短いと、こうなります。

### 記録は店の中の人へ送る。シートの作り手には届かない

送る画面に「送った記録の扱い」を、開くと読める形で置きました。書く中身を相談したとき、エイイチはシートの使われ方の前提をこう話しました。

> これって大前提として、観察シートは各店舗で使う前提で、オレに報告はしない前提なんだけど、あるとしたら店舗のデータをまとめた担当者や企業担当がオレに直接連絡してくるってパターンと思っているけど違う？

この前提に合わせて、メールは宛先を空にして開くようにしました（前は Pixels to Motors のアドレスが入っていました）。送る画面には「店長・お店の管理の方・ロボットの担当の方に送ります。」と書いています。データの扱いで書くのは、シートが記録をどこにも送らないこと、送る相手は自分で選ぶこと、送った記録は受け取った相手の決まりで扱われること、このスマホの記録の消し方、の4つだけにしました。入り口の下には「このシートは Pixels to Motors が作りました」とだけ出しています。

【画像】LOG414_09_sheet_list.png 測った記録の画面。まとめ・記録を送る（送った記録の扱いを開いた状態）・LINEで送る・メールで送る・1回ずつの記録・はじめから記録する・最初に戻る

### 前の記録が残っているときは、同じ目的のカードから続きを測る

入り口に戻って測り直すと、前の記録が消えない作りでした。リセットのボタンを足す案に対して、エイイチは「ボタンを増やすと迷いが増えるよね」と言い、こう決めました。

> 前の記録が残っているときは、設定をそのまま継続で計測し始める必要があると思うから、例えばロボットを入れていて改善したいをまた押したら、直接継続ページに遷移した方がいいんじゃない？

記録が残っているときだけ、入り口に「前の記録が残っています」と「記録を消して新しく始める」を出し、前と同じ目的のカードに「続きから」の札を付けました。そのカードを押すと、お店のことを飛ばして作業を選ぶ画面へ進みます。

【画像】LOG414_10_sheet_resume.png 記録が残っているときの入り口。前の記録の中身と「記録を消して新しく始める」を上に出し、前と同じ目的のカードに黒い枠と「続きから」の札を付けた

サイトのシリーズのページに組み込んだ表示で、入り口を飛ばして作業を選ぶ画面から始まっていたことがありました。前に目的を選んだことがあれば作業を選ぶ画面から始める、という最初の作りが残っていたためです。「続きから」のカードができたので、いつも入り口から始めるように直しました。

### 前のシートは noindex で残し、サイトを新しいシートに切り替えた

> 前のもサイトに表示させておいて残すという意味です（seoで表示されないようにメタデータを設定）

新しいシートを今までの住所（`/sim/serving-robot-observation`）に置き、前のシートは `/sim/serving-robot-observation-v1` に移しました。前の版には `<meta name="robots" content="noindex">` を入れ、検索結果に出ないようにしています。シリーズのページ（/series8）では、「観察シートを開く」のボタンの下に前の版へのリンクを置き、説明の文も新しいシートに合わせて書き直しました。試作の A・B・C は、比べた記録として残しています（どれも noindex）。

### 観察シートの作り直しのコミットの一覧

試作から切り替えまでの、p2m-site のコミットです（新しい順）。

```text
99cf0c6 10-31 13:23 観察シート：送った記録の扱いから「Pixels to Motors には届きません」を消す
d449e5a 10-31 13:22 観察シート：記録は店の中の人（店長・管理の方・ロボットの担当の方）へ送る前提にする。メールは宛先を空で開き、Pixels to Motors には届かないと書く
ad1d089 10-31 13:16 観察シート：入り口に「記録はこのスマホの中にだけ残る」の1行と作った人を足し、送る画面に開くと読める「送った記録の扱い」を置く
a255a46 10-31 13:08 観察シート：前に目的を選んだことがあっても、いつも入り口の画面から始める（記録が残っていれば「続きから」のカードで続ける）
33b9f29 10-31 12:59 観察シートを作り直した版に切り替える。前の版は /sim/serving-robot-observation-v1.html に残し（検索に出ないよう noindex）、/series8 の観察シートの説明の下にリンクを置く。一覧の「← もどる」は前にいた画面に戻す
f7ec80e 10-31 12:44 観察シート：前の記録が残っているとき、前と同じ目的のカードに「続きから」を付け、押すとお店のことを飛ばして作業を選ぶ画面へ進む
8b739ca 10-31 12:38 観察シート：入り口に、前の記録が残っているときだけ「記録を消して新しく始める」を出す（続けるときは目的を選ぶだけ。消すのは5秒の数字つきで2回押し、取り消せる）
73cb414 10-31 12:37 観察シート：食べている時間は測らず、お客さんの出入り（入ってきた・会計した）を数えて、回転率と滞在時間の目安を出す。卓の数と、数えはじめにいる卓の数を足した
d63d30e 10-31 12:32 観察シート：送る文から取り込み用のデータ（JSON）を外し、1回ずつの記録を人が読める行（時刻・作業・秒）にした。日付も足した
ce19031 10-31 12:30 観察シート：写真や図面はシートで受け取らず、LINE・メールで一緒に送れることだけを書いた（写っている人の扱いはお店の決まりに合わせる）
095571c 10-31 12:28 観察シート：「LINEで送る」を LINE の緑と吹き出しのアイコン、「メールで送る」に封筒のアイコンを付けた
f38f523 10-31 12:26 観察シート：入り口の目的のカードの上下の余白をさらに増やした（30px → 44px）
4d54c92 10-31 12:24 観察シート：「全部消す」を1回押したあと、5秒から数字が減っていき、押さなければ元に戻ることを見せる
a6ea80c 10-31 12:20 観察シート：「記録を見る」をアイコン付きの丸いボタンにした（何を測りますか・記録した直後・来店の数）
d206efd 10-31 12:17 観察シート：入り口とお店のこと以外のすべての画面に「最初に戻る」を置いた（「目的やお店を変える」をこの言葉に。記録は消えない）
d6db848 10-31 12:15 観察シート：画面を移るとき、前の画面のお知らせ（取り消すの帯）を消す
977f66f 10-31 12:15 観察シート：「記録を送る」を「LINEで送る」「メールで送る」の2つのボタンにした（送る文が入った状態で開く。メールは宛先・件名つき）。送るデータを短くした
127c60d 10-31 12:09 観察シート：来店の数の画面に「ほかの作業を測る」と「記録を見る」を足した（作業を選ぶ画面へ戻れるように）
4af6500 10-31 12:08 観察シート：お店のことに時間帯（1つ選ぶ・開いた時刻から自動で選ぶ）と今日のようす（いくつでも選ぶ＋自由に書く欄）を足した。送る記録にも入る
ed8b6df 10-31 12:06 観察シート：入り口の目的のカードの上下の余白を増やした（押しやすくするため）
6be7ebb 10-31 12:04 観察シート：「記録を見る」を画面の一番下から、カードとボタンのすぐ下に上げた（試作Aと全体のページ）
6376d88 10-31 12:02 観察シートの作り直し（試作Aをもとに全体）：入り口（目的）→ お店のこと → 何を測りますか → 測る → 記録した直後 → 来店の数 → 記録（まとめ・送る・はじめから）。検索には出さない
7275812 10-31 11:57 観察シートの試作A：「記録を見る」を画面の下に移し、記録した直後の画面にも足した
0b37a74 10-31 11:56 観察シートの試作A：カードを細い線から影のある形にした（作業のカード・ストップウォッチ・記録の一覧）
5eb72c3 10-31 11:53 観察シートの試作A：記録の画面に「はじめから記録する」を足した（2回押すと全部消える。消した直後は取り消せる）
7be2cf6 10-31 11:50 観察シートの試作A：上の「観察シート」「記録を見る」の文字を大きくし、作業を選ぶカードを画面の縦の真ん中に置いた
e0e66ea 10-31 11:47 観察シートの試作A：測る画面と記録した直後の画面を、スマホの画面の縦の真ん中に置いた
74d95e5 10-31 11:41 観察シートの試作A：作業を選ぶ画面を2列のカードにし、回数の数字を大きくした
8be6d8d 10-31 11:29 観察シートの作り直しの試作3案（店の方がスマホで使う前提）：A 1つずつ案内する・B 作業のボタンを押すだけ・C 押してから選ぶ。検索には出さない
286be6f 10-31 11:20 観察シートの試作：計測中の作業のボタンを黒くして経過時間を出し、計測の行に黒い枠と点滅する「計測中」の札を付けた
6698e2b 10-31 11:18 観察シートの試作：最初に目的を選ぶ入り口（導入を考えている・導入後に改善したい・ロボットなしで店の回し方を試算したい）。ロボットのない2つではロボットの記録を隠す
40d3a64 10-31 11:14 配膳ロボット観察シートの試作：作業の時間を測るストップウォッチ（案内・会計・片付け・拭く・料理を置く・呼び出し・卓ごとの食べている時間）。公開中のシートとは別の住所・別の保存先、検索には出さない
```

### 観察シートの全コード serving-robot-observation.html

```html
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<!-- 2026-10-31 観察シートを作り直した版（店の方がスマホで使う前提）。前の版は serving-robot-observation-v1.html に残す。記録はこの端末の中だけに残る（保存のキーは試作 obs-sheet-v2 と同じ） -->
<title>観察シート</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Noto+Sans+JP:wght@400;500;700&family=IBM+Plex+Mono:wght@500;600&display=swap" rel="stylesheet">
<style>
:root{--bg:#f7f7f7;--panel:#fff;--ink:#242424;--ink-2:#555;--ink-3:#8a8a8a;--line:#e2e2e2;--soft:#efefef;--on-ink:#fff;
--stuck:#b3372a;--stuck-bg:#fae6e3;--go:#1d6b47;--go-bg:#e3f1e9;
--f-ui:"Inter","Noto Sans JP",system-ui,-apple-system,"Hiragino Sans",sans-serif;--f-num:"IBM Plex Mono",ui-monospace,Menlo,monospace;--r:12px;color-scheme:light}
*{box-sizing:border-box}[hidden]{display:none!important}
html,body{margin:0}
body{background:var(--bg);color:var(--ink);font-family:var(--f-ui);font-size:18px;line-height:1.5;-webkit-text-size-adjust:100%}
.wrap{max-width:520px;margin:0 auto;padding:16px max(16px,env(safe-area-inset-left)) 120px}
.top{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:44px}
.top b{font-size:15px;color:var(--ink-2);font-weight:600}
h1{font-size:24px;line-height:1.35;margin:8px 0 6px}
.say{font-size:17px;color:var(--ink-2);margin:0 0 16px}
.num{font-family:var(--f-num);font-variant-numeric:tabular-nums}
button{font-family:var(--f-ui);cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
button:focus-visible{outline:3px solid var(--ink);outline-offset:2px}
.big{display:flex;align-items:center;justify-content:center;width:100%;min-height:72px;border-radius:var(--r);border:2px solid var(--ink);background:var(--ink);color:var(--on-ink);font-size:22px;font-weight:700}
.big.sub{background:var(--panel);color:var(--ink)}
.big:active,.tile:active,.pick:active{transform:scale(.98)}
.link{background:none;border:0;padding:12px 4px;color:var(--ink-2);font-size:16px;text-decoration:underline;min-height:44px}
.toast{position:fixed;left:0;right:0;bottom:0;padding:12px max(16px,env(safe-area-inset-left)) calc(12px + env(safe-area-inset-bottom));background:var(--ink);color:var(--on-ink);display:flex;align-items:center;gap:12px;justify-content:space-between;font-size:17px;z-index:20}
.toast button{background:var(--on-ink);color:var(--ink);border:0;border-radius:10px;font-size:17px;font-weight:700;padding:12px 18px;min-height:52px;white-space:nowrap}
.done{font-size:15px;color:var(--ink-3)}
.sheet-list{list-style:none;margin:8px 0 0;padding:0;background:var(--panel);border-radius:var(--r);border:1px solid var(--line)}
.sheet-list li{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 16px;border-top:1px solid var(--line);font-size:17px}
.sheet-list li:first-child{border-top:0}
.sheet-list .x{background:var(--soft);border:0;border-radius:10px;font-size:15px;padding:10px 14px;min-height:44px;color:var(--ink)}
@media (prefers-reduced-motion:reduce){.big:active,.tile:active,.pick:active{transform:none}}
</style>
<style>
:root{--shadow:0 1px 2px rgba(0,0,0,.06),0 4px 14px rgba(0,0,0,.08)}
.wrap{padding-bottom:24px}
.top b{font-size:20px;color:var(--ink);font-weight:700}
.top .link{font-size:18px}
.screen{display:flex;flex-direction:column;min-height:calc(100vh - 40px);min-height:calc(100svh - 40px)}
.mid{flex:1;display:flex;flex-direction:column;justify-content:center;padding-bottom:6vh}
.bottom-link{text-align:center;padding-top:20px}
.maker{margin:28px 0 0;text-align:center;font-size:14px;color:var(--ink-3)}
.maker a{color:var(--ink-3)}
.about{margin:0 0 14px;font-size:16px;color:var(--ink-2)}
.about summary{cursor:pointer;font-weight:700;color:var(--ink);padding:8px 0}
.about ul{margin:4px 0 0;padding-left:1.2em}
.about li{margin:4px 0}
.bottom-link .link{font-size:18px}
.listbtn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:56px;padding:0 26px;border:0;border-radius:99px;background:var(--panel);box-shadow:var(--shadow);color:var(--ink);font-size:19px;font-weight:700}
.listbtn:active{transform:scale(.98)}
.bottom-link .home{display:block;margin:14px auto 0;font-size:16px;color:var(--ink-3)}
.card{background:var(--panel);border-radius:var(--r);box-shadow:var(--shadow)}
.purpose{display:grid;gap:14px}
.purpose button{display:block;width:100%;text-align:left;border:0;padding:44px 22px;color:var(--ink);font-size:21px;font-weight:700;line-height:1.4}
.purpose button small{display:block;font-size:16px;font-weight:400;color:var(--ink-2);margin-top:4px}
.picks{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.pick{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;min-height:124px;padding:10px;border:0;color:var(--ink);font-size:20px;font-weight:700}
.pick .c{display:flex;align-items:baseline;gap:2px}
.pick .c .num{font-size:36px;font-weight:600;line-height:1.1}
.pick .c small{font-size:15px;font-weight:500;color:var(--ink-3)}
.pick.wide{grid-column:1/-1;min-height:96px}
.cnt{display:flex;align-items:baseline;gap:2px}
.cnt .num{font-size:28px;font-weight:600;line-height:1}
.cnt small{font-size:14px;color:var(--ink-3);font-weight:500}
.timebox{padding:28px 16px;text-align:center;margin:8px 0 16px}
.timebox .num{font-size:64px;font-weight:600;line-height:1}
.timebox.run{outline:2px solid var(--ink)}
.timebox .when{font-size:17px;color:var(--ink-2);margin-top:10px}
.big.stop{background:var(--stuck);border-color:var(--stuck)}
.big svg{flex:none;margin-right:10px}
.big.line{background:#06C755;border-color:#06C755;color:#fff}
.big.armed{background:var(--stuck);border-color:var(--stuck);color:#fff}
.result{font-size:40px;font-weight:700;text-align:center;margin:0 0 4px}
.stack{display:grid;gap:10px}
.prog{font-size:16px;color:var(--ink-2);text-align:center;margin-top:14px}
.tally{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:0 0 14px}
.tally button{min-height:84px;border:0;border-radius:var(--r);background:var(--panel);box-shadow:var(--shadow);font-size:18px;font-weight:700;color:var(--ink);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}
.tally .num{font-size:28px}
.field{margin-bottom:18px}
.field label{display:block;font-size:17px;color:var(--ink-2);margin-bottom:6px}
.field input{width:100%;min-height:60px;font:20px var(--f-ui);padding:10px 14px;border:0;border-radius:var(--r);background:var(--panel);box-shadow:var(--shadow);color:var(--ink)}
.step{display:flex;align-items:center;background:var(--panel);border-radius:var(--r);box-shadow:var(--shadow);overflow:hidden}
.step button{width:72px;min-height:64px;border:0;background:transparent;font-size:30px;color:var(--ink)}
.step output{flex:1;text-align:center;font-size:26px;font-weight:700}
.counter{padding:28px 16px;text-align:center;margin:8px 0 16px}
.counter .num{font-size:80px;font-weight:600;line-height:1}
.sum{padding:4px 16px;margin:6px 0 18px}
.sum .row{display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:14px 0;border-top:1px solid var(--soft)}
.sum .row:first-child{border-top:0}
.sum .row b{font-size:18px;white-space:nowrap}
.sum .row .v{text-align:right}
.sum .row .v .num{font-size:22px;font-weight:600}
.sum .row .v small{display:block;font-size:14px;color:var(--ink-3)}
h2{font-size:20px;margin:22px 0 4px}
.sheet-list{border:0;box-shadow:var(--shadow)}
.chips{display:flex;flex-wrap:wrap;gap:8px}
.chips button{min-height:52px;padding:0 18px;border-radius:99px;border:0;background:var(--panel);box-shadow:var(--shadow);font-size:18px;font-weight:600;color:var(--ink)}
.chips button[aria-pressed="true"]{background:var(--ink);color:var(--on-ink)}
.field textarea{width:100%;min-height:84px;font:18px var(--f-ui);padding:12px 14px;border:0;border-radius:var(--r);background:var(--panel);box-shadow:var(--shadow);color:var(--ink);margin-top:10px;resize:vertical}
.tally.io .card{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:14px 8px;min-height:120px}
.tally.io .num{font-size:52px;font-weight:600;line-height:1.1}
.tally.io small{font-size:15px;color:var(--ink-2)}
.purpose button.cont{outline:2px solid var(--ink)}
.purpose .badge{display:inline-block;margin-left:8px;padding:2px 10px;border-radius:99px;background:var(--ink);color:var(--on-ink);font-size:14px;font-weight:700;vertical-align:3px}
.resume{padding:20px 18px;margin-bottom:28px}
.resume b{font-size:19px}
.purpose-tag{font-size:15px;color:var(--ink-2)}
</style></head><body><div class="wrap">

<!-- 1 入り口 -->
<div id="sEntry" class="screen" hidden>
  <div class="top"><b>観察シート</b></div>
  <div class="mid">
    <div class="card resume" id="resume" hidden>
      <b>前の記録が残っています</b>
      <p class="say" id="resumeSum" style="margin:4px 0 4px"></p>
      <p class="say" style="margin:0 0 14px">続けるときは、下の「続きから」のカードを押してください。</p>
      <button class="big sub" id="newBtn">記録を消して新しく始める</button>
      <p class="say" id="newNote" style="text-align:center;margin-top:8px" hidden></p>
    </div>
    <h1>何のために測りますか？</h1>
    <p class="say">お店の人の作業の時間を、スマホで測ります。<br>記録はこのスマホの中にだけ残ります。送るまで、どこにも届きません。</p>
    <div class="purpose">
      <button class="card" data-purpose="pre">ロボットの導入を考えている<small>入れたらどう変わるかを試算する材料にします</small></button>
      <button class="card" data-purpose="post">ロボットを入れていて、改善したい<small>ロボットの往復と止まった回数も測ります</small></button>
      <button class="card" data-purpose="ops">ロボットはないが、店の回し方を試算したい<small>人数や配置を変えたらどうなるかを試算する材料にします</small></button>
    </div>
  </div>
  <p class="maker">このシートは <a href="https://pixelstomotors.com/" target="_blank" rel="noopener">Pixels to Motors</a> が作りました</p>
</div>

<!-- 2 お店のこと -->
<div id="sStore" class="screen" hidden>
  <div class="top"><button class="link" id="storeBack">← もどる</button></div>
  <div class="mid">
    <h1>お店のことを教えてください</h1>
    <p class="say">わからなければ、そのまま進めます。</p>
    <div class="field"><label for="fName">お店の名前</label><input id="fName" type="text" autocomplete="off" placeholder="例 〇〇店"></div>
    <div class="field"><label>今いるホールのスタッフ</label><div class="step"><button data-st="staff" data-d="-1" aria-label="1人減らす">−</button><output id="oStaff"></output><button data-st="staff" data-d="1" aria-label="1人増やす">＋</button></div></div>
    <div class="field"><label for="fTables">卓の数（だいたい）</label><input id="fTables" type="number" inputmode="numeric" min="1" placeholder="例 35"></div>
    <div class="field"><label>時間帯</label><div class="chips" id="cTime"></div></div>
    <div class="field" id="robotField"><label>動いているロボット</label><div class="step"><button data-st="robots" data-d="-1" aria-label="1台減らす">−</button><output id="oRobots"></output><button data-st="robots" data-d="1" aria-label="1台増やす">＋</button></div></div>
    <div class="field"><label>今日のようす（いくつでも）</label><div class="chips" id="cTags"></div>
      <textarea id="fNote" placeholder="ほかに気づいたこと（なくてもよい）"></textarea></div>
    <button class="big" id="storeGo">測りはじめる</button>
  </div>
</div>

<!-- 3 何を測りますか -->
<div id="sMenu" class="screen" hidden>
  <div class="top"><b>観察シート</b></div>
  <div class="mid">
    <h1>何を測りますか？</h1>
    <div class="picks" id="picks"></div>
    <div class="bottom-link"><button class="listbtn" data-go="sList"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="4" width="14" height="17" rx="2"/><path d="M9 4V3h6v1"/><path d="M9 10h6M9 14h6M9 18h4"/></svg>記録を見る</button><button class="link home" data-go="sEntry">最初に戻る</button></div>
  </div>
</div>

<!-- 4 測る -->
<div id="sRun" class="screen" hidden>
  <div class="top"><button class="link" id="runBack">← もどる</button><span class="cnt" id="runCnt"></span></div>
  <div class="mid">
    <h1 id="runT" style="text-align:center"></h1>
    <div class="timebox card" id="tb"><div class="num" id="tnum">0:00</div><div class="when" id="twhen"></div></div>
    <div class="tally" id="tally" hidden>
      <button id="tStop">止まった<span class="num" id="nStop">0</span></button>
      <button id="tHelp">人が動かした<span class="num" id="nHelp">0</span></button>
    </div>
    <button class="big" id="startBtn">スタート</button>
    <button class="big stop" id="stopBtn" hidden>ストップ</button>
    <div style="text-align:center"><button class="link" id="cancelBtn" hidden>やめる（記録しない）</button></div>
    <div class="bottom-link"><button class="link home" data-go="sEntry">最初に戻る</button></div>
  </div>
</div>

<!-- 5 記録した直後 -->
<div id="sDone" class="screen" hidden>
  <div class="top"><b id="doneT"></b></div>
  <div class="mid">
    <div class="result num" id="doneR"></div>
    <p class="say" style="text-align:center">記録しました</p>
    <div class="stack">
      <button class="big" id="againBtn">もう一度測る</button>
      <button class="big sub" data-go="sMenu">ほかの作業を測る</button>
    </div>
    <div style="text-align:center"><button class="link" id="undoBtn">まちがえたので消す</button></div>
    <p class="prog" id="doneP"></p>
    <div class="bottom-link"><button class="listbtn" data-go="sList"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="4" width="14" height="17" rx="2"/><path d="M9 4V3h6v1"/><path d="M9 10h6M9 14h6M9 18h4"/></svg>記録を見る</button><button class="link home" data-go="sEntry">最初に戻る</button></div>
  </div>
</div>

<!-- 6 お客さんの出入りを数える -->
<div id="sArrive" class="screen" hidden>
  <div class="top"><button class="link" data-go="sMenu">← もどる</button></div>
  <div class="mid">
    <h1 style="text-align:center">お客さんの出入り</h1>
    <div class="field" id="occField"><label for="fOcc">数えはじめたときに、お客さんがいる卓の数</label><input id="fOcc" type="number" inputmode="numeric" min="0" placeholder="例 20"></div>
    <div class="tally io">
      <div class="card"><small>入ってきた</small><span class="num" id="aN">0</span><small>組</small></div>
      <div class="card"><small>会計した</small><span class="num" id="lN">0</span><small>組</small></div>
    </div>
    <div class="stack">
      <button class="big" id="arriveBtn">入ってきた（1組）</button>
      <button class="big" id="leaveBtn">会計した（1組）</button>
    </div>
    <p class="say" style="text-align:center;margin-top:12px">入口から入ってきたら・レジで会計を終えたら押す</p>
    <button class="big sub" data-go="sMenu" style="margin-top:4px">ほかの作業を測る</button>
    <div class="bottom-link"><button class="listbtn" data-go="sList"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="4" width="14" height="17" rx="2"/><path d="M9 4V3h6v1"/><path d="M9 10h6M9 14h6M9 18h4"/></svg>記録を見る</button><button class="link home" data-go="sEntry">最初に戻る</button></div>
  </div>
</div>

<!-- 7 記録 -->
<div id="sList" hidden>
  <div class="top"><button class="link" id="listBack">← もどる</button><span class="purpose-tag" id="pTag"></span></div>
  <h1>測った記録</h1>
  <div class="sum card" id="sum"></div>
  <h2>記録を送る</h2>
  <p class="say" style="margin:0 0 10px">店長・お店の管理の方・ロボットの担当の方に送ります。どちらかを押すと送る文が入った状態で開くので、送る相手を選んでください。</p>
  <details class="about"><summary>送った記録の扱い</summary>
    <ul>
      <li>このシートは、記録をどこにも送りません。送るのは、下のボタンを押したときだけです。</li>
      <li>LINE もメールも、送る相手は自分で選びます。</li>
      <li>送った記録は、受け取った相手の決まりで扱われます。</li>
      <li>このスマホの記録は、「はじめから記録する」で消せます。</li>
    </ul>
  </details>
  <div class="stack">
    <button class="big line" id="lineBtn"><svg width="26" height="26" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 3C6.5 3 2 6.6 2 11c0 2.6 1.6 4.9 4.1 6.4-.2.9-.7 2.4-1 3.1-.1.3.2.6.5.4 1.1-.6 3.2-1.9 4.3-2.6.7.1 1.4.2 2.1.2 5.5 0 10-3.6 10-8S17.5 3 12 3z"/></svg>LINEで送る</button>
    <button class="big sub" id="mailBtn"><svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3.5 6.5 12 13l8.5-6.5"/></svg>メールで送る</button>
  </div>
  <p class="say" style="margin-top:10px">写真や図面があれば、LINE やメールで一緒に送れます。写っている人の扱いは、お店の決まりに合わせてください。</p>
  <h2>1回ずつの記録</h2>
  <ul class="sheet-list" id="list"></ul>
  <button class="big sub" id="resetBtn" style="margin-top:24px">はじめから記録する</button>
  <p class="say" id="resetNote" style="text-align:center;margin-top:8px" hidden>もう一度押すと、記録を全部消します</p>
  <div class="bottom-link"><button class="link home" data-go="sEntry">最初に戻る</button></div>
</div>

</div><div class="toast" id="toast" hidden role="status"><span id="toastMsg"></span><button type="button" id="toastUndo">取り消す</button></div>
<script>
var LS='p2m-obs-sheet-v2';
var TASKS=[
 {k:'pay',t:'会計',s:'お客さんがレジに立ったら',e:'レジを離れたら',sim:'90秒'},
 {k:'clear',t:'片付け',s:'お皿をまとめ始めたら',e:'まとめ終わったら',sim:'40秒'},
 {k:'wipe',t:'テーブルを拭く',s:'拭き始めたら',e:'セットし終わったら',sim:'40秒'},
 {k:'guide',t:'席へ案内',s:'お客さんに声をかけたら',e:'席に着いたら',sim:'45秒（歩く時間は別）'},
 {k:'call',t:'呼び出し',s:'テーブルに着いたら',e:'テーブルを離れたら',sim:'30秒'},
 {k:'place',t:'料理を置く',s:'テーブルに着いたら',e:'テーブルを離れたら',sim:'10秒'}
];
var ROBOT={k:'robot',t:'ロボットの往復',s:'ロボットが受け渡し口を出たら',e:'受け渡し口に戻ったら',sim:'止まる 往復の30%・人が動かす 1%'};
var PURPOSE={pre:'ロボットの導入を考えている',post:'ロボットを入れていて、改善したい',ops:'店の回し方を試算したい'};
var GOAL=15;
var TIMES=['朝','昼（ランチ）','午後','夜（ディナー）','深夜'];
var TAGS=['いつもどおり','混んでいる','空いている','スタッフが足りない','団体のお客さんがいる','雨の日','イベント・休日'];
function nowTime(){var h=new Date().getHours();return h<5?'深夜':h<11?'朝':h<15?'昼（ランチ）':h<17?'午後':h<22?'夜（ディナー）':'深夜';}
function $(i){return document.getElementById(i);}
function load(){try{var s=JSON.parse(localStorage.getItem(LS));if(s&&s.recs)return s;}catch(e){}return {purpose:'',store:{name:'',staff:3,robots:2,time:'',tags:[],note:'',tables:'',occ0:''},recs:[],arrivals:[],leaves:[]};}
var S=load();if(!S.leaves)S.leaves=[];
function save(){try{localStorage.setItem(LS,JSON.stringify(S));}catch(e){}}
function tasks(){return S.purpose==='post'?TASKS.concat([ROBOT]):TASKS;}
function T(k){var a=TASKS.concat([ROBOT]);for(var i=0;i<a.length;i++)if(a[i].k===k)return a[i];return {t:k};}
function cnt(k){return S.recs.filter(function(r){return r.k===k;}).length;}
function jp(sec){sec=Math.max(0,Math.round(sec));var m=Math.floor(sec/60),s=sec%60;return m?(m+'分'+(s?s+'秒':'')):s+'秒';}
function clock(ms){var s=Math.max(0,Math.floor(ms/1000));return Math.floor(s/60)+':'+String(s%60).padStart(2,'0');}
function med(a){if(!a.length)return null;var b=a.slice().sort(function(x,y){return x-y;}),m=Math.floor(b.length/2);return b.length%2?b[m]:(b[m-1]+b[m])/2;}
function buzz(){try{navigator.vibrate&&navigator.vibrate(15);}catch(e){}}
var toastTimer=null;
function toast(msg,undo){var t=$('toast');$('toastMsg').textContent=msg;t.hidden=false;$('toastUndo').hidden=!undo;$('toastUndo').onclick=function(){undo&&undo();t.hidden=true;};clearTimeout(toastTimer);toastTimer=setTimeout(function(){t.hidden=true;},8000);}
setInterval(function(){document.querySelectorAll('[data-since]').forEach(function(el){el.textContent=clock(Date.now()-Number(el.dataset.since));});},250);

var SCREENS=['sEntry','sStore','sMenu','sRun','sDone','sArrive','sList'];
var listFrom='sMenu';
function show(id){if(id==='sList'){var now=SCREENS.filter(function(s){return !$(s).hidden;})[0];if(now&&now!=='sList')listFrom=now;}if(running())stopRun(false);disarm();$('toast').hidden=true;SCREENS.forEach(function(s){$(s).hidden=s!==id;});render();window.scrollTo(0,0);}

/* 描く */
function render(){
  $('oStaff').textContent=S.store.staff+'人';$('oRobots').textContent=S.store.robots+'台';$('fName').value=S.store.name||'';
  $('robotField').hidden=S.purpose!=='post';
  if(!S.store.time)S.store.time=nowTime();if(!S.store.tags)S.store.tags=[];
  $('cTime').innerHTML=TIMES.map(function(t){return '<button data-time="'+t+'" aria-pressed="'+(S.store.time===t)+'">'+t+'</button>';}).join('');
  $('cTags').innerHTML=TAGS.map(function(t){return '<button data-tag="'+t+'" aria-pressed="'+(S.store.tags.indexOf(t)>=0)+'">'+t+'</button>';}).join('');
  if(document.activeElement!==$('fNote'))$('fNote').value=S.store.note||'';
  $('picks').innerHTML=tasks().map(function(t){return '<button class="pick card'+(t.k==='robot'?' wide':'')+'" data-k="'+t.k+'">'+t.t+'<span class="c"><span class="num">'+cnt(t.k)+'</span><small>回</small></span></button>';}).join('')+
    '<button class="pick card wide" data-go="sArrive">お客さんの出入り<span class="c"><small>入ってきた</small><span class="num">'+S.arrivals.length+'</span><small>組　会計</small><span class="num">'+S.leaves.length+'</span><small>組</small></span></button>';
  $('aN').textContent=S.arrivals.length;$('lN').textContent=S.leaves.length;
  if(document.activeElement!==$('fOcc'))$('fOcc').value=S.store.occ0===''||S.store.occ0==null?'':S.store.occ0;
  if(document.activeElement!==$('fTables'))$('fTables').value=S.store.tables===''||S.store.tables==null?'':S.store.tables;
  renderSum();renderList();
  $('resetBtn').hidden=!S.recs.length&&!S.arrivals.length&&!S.leaves.length;
  $('pTag').textContent=PURPOSE[S.purpose]||'';
  var parts=TASKS.concat([ROBOT]).filter(function(t){return cnt(t.k);}).map(function(t){return t.t+' '+cnt(t.k)+'回';});
  if(S.arrivals.length||S.leaves.length)parts.push('出入り '+(S.arrivals.length+S.leaves.length)+'回');
  $('resume').hidden=!parts.length;$('resumeSum').textContent=parts.join('・');
  [].forEach.call(document.querySelectorAll('.purpose button'),function(b){var on=!!parts.length&&b.dataset.purpose===S.purpose;b.classList.toggle('cont',on);
    var bd=b.querySelector('.badge');if(on&&!bd){bd=document.createElement('span');bd.className='badge';bd.textContent='続きから';b.insertBefore(bd,b.querySelector('small'));}if(!on&&bd)bd.remove();});
}
function flow(){
  var A=S.arrivals,D=S.leaves,all=A.concat(D);if(!all.length)return null;
  var t0=Math.min.apply(null,all),t1=Math.max.apply(null,all),h=(t1-t0)/3600000;
  var o={a:A.length,d:D.length,h:h};if(h<0.25)return o;
  o.aRate=A.length/h;o.dRate=D.length/h;
  var tb=Number(S.store.tables);if(tb>0)o.turn=o.dRate/tb;
  if(S.store.occ0!==''&&S.store.occ0!=null&&o.dRate>0){
    var ev=A.map(function(t){return [t,1];}).concat(D.map(function(t){return [t,-1];})).sort(function(x,y){return x[0]-y[0];});
    var L=Number(S.store.occ0),prev=t0,area=0;ev.forEach(function(e){area+=L*(e[0]-prev);L+=e[1];prev=e[0];});
    var avgL=area/(t1-t0);o.stay=avgL/o.dRate*60;}
  return o;
}
function hasRecs(){return !!(S.recs.length||S.arrivals.length||S.leaves.length);}
function renderSum(){
  var rows=tasks().filter(function(t){return cnt(t.k);}).map(function(t){var a=S.recs.filter(function(r){return r.k===t.k;});var m=med(a.map(function(r){return r.sec;}));
    var extra='';if(t.k==='robot'&&a.length){var st=a.reduce(function(s,r){return s+(r.stops||0);},0),hp=a.filter(function(r){return r.helps;}).length;extra='<small>止まった'+st+'回・人が動かした'+a.reduce(function(s,r){return s+(r.helps||0);},0)+'回</small>';}
    return '<div class="row"><b>'+t.t+'</b><span class="v">'+(m==null?'<span class="num">—</span>':'<span class="num">'+jp(m)+'</span>')+'<small>'+a.length+'回測った'+(a.length&&a.length<GOAL?'（あと'+(GOAL-a.length)+'回）':'')+'</small>'+extra+'</span></div>';});
  var f=flow();
  if(f){
    var sub=f.aRate==null?'<small>15分以上数えると、1時間あたりと回転率が出ます</small>':'<small>1時間に 来店 約'+Math.round(f.aRate)+'組・会計 約'+Math.round(f.dRate)+'組</small>';
    rows.push('<div class="row"><b>出入り</b><span class="v"><span class="num">'+f.a+'</span>組入って <span class="num">'+f.d+'</span>組会計'+sub+'</span></div>');
    if(f.turn!=null)rows.push('<div class="row"><b>回転率</b><span class="v"><span class="num">'+(Math.round(f.turn*10)/10)+'</span>回転／時<small>1卓に1時間で入れ替わる組の数</small></span></div>');
    if(f.stay!=null)rows.push('<div class="row"><b>滞在時間</b><span class="v"><span class="num">約'+Math.round(f.stay)+'分</span><small>目安（いた組の数 ÷ 帰るペース）</small></span></div>');
  }
  if(!rows.length)rows.push('<div class="row"><span class="done">まだ記録はありません</span></div>');
  $('sum').innerHTML=rows.join('');
}
function renderList(){
  var r=S.recs.slice().reverse();
  $('list').innerHTML=r.length?r.map(function(x){return '<li><span>'+T(x.k).t+'　<span class="num">'+jp(x.sec)+'</span></span><button class="x" data-rm="'+x.id+'">消す</button></li>';}).join(''):'<li class="done">まだ記録はありません</li>';
}

/* 入り口・お店 */
document.addEventListener('click',function(e){
  var b=e.target.closest('button');if(!b)return;
  if(b.dataset.purpose){var cont=hasRecs()&&S.purpose===b.dataset.purpose;S.purpose=b.dataset.purpose;save();show(cont?'sMenu':'sStore');return;}
  if(b.dataset.time){S.store.time=b.dataset.time;save();render();return;}
  if(b.dataset.tag){var i=S.store.tags.indexOf(b.dataset.tag);if(i>=0)S.store.tags.splice(i,1);else S.store.tags.push(b.dataset.tag);save();render();return;}
  if(b.dataset.st){var k=b.dataset.st,hi=k==='robots'?6:12,lo=k==='robots'?0:1;S.store[k]=Math.min(hi,Math.max(lo,S.store[k]+Number(b.dataset.d)));save();render();return;}
  if(b.id==='listBack'){show(listFrom);return;}
  if(b.dataset.go){show(b.dataset.go);return;}
  if(b.dataset.k){openTask(b.dataset.k);return;}
  if(b.dataset.rm){var id=b.dataset.rm,keep=S.recs.filter(function(r){return r.id===id;})[0];S.recs=S.recs.filter(function(r){return r.id!==id;});save();render();toast('1つ消しました',function(){S.recs.push(keep);S.recs.sort(function(a,b){return a.at-b.at;});save();render();});return;}
});
$('fName').addEventListener('input',function(e){S.store.name=e.target.value;save();});
$('fNote').addEventListener('input',function(e){S.store.note=e.target.value;save();});
$('storeBack').onclick=function(){show('sEntry');};
$('storeGo').onclick=function(){show('sMenu');};

/* 測る */
var cur=null,startAt=null,last=null,stops=0,helps=0;
function running(){return !!startAt;}
function openTask(k){cur=k;startAt=null;stops=0;helps=0;SCREENS.forEach(function(s){$(s).hidden=s!=='sRun';});window.scrollTo(0,0);var t=T(k);
  $('runT').textContent=t.t;$('runCnt').innerHTML='<small>これまで</small><span class="num">'+cnt(k)+'</span><small>回</small>';
  $('tnum').textContent='0:00';$('tnum').removeAttribute('data-since');$('twhen').textContent=t.s+'、スタート';$('tb').classList.remove('run');
  $('tally').hidden=true;$('startBtn').hidden=false;$('stopBtn').hidden=true;$('cancelBtn').hidden=true;}
function stopRun(keep){startAt=null;}
$('startBtn').onclick=function(){startAt=Date.now();buzz();$('tnum').dataset.since=startAt;$('tb').classList.add('run');$('twhen').textContent=T(cur).e+'、ストップ';
  $('startBtn').hidden=true;$('stopBtn').hidden=false;$('cancelBtn').hidden=false;$('tally').hidden=cur!=='robot';$('nStop').textContent=0;$('nHelp').textContent=0;};
$('tStop').onclick=function(){stops++;buzz();$('nStop').textContent=stops;};
$('tHelp').onclick=function(){helps++;buzz();$('nHelp').textContent=helps;};
$('stopBtn').onclick=function(){var sec=(Date.now()-startAt)/1000;buzz();last={id:'r'+Date.now(),k:cur,sec:sec,at:startAt};if(cur==='robot'){last.stops=stops;last.helps=helps;}
  S.recs.push(last);save();startAt=null;
  SCREENS.forEach(function(s){$(s).hidden=s!=='sDone';});render();window.scrollTo(0,0);
  $('doneT').textContent=T(cur).t;$('doneR').textContent=jp(sec);var n=cnt(cur);
  $('doneP').innerHTML=n>=GOAL?'目安の'+GOAL+'回に届きました':'<span class="cnt" style="justify-content:center"><small>これまで</small><span class="num">'+n+'</span><small>回　あと'+(GOAL-n)+'回で目安の'+GOAL+'回</small></span>';};
$('cancelBtn').onclick=function(){openTask(cur);};
$('runBack').onclick=function(){startAt=null;show('sMenu');};
$('againBtn').onclick=function(){openTask(cur);};
$('undoBtn').onclick=function(){if(last){S.recs=S.recs.filter(function(r){return r.id!==last.id;});save();}openTask(cur);};

/* 来店 */
$('arriveBtn').onclick=function(){var t=Date.now();S.arrivals.push(t);save();buzz();render();toast('入ってきた 1組',function(){S.arrivals=S.arrivals.filter(function(x){return x!==t;});save();render();});};
$('leaveBtn').onclick=function(){var t=Date.now();S.leaves.push(t);save();buzz();render();toast('会計した 1組',function(){S.leaves=S.leaves.filter(function(x){return x!==t;});save();render();});};
$('fOcc').addEventListener('input',function(e){S.store.occ0=e.target.value===''?'':Number(e.target.value);save();});
$('fTables').addEventListener('input',function(e){S.store.tables=e.target.value===''?'':Number(e.target.value);save();});

/* 送る */
function summaryText(){
  var L=['観察シートの記録','目的：'+(PURPOSE[S.purpose]||''),'お店：'+(S.store.name||'（未入力）')+'　スタッフ'+S.store.staff+'人'+(S.purpose==='post'?'　ロボット'+S.store.robots+'台':''),'時間帯：'+(S.store.time||'')+(S.store.tags&&S.store.tags.length?'　'+S.store.tags.join('・'):''),(S.store.note?'メモ：'+S.store.note:'')].filter(Boolean);
  tasks().forEach(function(t){var a=S.recs.filter(function(r){return r.k===t.k;}).map(function(r){return r.sec;});if(a.length)L.push(t.t+'：だいたい'+jp(med(a))+'（'+a.length+'回）');});
  var f=flow();if(S.store.tables)L.splice(3,0,'卓の数：約'+S.store.tables+'卓');
  if(f){L.push('出入り：入ってきた'+f.a+'組・会計した'+f.d+'組'+(S.store.occ0!==''&&S.store.occ0!=null?'（数えはじめに'+S.store.occ0+'卓にお客さん）':''));
    if(f.aRate!=null)L.push('1時間あたり：来店 約'+Math.round(f.aRate)+'組・会計 約'+Math.round(f.dRate)+'組');
    if(f.turn!=null)L.push('回転率：'+(Math.round(f.turn*10)/10)+'回転／時');
    if(f.stay!=null)L.push('滞在時間の目安：約'+Math.round(f.stay)+'分');}
  function hms(t){var d=new Date(t);return [d.getHours(),d.getMinutes(),d.getSeconds()].map(function(n){return String(n).padStart(2,'0');}).join(':');}
  var first=S.recs.length?Math.min.apply(null,S.recs.map(function(r){return r.at;})):(S.arrivals[0]||Date.now());
  var d0=new Date(first);L.splice(1,0,'日付：'+d0.getFullYear()+'/'+(d0.getMonth()+1)+'/'+d0.getDate());
  if(S.recs.length){L.push('','--- 1回ずつの記録 ---');S.recs.slice().sort(function(a,b){return a.at-b.at;}).forEach(function(r){L.push(hms(r.at)+'　'+T(r.k).t+'　'+(Math.round(r.sec*10)/10)+'秒'+(r.k==='robot'?'（止まった'+(r.stops||0)+'回・人が動かした'+(r.helps||0)+'回）':''));});}
  if(S.arrivals.length){L.push('','--- 入ってきた時刻 ---',S.arrivals.map(hms).join('、'));}
  if(S.leaves.length){L.push('','--- 会計した時刻 ---',S.leaves.map(hms).join('、'));}
  return L.join('\n');
}
var MAIL_TO='';
$('lineBtn').onclick=function(){location.href='https://line.me/R/share?text='+encodeURIComponent(summaryText());};
$('mailBtn').onclick=function(){var subj='観察シートの記録'+(S.store.name?'（'+S.store.name+'）':'');
  location.href='mailto:'+MAIL_TO+'?subject='+encodeURIComponent(subj)+'&body='+encodeURIComponent(summaryText());};

/* はじめから */
var arms={};
function disarmOne(id){var a=arms[id];if(!a)return;clearInterval(a.t);delete arms[id];var b=$(id);b.classList.remove('armed');b.textContent=a.label;$(a.note).hidden=true;}
function disarm(){Object.keys(arms).forEach(disarmOne);}
function armTap(id,noteId,label,doIt){
  if(!arms[id]){var a=arms[id]={left:5,label:label,note:noteId};var b=$(id);b.classList.add('armed');
    var paint=function(){b.innerHTML='全部消す　<span class="num">'+a.left+'</span>';$(noteId).innerHTML='もう一度押すと、記録を全部消します<br>押さなければ <span class="num">'+a.left+'</span> 秒で元に戻ります';};
    paint();$(noteId).hidden=false;a.t=setInterval(function(){a.left--;if(a.left<=0){disarmOne(id);return;}paint();},1000);return;}
  disarmOne(id);doIt();}
function wipeAll(after){var keep={recs:S.recs.slice(),arrivals:S.arrivals.slice(),leaves:S.leaves.slice()};S.recs=[];S.arrivals=[];S.leaves=[];save();after();
  toast('記録を全部消しました',function(){S.recs=keep.recs;S.arrivals=keep.arrivals;S.leaves=keep.leaves;save();render();});}
$('resetBtn').onclick=function(){armTap('resetBtn','resetNote','はじめから記録する',function(){wipeAll(function(){show('sMenu');});});};
$('newBtn').onclick=function(){armTap('newBtn','newNote','記録を消して新しく始める',function(){wipeAll(function(){render();});});};

/* 最初の画面 */
render();
SCREENS.forEach(function(s){$(s).hidden=true;});$('sEntry').hidden=false;
</script></body></html>
```


## 作業ログ ― 66本の撮り直しと比較動画は、次の記録で

今夜、依頼42の結果を確かめてから、依頼43（66本の撮り直し）を依頼箱に入れます。撮り直した素材から、6店の比較動画42本を作ります。比較動画を作る道具（`make_compare_stock.py`）はできていますが、まだ回していません。試算のロボットが動けなくなる割合（0.01）を Unity の値に合わせるかは、店で測った数字が入ってから決めます。
