サービスロボット導入シミュレーション Log#27 | 店で測った数字が届けば、配膳ロボットの試算はすぐ直せる

この記事の素材(コード・ログ・データ 8ファイル・まとめてダウンロードあり)

配膳ロボットの試算(ファミレスで配膳ロボットを入れたときの待ち時間を、計算で出すシミュレーション)は、前提にしている値109個のうち79個が、まだ根拠のない仮の値です。その109個を、値ごとに「何をもとにした数字か」を書いた1つの表にまとめ、店で測った数字が届いたら、その行を書き換えるだけで回し直せる形にしました。

試算が出す「ロボットを2台入れたら入店待ちが何分縮むか」「スタッフを1人減らせるか」という答えは、会計1組90秒、テーブルを拭くのに40秒、といった前提の数字で決まります。ロボットを入れるか迷っている店の人がまず聞きたいのは、「その90秒はどこから来た数字なのか」のはずです。ところが今までは、前提の数字がプログラムの中に直接書いてあり、どれが実感でどれが仮置きなのかが、どこにも書かれていませんでした。版を変えるときは、フォルダを丸ごとコピーしていました(v2_1〜v2_9)。これでは、店の数字が届いても、どこをどう直せばいいかを探すところから始まります。

前提の値を、プログラムの外にある1つの表(assumptions.json)に移し、値ごとに根拠の種類・出典・現場で測った回数・更新日を持たせた。 試算はこの表を読んで回ります。表に移すときに数字は1つも変えておらず、600条件を回し直して、前の版と全部の数字が同じになることを確かめました。

やったことなぜ結果
試算の前提109個を、根拠つきの表にまとめた
どの数字が実感で、どれが仮置きかを分かるようにするため
仮置き79・決まり17・設定11・実感1・公開事例1。Unity で測った値と現場の値は、まだ0
試算のプログラムと表を GitHub に入れた
いつ・なぜ・どの値を変えたかを、記録で追えるようにするため
前の版(v2.9)と、600条件で全部の数字が同じ
観察シートの記録を貼ると、表に入れる値の案が出る道具を作った
店から届いた LINE・メールの文を、そのまま試算に使える形にするため
試しに測った記録7回が、表の6つの行に分かれて入った
観察シートの「席へ案内」の測る区切りを変えた
シートの時間に歩く時間が入り、試算の案内の時間と比べられなかったため
「席に着いて説明を始めたら〜テーブルを離れたら」に変えた
Unity の比較動画を、まとめて作る準備をした
今夜撮り直す素材66本から、42本の比較動画を明日すぐ作り始めるため
作る道具を新しい素材に向け、試しの1コマを作った

下の図で、根拠の種類を押すと、その種類の前提が表のどの値かが出ます。店の数と回数を動かすと、測った値が試算に入る候補になるかが分かります。

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

店の立体図(席・キッチン・配膳ロボットの通り道)導入前シミュレーション配膳ロボットを、導入前に自分の店舗でシミュレーション店舗の図面と人数から、何人で回るか・どこで止まるか・月いくらかかるかを出します

作業ログ ― 配膳ロボットの試算の前提を、1つの表にまとめた

この記事のコード・ログ・データは、1ファイルずつ下に置いています。全文はリンク先にあります。

ファイルの一覧(8本)

01_sec01.py.txt ― 前提の表を書き出したスクリプト(make_assumptions_once.py)・6,039字

02_assumptions.json ― 前提の表 assumptions.json(全文)・23,270字

03_haizen_sim.py.txt ― 前提の表を読んで回す試算 haizen_sim.py(全文)・30,782字

04_sec01.txt ― v2.9 と同じ数字が出るかの確かめ・21,999字

05_export_params.py.txt ― Unity が読む値を書き出す export_params.py(全文)・5,544字

06_table.py_assumptions.csv_assumptions.csv ― 表計算ソフトで見るための写しを作る make_table.py と assumptions.csv / assumptions.csv(全文)・7,870字

07_field_import.py.txt ― 届いた文を取り込み、値の案を出す field_import.py(全文)・10,054字

08_sec10.py.txt ― 比較動画を作る make_compare_stock.py を、新しい素材に向けた・6,455字

この記事のコード・ログ・データは、1ファイルずつ下に置いています。全文はリンク先にあります。

ファイルの一覧(8本)

01_sec01.py.txt ― 前提の表を書き出したスクリプト(make_assumptions_once.py)・6,039字

02_assumptions.json ― 前提の表 assumptions.json(全文)・23,270字

03_haizen_sim.py.txt ― 前提の表を読んで回す試算 haizen_sim.py(全文)・30,782字

04_sec01.txt ― v2.9 と同じ数字が出るかの確かめ・21,999字

05_export_params.py.txt ― Unity が読む値を書き出す export_params.py(全文)・5,544字

06_table.py_assumptions.csv_assumptions.csv ― 表計算ソフトで見るための写しを作る make_table.py と assumptions.csv / assumptions.csv(全文)・7,870字

07_field_import.py.txt ― 届いた文を取り込み、値の案を出す field_import.py(全文)・10,054字

08_sec10.py.txt ― 比較動画を作る make_compare_stock.py を、新しい素材に向けた・6,455字

試算・前提の表・観察シートは、このシリーズで何をしているか

このシリーズでは、配膳ロボットを入れたファミレスの待ち時間を、2つの方法で調べています。この LOG で何度も出てくる言葉を、先にまとめます。

言葉何か
試算
サイトの /series8 で公開している、配膳ロボットの待ち時間の計算。プログラムは haizen_sim.py。お客さんが来て、案内され、料理が届き、会計して帰るまでを、SimPy(順番待ちを計算で再現する Python の道具)で1組ずつ進める。1条件を40回回した平均を出す
Unity のシミュレーション
店を3Dで建て、人とロボットを実際に歩かせるもの。通路で詰まる・止まるといった、計算では出ない動きを見る
前提
試算が置いている数字。会計1組90秒、テーブルを拭くのに40秒、ロボットが1往復で動けなくなる確率1%など。これが変わると、試算の答えが変わる
前提の表
この LOG で作った assumptions.json。前提の値ごとに、根拠の種類・出典・現場で測った回数・更新日を持つ
観察シート
店の人がスマホで作業の時間を測るページ(pixelstomotors.com/sim/serving-robot-observation )。記録は LINE かメールで、店長やロボットの担当の方へ送る
中央値
測った値を小さい順に並べたときの、真ん中の値。たまたま長くかかった1回に引きずられにくいので、表に入れる値はこれにする

試算のプログラムと前提の表は、GitHub(プログラムを、直した記録と一緒に置いておくサービス)の、このサイトのリポジトリ(非公開)の tools/serving_sim/ に置きました。

一つ断っておくと、この LOG の「見込める効果」は、すべて見込みです。店で測った数字は、まだ1つも届いていません。

前提の値はプログラムの中に直接書かれ、版はフォルダのコピーで分けていた

試算 v2.9 の前提は、haizen_sim.py の先頭に、Python の辞書として直接書かれていました。店に共通の値が24個(COMMON)、店のタイプごとの値が5店ぶん(STORES)、それに選べるロボットの台数や、1条件を回す回数、「店が回らない」とする線(NG_MIN)などです。値の横のコメントには「すべて仮」と書いてあるだけで、どれがファミレスで働いていた実感から来た数字で、どれが仮に置いた数字かは、読んでも分かりません。

版は、iCloud の作業フォルダの中で、フォルダごとコピーして分けていました。

simulation/
  v2_1/ v2_2/ v2_3/ v2_4/ v2_5/ v2_6/ v2_7/ v2_8/ v2_9/

いつ・なぜ・どの値を変えたかは、フォルダの中の README とコードの頭の説明を読み比べないと分かりません。店で測った数字が届いたとき、まず「どの値を置き換えるのか」「前の版で何を変えたのか」を探すところから始めることになります。

  • ねらい:店で測った数字が届いたときに、どの行を書き換えればよいかがすぐ分かり、直した記録が残るようにする
  • 変わったこと:前提109個をプログラムの外の表に移し、値ごとに根拠の種類・出典・回数・更新日を持たせた。試算はこの表を読んで回る。プログラムと表は git に入れ、フォルダのコピーで版を分けるのをやめた
  • 見込める効果:数字が届いてから試算を回し直すまでが、表の行を書き換えて回すだけになる(見込み。最初の店の記録が届いたときに、かかった時間で確かめる)

前提の表の、根拠の種類7つ

表の値は、どれも次の7つの根拠のどれかを持ちます。

根拠の種類意味今の数
仮置き
まだ根拠がない仮の値。現場・公開事例・Unity の値で置き換えていく
79
実感
ファミレスで働いていたころの経験
1(会計1組90秒)
公開事例
メーカーの仕様・導入事例など、公開されている値
1(洗い場が遠いホテルの店の追加距離)
UnityUnity で測った値0
現場
店で測った値。測った回数も書く
0
決まり
試算の回し方・判定の線・運用の決まりとして決めたもの
17
設定
試算の回し方の設定。店の前提ではない
11

値ごとに持つ欄は、値・単位・何の値か・根拠の種類・出典・現場で測った回数(n)・更新日・補足です。更新日が空の行は、表に移す前からの値で、いつ置いたかの記録がないものです。

実感と決まりに分けた値は、次のとおりです。振り分けは確かめてもらい、このままにしました。

値根拠出典・決めた経緯
会計1組90秒(pay_s)実感
ファミレスの経験で、1組90秒くらい(2026-10-25)
ピークの長さ120分(peak_min)
決まり
ファミレスはランチ・ディナーで混む時間が限られるので、ピークの2時間で区切る
レジは1つ・2つから選べる(registers)
決まり
試算 v2.8 で決めた。大型店は2つを最初に見せる(2026-10-25)
ロボットは6台まで選べる(robots_all)
決まり
5台・6台も選べるまま残す。いちばん良い条件だけを見せても納得されないため(2026-10-21)
席は空いているのに10分以上待ったら「店が回らない」(ng_min)
決まり
試算 v2.2 で決めた。5分だと厳しめ(2026-10-21)
料理はロボット優先。2分待っても来なければ人が運ぶ(robot_first_wait_s)
決まり
料理はロボット最優先で運ぶ運用(2026-10-23)

ロボットの速さ(0.5m/秒)と、1往復で止まる確率・動けなくなる確率は、根拠を仮置きのままにし、補足に「Unity で測って戻す値」と書きました。Unity ではもう測っていますが、試算の値として入れるかはまだ決めていないためです(LOG405 で、動けなくなる割合は Unity の方が多いと分かったが、今は変えないと決めた)。

前提の表を書き出したスクリプト(make_assumptions_once.py)

v2.9 のコードから値を読み出し、根拠と説明を付けて assumptions.json に書き出す、1回だけ使うスクリプトです。値は手で写さず、コードの中の値をそのまま読んでいます。このあと、根拠が分からない cold_min(これを超えて届いた皿を「待たせた皿」とする3分の線)を「決まり」から「仮置き」に直し、共通の値の並びを v2.9 のコードと同じ順にそろえ、日付の記録がない値の更新日を空にしました。

01_sec01.py.txt6,039字・105行・python
# 1回だけ使う:v2.9 の haizen_sim.py の中の値を、前提の表(assumptions.json)に書き出す
import json, sys
sys.path.insert(0, "/home/claude/v29")
import haizen_sim as h
D = "2026-10-31"
def rec(v, unit, what, basis="仮置き", source="", updated=None, note=""):
    r = {"value": v, "unit": unit, "what": what, "basis": basis, "source": source, "n": None, "updated": updated or "2026-10-19"}
    if note: r["note"] = note
別のタブで開く保存

前提の表 assumptions.json(全文)

02_assumptions.json23,270字・1,303行・json
{
 "about": "サービスロボット導入シミュレーション(配膳ロボットの試算 haizen_sim.py)の前提の表。試算はこの表を読んで回す。値を直すときは、ここを直して git に残す",
 "model": "v2.9",
 "basis_kinds": {
  "仮置き": "まだ根拠がない仮の値。現場・公開事例・Unity の値で置き換えていく",
  "実感": "エイイチの現場の経験(ファミレスで働いていたころ)",
  "公開事例": "メーカーの仕様・導入事例など、公開されている値",
  "Unity": "Unity(ServiceSim)で測った値",
別のタブで開く保存

前提の表を読んで回す試算 haizen_sim.py(全文)

変えたのは、前提の値を書いていた所(COMMON・STORES・図面のある店の名前・台数などの選び方・判定の線)を、表から読む形にしたことだけです。表の JSON では組(タプル)がリストになるので、元の形に戻してから使います。店の形は、サイトに公開している public/sim/layouts/ を直接読むようにしました。前は、試算のフォルダに店の形の写しを置いていました。

03_haizen_sim.py.txt30,782字・709行・python
"""
サービスロボット導入シミュレーション #1 配膳ロボット(試作 v2.9)

前提の値は、このファイルの隣の assumptions.json(前提の表)から読む(2026-10-31。エイイチと決めた)。
値ごとに根拠の種類(仮置き・実感・公開事例・Unity・現場・決まり・設定)と出典・更新日を持つ。
値を直すときは表を直し、git に残す。店の形は p2m-site の public/sim/layouts/(正)を直接読む。
表に移したときに値・式・乱数の使い方は変えていない(v2.9 と同じ結果になることを check_same.py で確かめた)。
別のタブで開く保存

v2.9 と同じ数字が出るかの確かめ

表に移したことで、試算の答えが少しでも変わっていたら意味がありません。3つの方法で確かめました。

1つ目は、v2.9 のコードと新しいコードを両方読み込み、前提の値が型まで同じか、32,850条件の並びが同じか、前提より下の式が1文字も変わっていないかを比べました。

import importlib.util, sys
def load(path, name):
    spec = importlib.util.spec_from_file_location(name, path); m = importlib.util.module_from_spec(spec)
    sys.path.insert(0, path.rsplit('/',1)[0]); spec.loader.exec_module(m); sys.path.pop(0); return m
old = load('/home/claude/v29/haizen_sim.py', 'old')
new = load('/home/claude/p2m-site/tools/serving_sim/haizen_sim.py', 'new')
def same(a, b, path=''):
    if type(a) != type(b): print('型が違う', path, type(a), type(b)); return False
    if isinstance(a, dict):
        if list(a) != list(b): print('並びが違う', path, list(a), list(b)); return False
        return all(same(a[k], b[k], path+'/'+str(k)) for k in a)
    if isinstance(a, (list, tuple)):
        return len(a) == len(b) and all(same(x, y, f'{path}[{i}]') for i, (x, y) in enumerate(zip(a, b)))
    if a != b: print('値が違う', path, a, b); return False
    return True
ok = True
for n in ['COMMON','STORES','ROBOTS','ROBOTS_ALL','MODES','TRAYS','FRAGILE_BY','REPS','NG_MIN','STAFF_FULL','UNSTABLE_MIN','LAYOUT_LABELS']:
    r = same(getattr(old, n), getattr(new, n), n); print(n, r); ok &= r
print('条件の並び', list(old.conditions()) == list(new.conditions()), len(list(new.conditions())))
print('式(前提の行より下)が同じ', open('/home/claude/v29/haizen_sim.py').read().split('def tri(rng, lmh):')[1].split('UNSTABLE_MIN')[0] == open('/home/claude/p2m-site/tools/serving_sim/haizen_sim.py').read().split('def tri(rng, lmh):')[1].split('UNSTABLE_MIN')[0])
print('ALL', ok)
COMMON True
STORES True
ROBOTS True
ROBOTS_ALL True
MODES True
TRAYS True
FRAGILE_BY True
REPS True
NG_MIN True
STAFF_FULL True
UNSTABLE_MIN True
LAYOUT_LABELS True
条件の並び True 32850
式(前提の行より下)が同じ True
ALL True

2つ目は、条件を全体から決まった間隔で600選び、新しいコードで回し直して、v2.9 の結果(results.json)と全部の指標を比べました。表を直していないのにずれたら、コードか店の形が変わっているということです。このスクリプトは、これからも同じ確かめに使います。

"""前提の表と試算のコードで、前の結果(results.json)と同じ数字が出るかを確かめる(2026-10-31)。
使い方: python3 check_same.py <前の results.json> [確かめる条件の数(既定 60)]
- 店ごと・ロボットの台数ごとに散らばるように、全条件から決まった間隔で選んで回し、指標を全部くらべる
- 表の値を直していないのにずれたら、コードか店の形が変わっている。表の値を直したあとなら、ずれるのが当たり前
- 前提(common)が前の結果と違うときは、そう表示してから比べる"""
import json
import sys
from multiprocessing import Pool

import haizen_sim as h

res = json.load(open(sys.argv[1], encoding="utf-8"))
n = int(sys.argv[2]) if len(sys.argv) > 2 else 60
if res.get("common") != json.loads(json.dumps(h.COMMON)):
    print("注意:前の結果と前提(common)が違う。ずれが出て当たり前")
rows = {h.key(r): r for r in res["rows"]}
conds = list(h.conditions())
step = max(1, len(conds) // n)
pick = conds[::step][:n]
with Pool() as pool:
    got = pool.map(h.run_condition, pick)
bad = 0
for c, r in zip(pick, got):
    old = rows.get(h.key(c))
    if old is None:
        print("前の結果にない条件", c); bad += 1; continue
    diff = {m: (old[m], r[m]) for m in r if m not in c and old.get(m) != r[m]}
    if diff:
        bad += 1; print("ずれ", c, diff)
print(f"{len(pick)} 条件を回し、{len(pick) - bad} 条件が前の結果と同じ(ずれ {bad})")
sys.exit(1 if bad else 0)
$ python3 check_same.py /home/claude/v29/results.json 600
600 条件を回し、600 条件が前の結果と同じ(ずれ 0)

real	3m56.918s
user	7m16.276s
sys	0m0.156s

店の形は、v2.9 の写し(版 08db8a0d067c)から、サイトに公開している今の店の形(版 9b8dae7ebf8c。スタッフの持ち場を足した版)に変わりました。卓ごとの受け渡し口からの距離は、6店とも同じでした。そのため、この確かめでも数字は変わっていません。

3つ目は、Unity が読む serving-robot-params.json を、新しいコードで書き出し直しました。公開中のものと、バイトまで同じでした。書き出しの道具は、Unity だけの回し方の決まり(入口番・持ち場など。公開中の params に直接書いて直している)を消さないよう、書き出し先の今の値を引き継ぐ形に直しています。

04_sec01.txt21,999字・95行・text
$ python3 export_params.py /home/claude/v29/results.json /tmp/p.json
/tmp/p.json 90 conditions / generation 9b8dae7ebf8c
  {'store': 'fam_p1', 'staff': 4, 'robots': 0, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 1, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 4.0915, 'staff_wait_mean': 2.6584, 'dish_wait_mean': 0.6259, 'pay_wait_mean': 4.8049}
  {'store': 'fam_p1', 'staff': 4, 'robots': 1, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 1.6842, 'staff_wait_mean': 0.4947, 'dish_wait_mean': 1.9664, 'pay_wait_mean': 5.6376}
  {'store': 'fam_p1', 'staff': 4, 'robots': 2, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 1.129, 'staff_wait_mean': 0.3856, 'dish_wait_mean': 0.9579, 'pay_wait_mean': 6.5762}
  {'store': 'fam_p5', 'staff': 4, 'robots': 0, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 1, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 3.076, 'staff_wait_mean': 2.1333, 'dish_wait_mean': 0.6299, 'pay_wait_mean': 4.8601}
  {'store': 'fam_p5', 'staff': 4, 'robots': 1, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 1.5758, 'staff_wait_mean': 0.5449, 'dish_wait_mean': 2.0126, 'pay_wait_mean': 5.8228}
  {'store': 'fam_p5', 'staff': 4, 'robots': 2, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 1.6014, 'staff_wait_mean': 0.468, 'dish_wait_mean': 1.01, 'pay_wait_mean': 6.597}
別のタブで開く保存

Unity が読む値を書き出す export_params.py(全文)

05_export_params.py.txt5,544字・107行・python
"""試算(haizen_sim.py)の条件と店の回し方の値を、Unity が読む JSON に書き出す(2026-10-22。v2.8 で2026-10-25 にレジの数を足した)。

「何について正か」(unity_design_v0.md の 0 章)で、条件と店の回し方の決まり・時間・数字の定義はサイトが正。
Unity はこの JSON を読み、値を手で写さない(LOG359 のずれ9「値の手写し」をなくすため)。

使い方: python3 export_params.py results.json <出力先>   (出力先は p2m-site の public/sim/serving-robot-params.json。unity_rules は出力先の今の値を引き継ぐ)
- common:店に共通の値。role が "unity_measures" の値は Unity で測ってサイトに戻すための仮の値なので、Unity は読まない
- stores:Unity で回す店(fam_p1・fam_p5・fam_p2・fam_p3・fam_p4・fam_p0)の値。registers はレジの数(v2.8)
別のタブで開く保存

表計算ソフトで見るための写しを作る make_table.py と assumptions.csv

正は assumptions.json です。CSV は表計算ソフトで開いて眺めるための写しで、CSV を直しても試算には入りません。

"""前提の表(assumptions.json)を、表計算ソフトで開ける CSV(assumptions.csv)に書き出し、根拠の種類ごとの数を出す(2026-10-31)
使い方: python3 make_table.py
- 正は assumptions.json。CSV は見るためのもので、CSV を直しても試算には入らない
- 表を直したら、これを回して CSV も一緒に git に入れる"""
import collections, csv, json, os
here = os.path.dirname(os.path.abspath(__file__))
A = json.load(open(os.path.join(here, "assumptions.json"), encoding="utf-8"))
rows = []
def add(group, store, key, r):
    v = r["value"]
    rows.append([group, store, key, json.dumps(v, ensure_ascii=False) if isinstance(v, (list, dict, bool)) else v,
                 r.get("unit", ""), r.get("what", ""), r.get("basis", ""), r.get("source", ""),
                 "" if r.get("n") is None else r["n"], r.get("updated", ""), r.get("note", "")])
for k, r in A["common"].items():
    add("共通", "", k, r)
for sk, s in A["stores"].items():
    for k, r in s["values"].items():
        add("店のタイプ", s["label"], k, r)
for g, name in (("choices", "選べる幅・回す回数"), ("judge", "判定の線")):
    for k, r in A[g].items():
        add(name, "", k, r)
with open(os.path.join(here, "assumptions.csv"), "w", encoding="utf-8-sig", newline="") as f:
    w = csv.writer(f)
    w.writerow(["区分", "店", "項目", "値", "単位", "何の値か", "根拠", "出典・決めた経緯", "現場で測った回数", "更新日", "補足"])
    w.writerows(rows)
cnt = collections.Counter(r[6] for r in rows)
print(f"{len(rows)} 行を assumptions.csv に書いた")
for k in A["basis_kinds"]:
    print(f"  {k}: {cnt.get(k, 0)}")
109 行を assumptions.csv に書いた
  仮置き: 79
  実感: 1
  公開事例: 1
  Unity: 0
  現場: 0
  決まり: 17
  設定: 11

assumptions.csv(全文)

区分,店,項目,値,単位,何の値か,根拠,出典・決めた経緯,現場で測った回数,更新日,補足
共通,,staff_speed,1.0,m/秒,スタッフが歩く速さ,仮置き,,,,
共通,,robot_speed,0.5,m/秒,ロボットが店内を走る速さ,仮置き,,,,Unity で測って戻す値(Unity の最高は0.6m/秒)。2026-10-24 に今のまま手を付けないと決めた
共通,,guide_s,45,秒,案内(席の説明など。歩く時間は別),仮置き,,,,
共通,,staff_place_s,10,秒,スタッフが卓に皿を置く,仮置き,,,,
共通,,robot_handoff_s,25,秒,客がロボットから皿を取る,仮置き,,,,
共通,,pay_s,90,秒,会計1組,実感,エイイチのファミレスの経験(1組90秒くらい),,2026-10-25,
共通,,call_s,30,秒,呼び出しへの対応(歩く時間は別),仮置き,,,,
別のタブで開く保存

全条件を分けて回し、1つにまとめる道具

全32,850条件を回すときは、Mac とクラウドに分けて回し、あとで1つの results.json にまとめます。v2.9 のときと同じやり方を、フォルダの名前に頼らない形にして入れました。

run_shard.py

"""v2.8 の全条件を、番号で分けて回す(2026-10-25。計算機を2台に分けて速くするため)。
使い方: python3 run_shard.py <k> <n> <出力.json> [使い回す結果.json ...]
- 条件の並び(haizen_sim.conditions())の番号を n で割った余りが k のものだけ回す
- 使い回す結果に同じ条件(同じ前提)があれば回さない。100条件ごとに出力へ途中経過を書く"""
import json, os, sys
from multiprocessing import Pool
import haizen_sim as h

k, n, out = int(sys.argv[1]), int(sys.argv[2]), sys.argv[3]
common = json.loads(json.dumps(h.COMMON))
done = {}
for path in sys.argv[4:] + [out]:
    try:
        old = json.load(open(path, encoding="utf-8"))
        if old.get("common") == common and old.get("reps") == h.REPS:
            done.update({h.key(r): r for r in old["rows"]})
    except FileNotFoundError:
        pass
conds = [c for i, c in enumerate(h.conditions()) if i % n == k]
todo = [c for c in conds if h.key(c) not in done]
print(f"shard {k}/{n}: {len(conds)} conditions, {len(todo)} to run", flush=True)

def save():
    rows = [done[h.key(c)] for c in conds if h.key(c) in done]
    with open(out + ".tmp", "w", encoding="utf-8") as f:
        json.dump({"common": h.COMMON, "reps": h.REPS, "shard": [k, n], "rows": rows}, f, ensure_ascii=False)
    os.replace(out + ".tmp", out)

with Pool() as pool:
    for i, r in enumerate(pool.imap_unordered(h.run_condition, todo, chunksize=2), 1):
        done[h.key(r)] = r
        if i % 100 == 0:
            save(); print(f"  {i}/{len(todo)}", flush=True)
save(); print("done", flush=True)

run_timed.py

"""run_shard.py と同じ分け方で、決めた秒数だけ回して途中経過を書く(Mac 側は1回の呼び出しが3分までなので、区切って回す)。
使い方: python3 run_timed.py <k> <n> <出力.json> <秒> [使い回す結果.json ...]"""
import json, os, sys, time
from multiprocessing import Pool
import haizen_sim as h
k, n, out, limit = int(sys.argv[1]), int(sys.argv[2]), sys.argv[3], float(sys.argv[4])
t0 = time.time()
common = json.loads(json.dumps(h.COMMON))
done = {}
for path in sys.argv[5:] + [out]:
    try:
        old = json.load(open(path, encoding="utf-8"))
        if old.get("common") == common and old.get("reps") == h.REPS:
            done.update({h.key(r): r for r in old["rows"]})
    except FileNotFoundError:
        pass
conds = [c for i, c in enumerate(h.conditions()) if i % n == k]
todo = [c for c in conds if h.key(c) not in done][::-1]   # 後ろから回す(クラウドは前から回している)
def save():
    rows = [done[h.key(c)] for c in conds if h.key(c) in done]
    with open(out + ".tmp", "w", encoding="utf-8") as f:
        json.dump({"common": h.COMMON, "reps": h.REPS, "shard": [k, n], "rows": rows}, f, ensure_ascii=False)
    os.replace(out + ".tmp", out)
got = 0
pool = Pool()
for r in pool.imap_unordered(h.run_condition, todo, chunksize=1):
    done[h.key(r)] = r; got += 1
    if time.time() - t0 > limit:
        break
pool.terminate()
save()
print(f"shard {k}/{n}: +{got} / left {len(todo) - got}", flush=True)

merge_shards.py

"""分けて回した結果(shards/*.json)を1つの results.json にまとめる(v2.9 の merge_shards_v29.py と同じやり方)
使い方: python3 merge_shards.py <shards のフォルダ> <出力 results.json>
- 同じ前提(common・reps)の行だけを使う。条件の並びは haizen_sim.conditions() の順
- 同じ条件が2つの計算機で回っていたら、数字が同じかを確かめる"""
import glob, json, os, sys
import haizen_sim as h
src, out = sys.argv[1], sys.argv[2]
common = json.loads(json.dumps(h.COMMON))
rows, dup, diff = {}, 0, 0
for f in sorted(glob.glob(os.path.join(src, "*.json"))):
    d = json.load(open(f, encoding="utf-8"))
    if d.get("common") != common or d.get("reps") != h.REPS:
        print("前提が違うので使わない:", f); continue
    for r in d["rows"]:
        k = h.key(r)
        if k in rows:
            dup += 1
            if any(abs(rows[k][m] - r[m]) > 1e-9 for m in r if isinstance(r[m], (int, float))):
                diff += 1
        else:
            rows[k] = r
conds = list(h.conditions())
missing = [c for c in conds if h.key(c) not in rows]
print("条件", len(conds), "揃った", len(conds) - len(missing), "足りない", len(missing), "重なり", dup, "うち数字が違う", diff)
if not missing:
    json.dump({"common": h.COMMON, "reps": h.REPS, "rows": [rows[h.key(c)] for c in conds]},
              open(out, "w", encoding="utf-8"), ensure_ascii=False)
    print(out, "を書いた")

build_page_v29.py(v2.9 のときに試算のページへ結果を入れた道具)

"""試算 v2.9(会計のスタッフの頼みを、レジに並んだ時刻で並べる版)の結果を、公開中の試算のページに入れる(2026-10-30)
使い方: python3 build_page_v29.py <公開中のページ> <v2.9 の results.json> <出力するページ>
- const D:結果の行(r)だけを v2.9 に入れ替える。条件の並び・列・店の情報・平面図は公開中のまま
- 画面:見出しの版を v2.9 にする。前提の「会計とレジ」に v2.9 の決まりを書く
- 費用の欄・LP 用の色・使われ方の記録は公開中のまま"""
import json, sys
import haizen_sim as h

live, res, out = sys.argv[1:4]
s = open(live, encoding="utf-8").read()
N = json.load(open(res, encoding="utf-8"))
assert N["common"] == json.loads(json.dumps(h.COMMON)), "結果が今の haizen_sim.py の前提と違う"
assert N["common"]["model"] == "v2.9"

lines = s.split("\n")
idx = [i for i, l in enumerate(lines) if l.startswith("const D = ")]
assert len(idx) == 1
D = json.loads(lines[idx[0]][len("const D = "):].rstrip(";"))
K = D["k"]
cond = K[:8]
mets = K[8:-1]
assert cond == ["store", "staff", "robots", "arrivals_per_h", "mode", "trays", "fragile_by", "registers"] and K[-1] == "ng"
conds = list(h.conditions())
rows_by_key = {h.key(r): r for r in N["rows"]}
assert len(rows_by_key) == len(conds) == len(D["r"]), (len(rows_by_key), len(conds), len(D["r"]))
new = []
for old in D["r"]:
    c = dict(zip(cond, old[:8]))
    r = rows_by_key[h.key(c)]
    new.append(old[:8] + [round(r[k], 3) for k in mets] + [1 if r["staff_wait_mean"] >= h.NG_MIN else 0])
D["r"] = new
assert {k: v for k, v in D["common"].items() if k != "model"} == {k: v for k, v in N["common"].items() if k != "model"}
D["common"]["model"] = "v2.9"
lines[idx[0]] = "const D = " + json.dumps(D, ensure_ascii=False, separators=(",", ":")) + ";"
s = "\n".join(lines)

def rep(a, b):
    global s
    assert s.count(a) == 1, (a, s.count(a))
    s = s.replace(a, b)

rep("数字はすべて仮置き(試作 v2.8)", "数字はすべて仮置き(試作 v2.9)")
rep("会計はレジ1つにつき1組ずつ。食べ終えた組は卓に座ったままレジの順番を待ち、順番が来たらスタッフの手が空くのを待って会計する。",
    "会計はレジ1つにつき1組ずつ。食べ終えた組は卓に座ったままレジの順番を待つ。会計のスタッフは、食べ終えてレジに並んだ順に付き、同じ優先の配膳・案内・呼び出しより先になる。会計が終わると、同じスタッフがそのまま次の組の会計に付く(v2.9 から。それまでは、レジの順番が来てからスタッフを頼んだので、ほかの仕事の後ろに並び、そのあいだレジが止まっていた)。")
open(out, "w", encoding="utf-8").write(s)
print(out, len(new), "rows")

試算の置き場所の決まり(README.md)と、保存先の決まりに足した2行

# 配膳ロボットの試算(サービスロボット導入シミュレーション)

/series8 の配膳ロボットの試算(haizen_sim.py)と、その前提の表。2026-10-31 に iCloud の `simulation/v2_9/` からここへ移した。

## 何のためにこの形にしたか

現場のデータ(観察シート・動画のメモ・店の協力で測った値)が届いたときに、試算へ正確に、すぐ入れられるようにするため。

- 前提の値は `assumptions.json`(前提の表)に1つにまとめた。試算はこの表を読んで回る。コードの中に値を書かない
- 値ごとに、根拠の種類・出典・現場で測った回数・更新日を持つ。どの値が実測で、どれがまだ仮かが表で分かる
- 直した記録は git に残る(いつ・なぜ・どの値を)。版ごとにフォルダをコピーする形(v2_1〜v2_9)はやめた

## 根拠の種類

| 種類 | 意味 |
|---|---|
| 仮置き | まだ根拠がない仮の値。現場・公開事例・Unity の値で置き換えていく |
| 実感 | エイイチの現場の経験(ファミレスで働いていたころ) |
| 公開事例 | メーカーの仕様・導入事例など、公開されている値 |
| Unity | Unity(ServiceSim)で測った値 |
| 現場 | 現場で測った値。`n` に測った回数を書く |
| 決まり | エイイチと決めた、試算の回し方・判定の線・運用の決まり |
| 設定 | 試算の回し方の設定。店の前提ではない |

## ファイル

- `assumptions.json`:前提の表(正)。共通の値(common)・店のタイプ(stores)・図面のある店の作り方(layout_stores)・選べる幅と回す回数(choices)・判定の線(judge)
- `assumptions.csv`:表計算ソフトで見るための写し(`make_table.py` で作る。CSV を直しても試算には入らない)
- `haizen_sim.py`:試算 v2.9。式と乱数の使い方は v2.9 のまま
- `check_same.py`:前の結果と同じ数字が出るかを、条件を選んで回して確かめる
- `run_shard.py`・`run_timed.py`・`merge_shards.py`:全条件を分けて回し、1つの results.json にまとめる
- `export_params.py`:Unity が読む `public/sim/serving-robot-params.json` を書き出す(unity_rules は今の値を引き継ぐ)
- `build_page_v29.py`:v2.9 のときに試算のページへ結果を入れた道具(版ごとに書き替えて使う)

店の形は、`public/sim/layouts/`(正)を直接読む。前は試算のフォルダに写しを置いていた。

## 前提の値を直す流れ

1. `assumptions.json` の値を直し、`basis`・`source`・`n`・`updated` も書く(現場の値なら basis は「現場」、n は測った回数)
2. `python3 make_table.py` で CSV を作り直す
3. 結果に効く値を直したら、`model` の版を上げる(例 v2.9 → v2.10)
4. 全条件を回す(下)→ ページと params に入れる → 公開
5. git に、直した値と理由を書いて入れる

結果(results.json・20MB)は git に入れない。iCloud の `サービスロボット導入シミュレーション/simulation/` に版の名前で置く。

## 現場の記録(観察シート)を取り込む(2026-10-31)

観察シートの記録は、店の人が LINE かメールで店の中の人へ送る。その文が担当の方から届いたら、文をそのまま .txt にして取り込む。

```
python3 field_import.py <field のフォルダ> 届いた文.txt     # field のフォルダは iCloud の サービスロボット導入シミュレーション/field/
```

店のデータは GitHub に入れない。取り込んだもの(原文・1回ずつの記録・店の条件・値の案 proposal.md)は field のフォルダにだけ置く。

| シートの記録 | 前提の表の行 | 入れ方 |
|---|---|---|
| 会計 | `pay_s` | 中央値の秒 |
| 片付け | `clear_s` | 同上 |
| テーブルを拭く | `wipe_s` | 同上 |
| 席へ案内(席に着いて説明を始めたら〜テーブルを離れたら) | `guide_s` | 同上。2026-10-31 16:08(シートの直しの公開)より前の記録は、歩く時間を含む前の区切りなので使わない |
| 呼び出し | `call_s` | 同上 |
| 料理を置く | `staff_place_s` | 同上 |
| ロボットの往復の「止まった」 | `pause_rate` | 1回以上止まった往復の割合 |
| ロボットの往復の「人が動かした」 | `stuck_rate` | 人が動かした往復の割合 |
| ロボットの往復の時間・回転率・滞在時間の目安 | 入れない | 試算の結果と比べる答え合わせ用 |
| スタッフ・卓の数・来店のペース | 入れない(今は記録だけ) | 店ごとの値で試算を回すのは、店ごとの前提を重ねる段で作る |

決まり(2026-10-31 エイイチと決めた):
- 値は中央値。店ごとに15回に届くまでは「参考」
- 標準の値を置き換える候補にするのは、2店以上・合わせて15回以上そろったとき。表は自動で書き換えない。proposal.md の案と差を見て、エイイチが承認してから直す
- 表に入れるときは basis を「現場」、n に回数、source に店の数と期間を書く。店の名前は書かない

## 回し方

```
pip install simpy==4.1.2
python3 check_same.py <前の results.json> 60        # 表を直していないなら、ずれ 0 になる
python3 run_shard.py <k> <n> shards/<名前>.json      # 分けて回す(Mac 側は run_timed.py で区切る)
python3 merge_shards.py shards results.json
python3 export_params.py results.json ../../public/sim/serving-robot-params.json
python3 make_table.py
```

## 移したときの確かめ(2026-10-31)

- 表から読んだ値(共通・店のタイプ5つ・図面のある店6つ・選べる幅・回す回数・判定の線)が、v2.9 のコードの値と型まで同じ
- 32,850 条件の並びが同じ。前提より下の式は1文字も変えていない
- 店の形を `public/sim/layouts/`(版 9b8dae7ebf8c)から読んでも、卓ごとの距離は v2.9 の写し(08db8a0d067c)と同じ
- `check_same.py` で600条件を回し、v2.9 の results.json と全指標が同じ(ずれ 0)
- `export_params.py` で書き出した params が、公開中のものとバイトまで同じ

データの置き場所は、データの種類ごとに1か所と決めています。今回、次の2行を足しました。店の記録は店のデータなので、GitHub には入れません。

データの種類保存先
配膳ロボットの試算のコードと前提の表(assumptions.json)
GitHub PixelstoMotors/p2m-site の tools/serving_sim/(結果の results.json は iCloud の simulation/ に版の名前で置く)
サービスロボット導入シミュレーションの現場の記録(観察シートの届いた文・1回ずつの記録・店の条件・値の案)
iCloud Drive/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/field/(GitHub に入れない)

作業ログ ― 観察シートの記録を、試算の前提の表に取り込む

観察シートの記録は、店の中の人へ LINE・メールで送られる

観察シートは、店の人がスマホで、会計・片付け・テーブルを拭く・席へ案内・呼び出し・料理を置くの6つの作業の時間を、スタートとストップで測るページです。ロボットを入れている店では、ロボットの往復の時間と、止まった回数・人が動かした回数も測れます。

測った記録は、店長・お店の管理の方・ロボットの担当の方へ、LINE かメールで送ります。こちらに届くとしたら、店のデータをまとめた担当の方や、企業の担当の方から、その文がそのまま渡される形です。なので、取り込みの入り口は「届いた文をそのまま貼る」にしました。

観察シートの記録と、前提の表の行の対応

シートの記録前提の表の行入れ方
会計(レジに立つ〜離れる)pay_s中央値の秒
片付け(まとめ始め〜まとめ終わり)
clear_s同上
テーブルを拭く(拭き始め〜セット終わり)
wipe_s同上
席へ案内(席に着いて説明を始めたら〜テーブルを離れたら)
guide_s同上
呼び出し(テーブルに着く〜離れる)
call_s同上
料理を置く(テーブルに着く〜離れる)
staff_place_s同上
ロボットの往復の「止まった」pause_rate1回以上止まった往復の割合
ロボットの往復の「人が動かした」
stuck_rate人が動かした往復の割合
ロボットの往復の時間・回転率・滞在時間の目安
入れない
卓までの距離が分からないので、ロボットの速さは出せない。試算の結果と比べる答え合わせに使う
スタッフ・卓の数・来店のペース
入れない(今は記録だけ)店ごとの前提を重ねる段で使う

ロボットの2つを「割合」にしたのは、試算の pause_rate・stuck_rate が「1往復で起きる確率」だからです。1往復で3回止まっても、1回止まった往復として数えます。

測った値を試算に入れる決まり4つ

決めたこと決めた中身理由
届いた記録の置き場所
iCloud の サービスロボット導入シミュレーション/field/。届いた文の原文と、取り込んだ表を置く。GitHub には入れない
店の記録は店のデータなので、リポジトリが非公開でも GitHub には置かない
席へ案内の時間
シートの区切りを「席に着いて説明を始めたら〜テーブルを離れたら」に変える
試算の guide_s は歩く時間を含まない。店の形がない店でも、そのまま入れられる
標準の値を置き換える候補にする条件
2店以上・合わせて15回以上
1店だけだと、その店のくせが全部の店に入ってしまう
店ごとの値で試算を回すか今は記録だけ
店ごとの前提を重ねる仕組みは、記録が届いてから作る

あわせて、1つの値は測った回数の中央値で入れ、店ごとに15回(シートの目安と同じ)に届くまでは「参考」として扱う、としました。前提の表は自動では書き換えません。値の案と今の値との差を見て、承認してから表を直します。表に入れるときは、根拠を「現場」にして、n に回数、出典に店の数と期間を書きます。店の名前は書きません。

観察シートの「席へ案内」を、歩く時間を含まない区切りに変えた

前のシートは、案内を「お客さんに声をかけたら〜席に着いたら」で測っていました。これだと、入口から席まで歩く時間が入ります。試算の案内(guide_s)は、歩く時間を別に計算して足しているので、シートの時間をそのまま入れると、歩く時間を二重に数えてしまいます。

  • ねらい:シートで測った案内の時間を、引き算なしで試算の guide_s に入れられるようにする
  • 変わったこと:測る区切りを「席に着いて説明を始めたら〜テーブルを離れたら」に変えた。画面の「試算で置いている値」も「45秒(歩く時間は別)」から「45秒」にした
  • 見込める効果:店の形(図面)がない店の記録でも、案内の時間を試算に入れられる(見込み)
diff --git a/public/sim/serving-robot-observation.html b/public/sim/serving-robot-observation.html
index 3566b22..e5342b8 100644
--- a/public/sim/serving-robot-observation.html
+++ b/public/sim/serving-robot-observation.html
@@ -248,7 +248,7 @@ 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:'guide',t:'席へ案内',s:'席に着いて説明を始めたら',e:'テーブルを離れたら',sim:'45秒'},
  {k:'call',t:'呼び出し',s:'テーブルに着いたら',e:'テーブルを離れたら',sim:'30秒'},
  {k:'place',t:'料理を置く',s:'テーブルに着いたら',e:'テーブルを離れたら',sim:'10秒'}
 ];
観察シートの「席へ案内」の測る画面。0:00 の下に「席に着いて説明を始めたら、スタート」と出ている

直しを公開したのは 2026-10-31 の16時8分ごろです。それより前に測った案内の記録は、歩く時間を含む前の区切りなので、値の案には使いません。

届いた文を取り込み、値の案を出す field_import.py(全文)

届いた文を .txt にして渡すと、field/ に次の4つを作ります。

  • raw/:届いた文の原文の写し(直さない)
  • observations.csv:1回ずつの記録。同じ店・同じ日・同じ時刻・同じ作業の行は、二重に入れない
  • store_conditions.csv:1通ごとの店の条件(スタッフ・卓の数・時間帯・ようす・来店のペースなど)
  • proposal.md:前提の表に入れる値の案と、今の値との差。取り込むたびに作り直す
07_field_import.py.txt10,054字・229行・python
"""観察シートの記録(LINE・メールで届いた文)を取り込み、試算の前提の表に入れる値の案を出す(2026-10-31)

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

field のフォルダは iCloud の サービスロボット導入シミュレーション/field/(店のデータは GitHub に入れない)。
- raw/                  届いた文の原文(取り込んだときに写す。直さない)
別のタブで開く保存

スマホで測った試しの記録7回を取り込んだ

道具ができたあと、使えるかを試しました。机の上で観察シートを開き、作業ごとに1〜2回スタートとストップを押して、記録を送る文をチャットに貼りました。店で測ったものではありません。

届いた文(試しの記録)

観察シートの記録
日付:2026/10/31
目的:ロボットを入れていて、改善したい
お店:(未入力) スタッフ3人 ロボット2台
卓の数:約20卓
時間帯:昼(ランチ) スタッフが足りない・いつもどおり
会計:だいたい13秒(1回)
テーブルを拭く:だいたい12秒(1回)
席へ案内:だいたい11秒(1回)
呼び出し:だいたい7秒(1回)
料理を置く:だいたい8秒(2回)
ロボットの往復:だいたい27秒(1回)
出入り:入ってきた15組・会計した7組(数えはじめに10卓にお客さん)

--- 1回ずつの記録 ---
16:10:41 会計 12.7秒
16:10:57 席へ案内 10.7秒
16:11:12 ロボットの往復 26.7秒(止まった0回・人が動かした1回)
16:11:56 料理を置く 5.3秒
16:12:04 料理を置く 10.1秒
16:12:18 呼び出し 6.9秒
16:12:28 テーブルを拭く 12秒

--- 入ってきた時刻 ---
16:11:44、16:11:44、16:11:45、16:11:45、16:11:45、16:11:45、16:11:46、16:11:46、16:11:50、16:11:51、16:11:51、16:11:51、16:11:51、16:11:51、16:11:52

--- 会計した時刻 ---
16:11:48、16:11:48、16:11:48、16:11:49、16:11:49、16:11:52、16:11:53

取り込んだときの出力

../try_eiichi.txt: 名前なし_2026-10-31 2026-10-31 → 記録 7 回(新しく 7 回)・原文 raw/2026-10-31_名前なし_20261031.txt
proposal.md を作り直した

proposal.md(値の案)

# 前提の表に入れる値の案(観察シートの記録から)

作った日:2026-10-31 記録 7 回

- 店ごとに 15 回に届くまでは「参考」。標準の値の候補は、2店以上・合わせて 15 回以上
- 表はまだ書き換えていない。候補を表に入れるかはエイイチが決める(basis を「現場」、n に回数、source に店の数と期間。店の名前は書かない)
- 中央値で比べる。席へ案内は 2026-10-31 16:08 より前の記録を使わない(歩く時間を含む前の区切り)

## 作業の時間(秒)

| 行 | シートの作業 | 今の値(根拠) | 全体の中央値 | 店の数 | 回数 | 判定 | 店ごと(中央値・回数) |
|---|---|---|---|---|---|---|---|
| `pay_s` | 会計 | 90(実感) | 12.7 | 1 | 1 | 参考 | 名前なし_2026-10-31 12.7秒・1回(参考) |
| `clear_s` | 片付け | 40(仮置き) | — | 0 | 0 | 記録なし | — |
| `wipe_s` | テーブルを拭く | 40(仮置き) | 12 | 1 | 1 | 参考 | 名前なし_2026-10-31 12秒・1回(参考) |
| `guide_s` | 席へ案内 | 45(仮置き) | 10.7 | 1 | 1 | 参考 | 名前なし_2026-10-31 10.7秒・1回(参考) |
| `call_s` | 呼び出し | 30(仮置き) | 6.9 | 1 | 1 | 参考 | 名前なし_2026-10-31 6.9秒・1回(参考) |
| `staff_place_s` | 料理を置く | 10(仮置き) | 7.7 | 1 | 2 | 参考 | 名前なし_2026-10-31 7.7秒・2回(参考) |

## ロボット(往復のうち、起きた往復の割合)

| 行 | 数え方 | 今の値(根拠) | 全体 | 店の数 | 往復の回数 | 判定 | 店ごと |
|---|---|---|---|---|---|---|---|
| `pause_rate` | 1回以上止まった往復 | 0.3(仮置き) | 0 | 1 | 1 | 参考 | 名前なし_2026-10-31 0・1往復 |
| `stuck_rate` | 人が動かした往復 | 0.01(仮置き) | 1 | 1 | 1 | 参考 | 名前なし_2026-10-31 1・1往復 |

ロボットの往復の時間(答え合わせ用・表には入れない):中央値 26.7 秒・1 往復

## 店の条件(今は記録だけ。店ごとの値で試算は回さない)

| 店 | 日付 | 時間帯・ようす | スタッフ | ロボット | 卓 | 来店/時 | 回転率/時 | 滞在の目安(分) |
|---|---|---|---|---|---|---|---|---|
| 名前なし_2026-10-31 | 2026-10-31 | 昼(ランチ) スタッフが足りない・いつもどおり | 3 | 2 | 20 | — | — | — |

observations.csv

store,date,time,kind,sec,stops,helps,raw_file
名前なし_2026-10-31,2026-10-31,16:10:41,会計,12.7,,,2026-10-31_名前なし_20261031.txt
名前なし_2026-10-31,2026-10-31,16:10:57,席へ案内,10.7,,,2026-10-31_名前なし_20261031.txt
名前なし_2026-10-31,2026-10-31,16:11:12,ロボットの往復,26.7,0,1,2026-10-31_名前なし_20261031.txt
名前なし_2026-10-31,2026-10-31,16:11:56,料理を置く,5.3,,,2026-10-31_名前なし_20261031.txt
名前なし_2026-10-31,2026-10-31,16:12:04,料理を置く,10.1,,,2026-10-31_名前なし_20261031.txt
名前なし_2026-10-31,2026-10-31,16:12:18,呼び出し,6.9,,,2026-10-31_名前なし_20261031.txt
名前なし_2026-10-31,2026-10-31,16:12:28,テーブルを拭く,12.0,,,2026-10-31_名前なし_20261031.txt

store_conditions.csv

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
名前なし_2026-10-31,2026-10-31,ロボットを入れていて、改善したい,昼(ランチ),スタッフが足りない・いつもどおり,3,2,20,15,7,,,,,10,,2026-10-31_名前なし_20261031.txt

7回の記録が、表の6つの行(会計・テーブルを拭く・席へ案内・呼び出し・料理を置く・人が動かした往復の割合)と、止まった往復の割合に分かれて入りました。どれも1店で15回に届いていないので、判定は全部「参考」で、前提の表は書き換えていません。来店のペースと回転率が空なのは、数えた時間が10秒ほどしかなく、シートが「1時間あたり」の行を出さなかったためです。店で15分以上数えれば出ます。

試しの記録は、本物の店の記録と混ざらないように、field/_試し/ に入れました。

試しで見つけた、席へ案内の記録を使い始める日のずれ

最初の道具は、新しい区切りの案内の記録を「2026-11-01 から」使う決まりにしていました。ところが、試しの記録は 10月31日の16時10分に、もう新しい区切りで測ったものです。日付で分けると、この記録が使われません。そこで、日付ではなく、シートの直しを公開した時刻(2026-10-31 16:08)より後の記録から使う形に直しました。上の proposal.md は、直したあとの道具で作り直したものです。

作業ログ ― 配膳ロボットの比較動画を、まとめて作る準備

比較動画は、同じお客さんの来方で撮った2本を左右に並べる

比較動画は、Unity で撮った同じ店の2本(たとえばロボット0台と2台)を、左右に並べて同時に流す動画です。同じ店の録画は、どれも同じお客さんの来方で撮っているので、違いはロボットの台数やスタッフの人数だけになります。1店7本、6店で42本を作ります。

素材の66本は、スタッフの待つ場所の決まり(手が空いた人のうち1人はレジ内で入口を見て、ほかの人はパントリーのまわりに散らばる)を入れたので、今夜 Claude Code が撮り直します。21時に依頼箱へ入れる予約をしてあり、Mac の作業はありません。KIOXIA の空きは 1.8TB で、前の夜の66本は 2.6GB でした。

比較動画を作る make_compare_stock.py を、新しい素材に向けた

  • 読む素材の夜を選べるようにした(--night。既定は今夜の 20261031_rec)。できた動画は stock/{夜の名前}/ に入れ、前の夜の素材で作った動画と混ざらないようにした
  • 1本目で止まっていた原因を直した。作業用のファイルを /tmp の決まった名前で作っていたため、前の回の残りとぶつかって書けなかった。毎回新しい作業用のフォルダを作る形にした
  • 字幕の字体を選べるようにした(CMP_FONT)。試しに頭の数秒だけ作れるようにした(--max-sec)
08_sec10.py.txt6,455字・92行・python
"""比較動画のストックを、録画ストック(night/{夜の名前})から順に作る(2026-10-31)
使い方: python3 make_compare_stock.py [--night 20261031_rec] [--out フォルダ] [--only N]   ※ できている mp4 は飛ばす
- 既定の夜は 20261031_rec(入口番・持ち場ありで撮り直した素材)。出力は stock/{夜の名前}/
- 1店7本:空いている・スタッフが多い・混んでいる・スタッフが少ない・普通(0台と2台)、普通の0台と1台・1台と2台
- 並べる順は、店 ③⑦④⑤⑥②、店の中は上の順(図の右上から)
- 片側は元の録画の半分の大きさ(幅1280)。縦は店の形に合わせる(縦横比を保つ)
- 30倍速(録画は15倍速なので1コマおき)
出力: videos/service_sim_compare/stock/{店}_{条件}_r{左}_vs_r{右}.mp4(字幕の控え .ass も)・進み具合は stock/progress.log
別のタブで開く保存

前の夜の素材で、1本目(③ 効率重視・入口が角、空いている、ロボット0台と2台)の頭の20秒だけを作りました。下の記録の時刻は、Mac 連携の作業環境の時計(協定世界時)で、日本時間では16時19分・16時20分です。

10-31 07:19 失敗 1/42 p1_aki_r0_vs_r2.mp4 [Errno 13] Permission denied: '/tmp/p1_aki_r0_vs_r2.ass'
10-31 07:19 全部おわり
10-31 07:20 できた 1/42 p1_aki_r0_vs_r2.mp4 長さ401秒 作るのに6秒
10-31 07:20 全部おわり
試しに作った比較動画の1コマ。左がロボット0台、右がロボット2台の③の店。上に店の時刻・入店待ちの組・片付け待ちの卓・動けなくなった回数、下に店と条件の説明と色の見本

20秒を6秒で作れたので、全部で約7分の動画1本は、約2分で作れる見込みです。ただ、Mac 連携の作業環境は1回の作業が3分までで、後ろで動かし続けた処理は、作業の区切りで止まります。そのため、1回に1本ずつ作る形にしました。42本で1時間半ほどの見込みです。明日の朝7時に、撮り直しが終わったかを確かめ、終わっていれば右上(③の空いている店)から作り始める予約を入れました。

店ごとの前提を重ねる段は、店の記録が届いてから作る

前提の表と取り込みの道具で、店の数字を試算に入れる入り口はできました。まだないのは、標準の値を書き換えずに「この店で測った値」を重ねて、その店だけの試算を回す仕組みです。これは記録だけにしておき、店の記録が届いてから作ります。

もう一つ、表の中の「Unity」の根拠は0個のままです。Unity ではロボットの速さや止まる回数をもう測っていますが、それを試算に入れるかは、まだ決めていません。入れるときは、この表の上で、根拠を「Unity」にして、どの夜の何回分の値かを書いて入れます。