---
title: "サービスロボット導入シミュレーション Log#43 | 病院の通れない所を直すと、搬送ロボットの問題が増えた"
slug: hospital-transport-robot-blocked-paths-more-conflicts
date: 2026-11-11
day: 488
meta_description: "病院の搬送ロボットを Unity で動かすシミュレーションで、通れなかった患者と職員41人を通すと、ロボットの問題は1日84件から132件に増えた。多くは、通り抜け用に開けた所へ職員がふだんも行くようになった所で起きた。"
tags: [サービスロボット導入シミュレーション, 搬送ロボット, 病院, 人の流れ, 車いす, Unity, シミュレーション]
series: "導入シミュレーション"
---

病院で検体や薬を運ぶ搬送ロボットを Unity で動かすシミュレーションで、それまで行き先へ歩いて行けなかった救急の患者・病棟から検査に来る患者・ほかの階の職員の41人を通すと、ロボットに起きる問題（急に止まった・ひやり・人がよけさせられたなど）は1日84件から132件に増えた。**増えた48件のうち37件は、患者に付き添ったり通り抜けたりするために職員が入れるようにした所へ、職員がふだんの行き来でも行くようになり、大廊下の東の端でロボットと会った分だった。** 同じ回で、待合の椅子と車いすの場所の色も、引きの絵で見分けられるように直した。

この回で良くなったこと、できるようになったことです。

- 病院のフロアで「行けない行き先」が0人になった。救急の患者18人・X線室へ行く病棟の患者17人・ほかの階の職員6人が、Unity の病院の中を歩くようになった。病棟の患者に付き添う看護師は、前は大廊下の端で1日74回立って待っていたが、91回とも検査の所の中で待つようになった
- 外来の大廊下に同時にいる人の数に、比べる相手の根拠ができた。これまで比べていた60人・20人は根拠のない仮置きで、この階の外来の人数から出し直すと、混む時間で2〜8人・午後で1〜3人だった。Unity の病院は混む時間で平均2.2人・午後で1.3人で、この幅に入った
- 待合の椅子（ベンチ）と車いすの場所が、引きの絵でも見分けられるようになった。椅子は薄いミントから濃いミント、車いすの場所は黄色の面から灰色の枠とバツにした。見た目だけの変更なので、見た目を出さない回の結果は前と1バイトも違わなかった
- 作業は依頼箱から Claude Code が回した。色の変更（依頼153）は38分、通れなかった人を通して1日を撮り直す回（依頼154）は197分。Unity を人が開いて手で直した所はない。前に同じことを手でやったことはないので、比べる時間はない

困っていたのは2つです。1つは、前の回（[LOG485](https://pixelstomotors.com/record/hospital-transport-robot-full-floor-people-flow)）で患者と職員を部屋に出入りさせたとき、だれがどこに入れるかの決まりが狭すぎて、救急の患者は玄関から救急の待合へ、病棟の患者は X線室へ歩いて行けず、Unity が7つの「決めてほしいこと」を出して止まっていたことです。もう1つは、立体の絵で待合の椅子と車いすの場所が見えにくかったことです。

> 立体図の色を変更したいので、いくつか提案してください（椅子の色と）車椅子のスペースが視認性良くない。

| 何を | なぜ | どうなったか |
|---|---|---|
| 椅子と車いすの場所の色を、案を並べて決めた | 薄いミントの椅子が床と同じ明るさで、車いすの場所の黄色が患者の黄色と同じだった | 椅子は濃いミント #4FAE8E、車いすの場所は灰色 #5F5E5A の枠とバツ（依頼153） |
| 大廊下の参考の人数を出し直した | 比べていた60人・20人に根拠がなかった | この階の外来の人数から、混む時間2〜8人・午後1〜3人 |
| だれがどこに入れるかを直した（サイト側） | 救急の患者・病棟の患者・付き添いの看護師・ほかの階の職員が、行き先へ歩いて行けなかった | 通り抜けだけの所と、付き添いで入る所を足した |
| 通れなかった41人を出して1日を撮り直した | 依頼149 で7つの「決めてほしいこと」が出て止まっていた | 行けない行き先は0人。問題は1日84件から132件に増えた（依頼154） |

【図解】LOG488_conflicts_by_area.html

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

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


## 作業ログ ― 待合の椅子と車いすの場所の色を、案を並べて決める（依頼153）

### 依頼箱と Claude Code は、Unity の作業を自動で回す仕組み

この回の Unity の作業は、どれも「依頼箱」で回しました。依頼箱は、Claude（チャットの側）が書いた依頼文を Mac のフォルダに置くと、Mac の見張り係が [Claude Code](https://www.anthropic.com/claude-code)（Mac の上でコードを書いて動かす Claude）に1件ずつ渡し、報告をフォルダに書き戻す仕組みです。Unity を人が開いて操作することはなく、終わると Mac に通知が出ます。依頼文と報告は、どれもこの記事の下に全文で載せています。

| 言葉 | この記事での意味 |
|---|---|
| 依頼箱 | Claude が書いた依頼を、Mac の見張り係が Claude Code に渡す仕組み（iCloud の 依頼箱/ フォルダ） |
| 依頼153・依頼154 | 依頼箱に置いた依頼の番号 |
| 見た目なしの回 | Unity で人やロボットの見た目を作らずに回す回。問題の数はこの回を正とする |
| check-in の番号（cs:123 など） | Unity の場面とコードを Unity Version Control に保存したときの番号 |

### 薄いミントの椅子と黄色の車いすの場所は、床と人に紛れていた

前の回（[LOG485](https://pixelstomotors.com/record/hospital-transport-robot-full-floor-people-flow)）で決めた色は「ミント×木目」です。待合の椅子は薄いミント（#9FD6CB）、車いすの場所は黄色（#F2C94C）の面でした。立体の絵にすると、椅子はクリーム色の床とほとんど同じ明るさで形が浮かず、車いすの場所は患者（黄色の丸）と同じ黄色で、人なのか場所なのかが分かりませんでした。

案は、チャットの中に小さな絵を描いて並べました（Unity で撮る前に方向を決めるため）。

| 案の組 | 中身 | 決めたこと |
|---|---|---|
| 椅子と車いすの場所だけ（3案） | A ミントを濃く・B 椅子を深緑で締める・C 車いすの場所を濃い青で主役に | A をもとにする |
| 床から見直す（4案） | 明るいグレー・木目に寄せる・ミントと木目を入れ替え・一段暗く | 床は今のクリームのまま |
| 車いすの場所の印（3案） | 淡い青の面＋白のバツ・青の枠＋青のバツ（面なし）・淡い青の面＋濃い青のバツ | 面を塗らない枠とバツ |
| 枠とバツの線（7案） | 青・灰色・深緑 × 細・中、青の破線 | 灰色・中 |

車いすの場所を黄色から外したのは、患者の黄色と重ねないためです。青は車いすの国際シンボルの色ですが、職員の濃い青と近くなるので、灰色にしました。灰色は壁と同じ系統で「床の区画」として収まり、ロボットと人のじゃまをしません。線の太さは、四角の1辺の約7.5% にしました。

### 依頼153：見た目だけを変え、結果が1バイト一致することを確かめた

色は Unity の HospitalLook.cs の、色Aをまとめて書いた表で変えました。車いすの場所は、黄色の面1枚を、枠4本とバツ2本の線に置き換えています。線は床から5mm だけ浮かせ、当たり判定を付けていません。

【画像】LOG488_01_colors153.png 左が依頼149（椅子 #9FD6CB・車いすの場所＝黄色の面）、右が依頼153（椅子 #4FAE8E・車いすの場所＝灰色の枠とバツ）。上から真上の全体・引き西半分・寄り総合受付と会計の待合・寄り中待合（10時ごろ）

- ねらい：引きの絵で、待合の椅子の並びと車いすの場所を、黄色の患者と混ぜずに見分けられるようにする
- 変わったこと：椅子 #9FD6CB → #4FAE8E、車いすの場所は黄色 #F2C94C の面 → 灰色 #5F5E5A の枠とバツ（線の太さ約11cm）。見た目なしの回の記録17個は、依頼149 と1バイト一致した
- 見込める効果：Unity の静止画と動画を、病院の人に見せたときに待合の形が一目で読める。数は変わらないので、前の回の結果とそのまま比べられる（見込み。見せた人の反応で確かめる）

Claude Code が目で見た結果は次のとおりです（直さずに書くだけ、と頼んだもの）。

| 見たこと | 結果 |
|---|---|
| 椅子が床から浮いて見えるか | 寄りでも引きでも浮いて見える |
| バツが引きで途切れたり消えたりしないか | 真上では12か所とも見える。引きの遠い所は細く明るめだが、消えてはいない |
| 黄色の人と見分けられるか | 見分けられる。車いすの人が印の上にいると、車いすの台と線が近い灰色で重なる |
| 照明で色が外れていないか | 色合いは狙いどおり。どちらも明るく出る（椅子 約 #60C4A0・線 約 #747068） |

Claude Code からの「決めてほしいこと」5つ（線は1.5m 四方なので約11cm・待合の床が1cm低いので約15mm 浮く・照明で明るく出る・車いすの人と線が重なる・h0 の長いすも同じ色になる）は、どれもそのままにしました。照明に合わせて色の値を追うと、ほかの物との釣り合いが崩れるためです。

#### 依頼153 の依頼文

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 120
---
# 依頼153：病院の待合の椅子の色と車いすの場所の印を変え、寄りと引きで確かめる

依頼149 の静止画を見て、エイイチと決めたこと（2026-11-11）：
- 待合の椅子（ベンチ）の薄いミントは、クリームの床とほとんど同じ明るさで見えにくい → **濃いミント #4FAE8E** にする
- 車いすの場所の黄色い四角は、黄色の人（患者）と区別がつかない → **黄色の面をやめ、灰色 #5F5E5A の枠とバツ（×）の線だけ**にする（面は塗らない。床の色が透ける）
  - 線の太さは、四角の1辺の約 7.5%（1辺 1.2 m なら約 9 cm）。枠とバツは同じ太さ・同じ色
  - 床から少しだけ浮かせて（5 mm ほど）ちらつかないようにする

**見た目だけの変更。** 床・壁・人・机・ベッド・ロボットの色、カメラ、人とロボットの動き、数える問題は変えない。
車いすの場所の印には当たり判定を付けず、NavMesh も変えない。
**配膳の場面・配膳の実行ファイル・配膳の台帳、配膳と共通の部品には触らない。** 変えるのは Assets/ServiceSim/Hospital/ の中だけ。
依頼149 の「決めてほしいこと」1〜7 はまだ決まっていないので、手を付けない。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、その項目は止めて報告に書き、ほかは続ける。**

## 始める前に確かめること（違えば回さずに報告して終わる）
作業場所の保留 0（cs:122 のあと）・Unity と回し係が動いていない・KIOXIA がマウント済み

## 1. 変える
- 椅子（ベンチ）と車いすの場所を表す物を場面とコードから全部探し、一覧（名前・置き場所・数）を報告に書く。色Aの決め方がまとめて書かれている所があれば、そこで変える
- 黄色を使っている物がほかにもあれば（人の色を除く）、変えずに一覧に書くだけにする
- check-in（保留0）して、Builds/hospital153/ を作る。前の実行ファイルは残す

## 2. 確かめ（違えば 3 は撮らずに止める）
- 見た目なしの回を、依頼149 と同じ条件（廊下だけ・0.6m/秒・種0）で回し、結果が依頼149 と1バイト一致すること（見た目だけの変更なので）
- 印に当たり判定が付いていないこと、NavMesh が変わっていないこと

## 3. 撮る（依頼149 と同じカメラの値。cams149.txt）
- 10:00 ごろの時刻で：真上の全体（top_all_10）、引き 西半分（w_west_10）、寄り 総合受付と会計の待合（n_wait_10）、寄り 中待合（n_midwait_10）
- 並べた図：左に依頼149 の同じ静止画、右に今回のもの（4段）
- 寄りと引きのそれぞれで、次を目で見て報告に書く（直さずに書くだけ）
  - 椅子が床から浮いて見えるか
  - 車いすの場所のバツが、引きで途切れたり消えたりしていないか。黄色の人と見分けられるか
  - 照明で色が狙い（#4FAE8E・#5F5E5A）から大きく外れて見えていないか
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital/20261111_look153/。並べた図と静止画は iCloud の …/サービスロボット導入シミュレーション/hospital/look153/ にも写し、md5 を確かめる

## 報告
claude_code_reports/20261111_hospital_153.txt に：始める前の確かめ、1 の一覧と変えた所、2 の確かめ、3 の目で見た結果、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：止めた（決めてほしいことあり）」／「判定：失敗」
```

#### 依頼153 の結果（依頼箱の結果の全文）

```markdown
# 結果：20261111_153_病院の待合の椅子の色と車いすの場所の印を変えて寄りと引きで確かめる

- 状態：完了
- 始めた時刻：2026-11-11 16:02
- 終わった時刻：2026-11-11 16:40（38分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 1557d8d9-3660-4257-a6ac-372bd0b4dc77`

## Claude Code の報告

**やったこと**
- 始める前の確かめはすべて合っていました。作業場所の保留は 0（cs:122 のあと）、Unity と回し係は動いておらず、KIOXIA はマウント済みです。
- 椅子（ベンチ）と車いすの場所を、場面とコードから探しました。
  - どちらも場面ファイルには置かれておらず、見た目ありのときにデータから作られます。
  - ベンチは 63 個です（総合受付・会計・待合 20／ほかの外来 12／お薬の窓口 9／生理検査 8／内科外来 6／外来化学療法室 4／救急室 4）。
  - 車いすの場所は 12 か所で、どれも約 1.5 m 四方です（総合受付の待合 4／外科・整形外科の中待合 3／ほかの5か所に 1 ずつ）。
- 色A の決め方がまとめて書かれている表（HospitalLook.cs）で変えました。
  - 椅子：#9FD6CB → #4FAE8E。
  - 車いすの場所：黄色の面をやめ、灰色 #5F5E5A の枠4本とバツ2本の線だけにしました。線の太さは短い辺の 7.5%、上の面は床から 5 mm 上で、当たり判定は付けていません。
- 人の色を除くと、本当の黄色はほかにありませんでした。黄色に近い物（いす・リクライニングチェア・杖・ロボットの前の印など）は変えずに報告に一覧で書きました。
- check-in は cs:123 で、そのあと保留 0 です。Builds/hospital153 を作り、前の実行ファイルは残しています。
- 確かめの2回（見た目なし・見た目あり）を回し、そのあと 10:00 ごろの静止画4枚と、依頼149 と並べた図（4段）を作りました。

**結果**
- 見た目なしの回は、依頼149 と記録 17 個が1バイト一致しました。見た目ありの回も同じく一致し、違うのは回の名前・番号・かかった時間だけです。
- 印に当たり判定は付いていません。NavMesh の焼き直しのログも依頼149 と同じで、動きの記録も一致したので、NavMesh は変わっていません。
- 撮る回は 8880 秒まで、流して書く記録が確かめの回と頭から一致しました。撮り終えたあと終わらずに止まったので強制終了しましたが、記録は書き終えていました。
- 目で見た結果（直していません）:
  - **椅子が浮いて見えるか**：寄りでも引きでも浮いて見えません。依頼149 と違い、床との差がはっきりしました。
  - **バツが引きで途切れたり消えたりしないか**：真上では 12 か所とも見えます。引き（西半分）の遠い所は線が細く明るめに見えますが、消えてはいません。
  - **黄色の人と見分けられるか**：見分けられます。ただし、車いすの人が印の上にいると、車いすの台と線が近い灰色で重なって見えます。
  - **照明で色が外れていないか**：色合いは狙いどおりですが、どちらも明るく出ています。椅子は約 #60C4A0（狙い #4FAE8E）、線は約 #747068（狙い #5F5E5A）です。
- 出力は KIOXIA の …/hospital/20261111_look153/ にあります。並べた図と静止画4枚は iCloud の …/hospital/look153/ にも写し、5つとも md5 が一致しました。
- 報告は claude_code_reports/20261111_hospital_153.txt に書き、KIOXIA にも写しを置きました。最後の行は「判定：合格」です。

**変えたファイル**
- Assets/ServiceSim/Hospital/HospitalLook.cs
- Assets/ServiceSim/Hospital/Editor/HospitalSetup.cs（実行ファイルの置き場所を hospital153 に変えただけ）

**決めてほしいこと**
1. 線の太さ：依頼は「1辺 1.2 m なら約 9 cm」でしたが、データの車いすの場所は約 1.5 m 四方なので、7.5% の決まりに合わせて約 11 cm にしました。これでよいですか。
2. 床から 5 mm は、廊下の床から測りました。待合の床は廊下より 1 cm 低いので、待合では約 15 mm 上に見えます。これでよいですか。
3. 照明で、椅子も線も狙いより明るく見えます。この見え方でよいですか。
4. 線の灰色が車いすの台の色に近く、車いすの人が印の上にいると重なって見えます。このままでよいですか。
5. 色A の表を変えたので、依頼139・140 の形（h0）の長いすも同じ濃いミントになります。これでよいですか。

依頼149 の「決めてほしいこと」1〜7 には手を付けていません。
```

#### report153.txt（依頼153 の報告の全文）

```text
依頼153：病院の待合の椅子の色と車いすの場所の印を変え、寄りと引きで確かめる（2026-11-11）

■ 始める前の確かめ（すべて合っていたので進めた）
- 作業場所の保留：0（cm status で変更なし。cs:122 - head）
- Unity（エディタ）・回し係（night_runner・rec_runner・HospitalSim）：動いていない（動いていたのは Unity Hub だけ）
- KIOXIA：/Volumes/KIOXIA にマウント済み

■ 1. 変えた所
（1）椅子（ベンチ）と車いすの場所を表す物の一覧
  場面ファイル（Hospital.unity）には置いていない。どちらも -h139Look on のとき HospitalLook.cs がデータから作る（当たりなし・NavMesh を焼いたあと）。
  ・ベンチ（Data/hospital_h1-149.json の interior.furniture、kind「ベンチ」、高さ 0.45 m、張り地の材質 cloth）　計 63
      総合受付・会計・待合 20／ほかの外来 12／お薬の窓口 9／生理検査 8／内科外来 6／外来化学療法室 4／救急室 4
      場面の物の名前：Look143_Interior の下の F{番号}_ベンチ
  ・車いすの場所（同じ表、kind「車いすの場所」、どれも約 1.5 m × 1.5 m）　計 12
      総合受付・会計・待合 4（総合受付の待合）／ほかの外来 3（外科・整形外科の中待合）／内科外来 1（内科の中待合）／
      外来化学療法室 1（化学療法の受付・待合）／お薬の窓口 1／生理検査 1／救急室 1（救急の待合）
      場面の物の名前：F{番号}_車いすの場所（今回から _FrameS・_FrameN・_FrameW・_FrameE・_X1・_X2 の6本ずつ、計 72 本）
  ・ほかに cloth（張り地）を使う物：h0（依頼139・140 の廊下だけの形）の長いす Bench000〜（BuildWaiting）。色A の表を変えたので、こちらも同じ濃いミントになる
  ・材質ファイル Materials/H139_BenchCloth.mat（色 #9FD6CB 相当）は動かすときに SetScheme で色を上書きするので、触っていない
（2）色A の決め方がまとめて書かれている所で変えた（Assets/ServiceSim/Hospital/HospitalLook.cs）
  ・SchemeHex の A の張り地：#9FD6CB → #4FAE8E
  ・KindHex の「車いすの場所」：#F2C94C → #5F5E5A
  ・印の作り：黄色の面1枚 → 枠4本＋バツ2本の線だけ（面は塗らない）。新しい WcMark（Part／BoxXZ を使うので Collider は付かない）
      線の太さ＝四角の短い辺の 7.5%（WcLineFrac 0.075。1.5 m なので約 11 cm、1.49 m の所は約 11.2 cm）。枠とバツは同じ太さ・同じ材質
      バツは対角線に沿った2本で、線の角が四角の外へ出ない長さにした
      上の面＝床（廊下の床の上面）から 5 mm（WcMarkH 0.01 → 0.005）。下の面はもとのまま床より 2 cm 下
  ・Editor/HospitalSetup.cs：実行ファイルの置き場所を Builds/hospital153 に（前の hospital127〜149 は残っている）
（3）黄色を使っているほかの物（人の色を除く。変えていない）
  ・本当の黄色はほかに無い。黄色に近い物だけ書く：
      いす #C9D98A（若草）／リクライニングチェア #F0B27A（橙）／杖の人の杖 H127_Cane（橙 0.95,0.45,0.15。人の見た目の一部）／
      ロボットの前の印 H139_RobotFront（橙 #FF5409 相当）／色C の張り地 #B3CC6E（黄緑。使っていない案）／
      待合の床 #E4CFAA・木目 #D7B88C・ベッド #E9E4D8（黄みのクリーム）
  ・人の色（変えていない）：H139_Guest（患者・付き添い）、H127_Patient
（4）check-in と実行ファイル
  ・cs:123（HospitalLook.cs・Editor/HospitalSetup.cs の2つ）。check-in のあと保留 0
  ・Builds/hospital153/HospitalSim.app（build_info.json：unity_cs ServiceSim/cs:123、pending_changes 空）

■ 2. 確かめ（合格）
  ・見た目なし check153a（廊下だけ・0.6m/秒・種0・人の動き flow、依頼149 の check149a と同じ引数）：
      記録 17 個（access147・build143・crowd・elevator149・events・legs・moves・people147・rescues・robot_track・robot_zones・
      seats147・staff147・staff_moves147・toilet149・trips・zones147）が check149a と1バイト一致。summary.json の違いは run・unity_cs・wall_s だけ
  ・見た目あり（印あり・カメラなし）check153L：check149L とも check149a とも同じ 17 個が1バイト一致（同じく run・unity_cs・wall_s だけ違う）
  ・印に当たり判定が無いこと：印は Part（MeshFilter と MeshRenderer だけを付ける）で作り、Collider を付けるコードは HospitalLook.cs に無い。
      見た目は NavMesh を焼いたあと（焼き直し AfterBake147 のあと）に作り、焼くときに集めるのは PhysicsColliders だけ
  ・NavMesh が変わっていないこと：焼き直しのログ（access の所 53 枚・区画 28・壁の箱 150・家具の箱 78）が依頼149 と同じ行。
      人とロボットの動きの記録が見た目ありでも1バイト一致なので、通れる所は変わっていない
  ・撮る回 rec153 は check153L と、流して書く記録（crowd・elevator149・events・moves・robot_track・staff_moves147・toilet149・zones147）が
      撮り終えた 8880 秒まで頭からバイト一致（依頼149 の rec149 と同じ形）。撮り終えたあと終わらずに止まったので kill -9 で止めた（記録は書き終えていた）

■ 3. 目で見た結果（直していない。カメラは cams149.txt と同じ値、時刻 9時58分00秒＝8880 秒）
  画素の色（JPG を4段階に丸めて多い順。床はどれも #FCF4E0）：
    ベンチの上面 #60C4A0〜#64C8A0（狙い #4FAE8E）、印の線 #747068 前後（狙い #5F5E5A）
    参考：依頼149 のベンチは #B8F4E0（狙い #9FD6CB）
  ・椅子が床から浮いて見えるか
      寄り（総合受付と会計の待合・中待合）：浮いて見えない。手前の面が濃い緑の陰になり、足元に影が続いている
      引き（西半分）・真上：浮いて見えない。影はベンチの南側に付くが離れていない。床との差ははっきりした（依頼149 はほぼ同じ明るさだった）
  ・車いすの場所のバツが、引きで途切れたり消えたりしていないか
      真上の全体：12 か所とも枠とバツが見える（1か所 30 画素ほど）。途切れていない
      引き 西半分：枠とバツは見えるが、遠い所は線が細く明るめに見える。消えてはいない
      寄り：太さがそろってはっきり見える
  ・黄色の人と見分けられるか
      見分けられる（灰色の線だけで黄色の面が無いので、黄色の人と重ならない）
      ただし、車いすの人が印の上にいると、車いすの台（濃い灰色）と印の線が近い色で重なって見える（真上・寄り 総合受付の待合で1か所）
  ・照明で色が狙いから大きく外れて見えていないか
      ベンチ：色合い（緑み）は狙いどおり。明るさは狙いより 1〜2 割明るい（#4FAE8E → #60C4A0 前後）。日の光と環境光のため
      印の線：灰色のまま。狙いより明るい（#5F5E5A → #747068 前後）。引きの遠い所はさらに明るく見える

■ 出力の場所
  KIOXIA：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital/20261111_look153/
    runs/（check153a・check153L・rec153）、stills/（still153_*.png 4枚）、sheets/sheet153_cmp_10.png（並べた図・4段・左＝依頼149・右＝今回）、
    crops/（目で見るための切り抜き）、cmp153_out.txt・cmp153_rec.txt（比べた結果）、colors153.txt（画素の色）、md5_icloud153.txt、
    cams153.txt・camspath153*.csv、logs/、tools/（run153.sh・rec153.sh・pan153.py・sheet153.py と、依頼149 から写した pan140・cams140・cams143・cams149・camfit139・cmp149）
  iCloud：…/サービスロボット導入シミュレーション/hospital/look153/
    sheet153_cmp_10.png 70088352dcb7fbe9a66da5b4124e60f8
    still153_top_all_10.png ecb4084082d6d31143e346d302dc4eef
    still153_w_west_10.png 9232a2a1b2f265dad7d65703fe1b1754
    still153_n_wait_10.png 10a1246daa9b94e66b639b5daaa40d76
    still153_n_midwait_10.png 678d87528ba7d24a290398b01ee01eab
    （5つとも KIOXIA と md5 一致）

■ check-in の番号
  cs:123

■ 決めてほしいこと
  1. 線の太さ：依頼は「1辺 1.2 m なら約 9 cm」だが、データの車いすの場所はどれも約 1.5 m 四方。決まり（1辺の 7.5%）に合わせて約 11 cm にした。これでよいか
  2. 「床から 5 mm」は、印の上の面を廊下の床の上面から 5 mm にした。待合の床は廊下の床より 1 cm 低いので、待合では床から約 15 mm 上に見える。これでよいか
  3. 照明で、ベンチは狙いより明るく（#60C4A0 前後）、印の線も明るく（#747068 前後）見える。この見え方でよいか
  4. 印の線の灰色が、車いすの台の濃い灰色に近く、車いすの人が印の上にいると重なって見える。このままでよいか
  5. 色A の表を変えたので、h0（依頼139・140 の形）の長いすも同じ濃いミントになる。これでよいか
  （依頼149 の「決めてほしいこと」1〜7 には手を付けていない）

判定：合格
```

#### colors153.txt（静止画から測った椅子と線の色）

```text
top_all_10 緑み 多い順 [('#60C4A0', 7404), ('#60C8A0', 4760), ('#64C8A0', 4492)] ／灰色み [('#646058', 2620647), ('#64605C', 16637), ('#70706C', 10162)] ／床 [('#FCF4E0', 1762905)]
w_west_10 緑み 多い順 [('#64C4A0', 12616), ('#60C8A0', 11118), ('#60C4A0', 9401)] ／灰色み [('#646058', 1212650), ('#6C6C6C', 8931), ('#64605C', 5925)] ／床 [('#FCF4E0', 2469619)]
n_wait_10 緑み 多い順 [('#60C4A0', 31060), ('#60C8A0', 18792), ('#64C4A0', 18751)] ／灰色み [('#646058', 173558), ('#686868', 55685), ('#6C6C6C', 17061)] ／床 [('#FCF4E0', 3398075)]
n_midwait_10 緑み 多い順 [('#64C8A0', 66576), ('#60C4A0', 30403), ('#60C8A0', 15096)] ／灰色み [('#686868', 59057), ('#6C6C6C', 11520), ('#747068', 6965)] ／床 [('#FCF4E0', 3810783)]
寄り 中待合 n_midwait_10 (2480, 1500, 2720, 1830) 印の線（灰色の画素 多い順） [('#747068', 5016), ('#74706C', 2294), ('#747064', 1501)]
真上 top_all_10 (890, 1010, 975, 1050) 印の線（灰色の画素 多い順） [('#747064', 71), ('#747068', 62), ('#787064', 56)]
参考 依頼149 中待合 椅子（狙い #9FD6CB）多い順 ['#B8F4E0', '#B8F4E4', '#B8F0E0']
```

#### cmp153_out.txt（見た目なしの回を依頼149 と比べた結果）

```text
== check153a vs check149a
同じ 17：access147.csv・build143.csv・crowd.csv・elevator149.csv・events.csv・legs.csv・moves.csv・people147.csv・rescues.csv・robot_track.csv・robot_zones.csv・seats147.csv・staff147.csv・staff_moves147.csv・toilet149.csv・trips.csv・zones147.csv
違う 0：
summary.json の違う項目： run, unity_cs, wall_s
== check153L vs check149L
同じ 17：access147.csv・build143.csv・crowd.csv・elevator149.csv・events.csv・legs.csv・moves.csv・people147.csv・rescues.csv・robot_track.csv・robot_zones.csv・seats147.csv・staff147.csv・staff_moves147.csv・toilet149.csv・trips.csv・zones147.csv
違う 0：
summary.json の違う項目： run, unity_cs, wall_s
== check153L vs check149a
同じ 17：access147.csv・build143.csv・crowd.csv・elevator149.csv・events.csv・legs.csv・moves.csv・people147.csv・rescues.csv・robot_track.csv・robot_zones.csv・seats147.csv・staff147.csv・staff_moves147.csv・toilet149.csv・trips.csv・zones147.csv
違う 0：
summary.json の違う項目： run, unity_cs, wall_s
```

#### cmp153_rec.txt（撮る回の記録を確かめの回と比べた結果）

```text
同じ 4：access147.csv・build143.csv・rescues.csv・staff147.csv
違う 13：crowd.csv（頭から 2552 / 2552・9259 バイト一致・短い方は全部一致）・elevator149.csv（頭から 12447 / 12447・59747 バイト一致・短い方は全部一致）・events.csv（頭から 3565 / 3565・20089 バイト一致・短い方は全部一致）・legs.csv（頭から 1152 / 1250・7010 バイト一致）・moves.csv（頭から 190 / 190・786 バイト一致・短い方は全部一致）・people147.csv（頭から 183 / 206017・271206 バイト一致）・robot_track.csv（頭から 512267 / 512267・1992149 バイト一致・短い方は全部一致）・robot_zones.csv（頭から 70 / 4561・4589 バイト一致）・seats147.csv（頭から 115 / 41025・41049 バイト一致）・staff_moves147.csv（頭から 43520 / 43520・155214 バイト一致・短い方は全部一致）・toilet149.csv（頭から 8121 / 8121・22906 バイト一致・短い方は全部一致）・trips.csv（頭から 476 / 2455・2499 バイト一致）・zones147.csv（頭から 192480 / 192480・716594 バイト一致・短い方は全部一致）
summary.json の違う項目： braking, crowd_max, flow_elevator, flow_escort_calls, flow_escorts, flow_exited, flow_inside_at_end, flow_near_stops, flow_not_spawned, flow_nurse_calls, flow_skip_notes, flow_skips, flow_spawn_waits, flow_spawned, flow_staff_moves, flow_toilet_extend_n, flow_toilet_extend_s, flow_toilet_not_due, flow_toilet_trips, flow_ward_nurse_hall_waits, frames, last_trip_end_s, legs_done, legs_total, moves, moves_done, n1_sudden_stop, n2_fast_near_miss, n2_near_miss, n2_near_robot_moving, n3_people_yield, n6_overlap_moving, near_min_m, person_max_step_m, run, run_corridor_n1, run_corridor_n2, run_corridor_n3, run_end_s, run_room_n1, run_room_n2, run_room_n3, run_room_n6, sim_s, status, stretchers, trips_done, trips_not_run_after_end, trips_unfinished_at_end, wait_n2_near_miss, wait_n2_near_robot_stopped, wait_n3_people_yield, wall_s
```

#### cams153.txt（静止画のカメラの値）

```text
top_all_10,0,90,60.00,30.00,142.8,40,2720,2160
w_west_10,30,75,34.06,21.26,85.8,40,2720,2160
n_midwait_10,30,75,31.80,35.73,32.4,40,2720,2160
n_wait_10,30,75,46.58,20.95,54.6,40,2720,2160
```

#### run153.sh（確かめの回を回す）

```bash
#!/bin/bash
# 依頼153：確かめの回（依頼149 の check149a・check149L と同じ条件。h1・人の動き flow・廊下だけ・0.6m/秒・種0）
#   check153a＝見た目なし（check149a と1バイト一致か）、check153L＝見た目あり・カメラなし・車いすの場所の印あり（check149a・check149L と一致か）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim
O=/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital/20261111_look153
EXE=Builds/hospital153/HospitalSim.app/Contents/MacOS/ServiceSim
CS=$(python3 -c "import json;print(json.load(open('Builds/hospital153/build_info.json'))['unity_cs'])")
run() { L=$1; shift; $EXE -batchmode -nographics -h143Layout h1 -h127Speed 0.6 -h127Seed 0 -h127Out $O/runs -h127Cs $CS -h136Mode day -h138Mode 138 -h127Label $L -h127MaxWallMin 200 "$@" -logFile $O/logs/$L.log; echo "$L exit $?" >> $O/logs/run153.exit; }
run check153a -h147People flow -h136Plan hall &
run check153L -h147People flow -h136Plan hall -h139Look on -h140Scheme A -h143Wheelchair on &
wait
echo done >> $O/logs/run153.exit
```

#### rec153.sh（撮る回を回す）

```bash
#!/bin/bash
# 依頼153：撮る回（依頼149 の rec149 と同じ条件：h1・人の動き flow・廊下だけ・0.6m/秒・種0・色A・車いすの場所の印あり）。
#   カメラは cams149.txt の10時の4つ（top_all_10・w_west_10・n_midwait_10・n_wait_10）だけ。8880 秒で撮り終えたら止める
cd /Users/shimazueiichi/Robotics_UX/ServiceSim
O=/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital/20261111_look153
EXE=Builds/hospital153/HospitalSim.app/Contents/MacOS/ServiceSim
CS=$(python3 -c "import json;print(json.load(open('Builds/hospital153/build_info.json'))['unity_cs'])")
CAMS=()
while read -r c; do CAMS+=(-h139Cam "$c"); done < $O/cams153.txt
$EXE -batchmode -h143Layout h1 -h147People flow -h143Wheelchair on -h127Speed 0.6 -h127Seed 0 -h127Out $O/runs -h127Cs $CS -h136Mode day -h136Plan hall -h138Mode 138 -h139Look on -h140Scheme A \
  -h127Label rec153 "${CAMS[@]}" -h141CamPath $O/camspath153.csv -h139EndAfterShots on -h127MaxWallMin 200 -logFile $O/logs/rec153.log
echo "rec exit $?" > $O/logs/rec153.exit
```

#### pan153.py（静止画に時刻と問題の数のパネルを付ける）

```python
#!/usr/bin/env python3
"""依頼153（pan149.py の写し：読む表を camspath153_*.csv に、静止画は still153_*.png で 10時の4つだけ）。もとの説明：依頼149（pan147.py の写しを直した：カメラは cams149、静止画は6つ×2つの時刻、エレベーター・トイレ）。もとの説明：依頼147（pan143.py の写しを直した：カメラは cams147、静止画7枚、題に人の動き）。もとの説明：依頼143：h1 の静止画（8枚）と1日の早回しの動画に、輪・パネル・凡例・上の帯を付ける（1920×1080）。依頼141 の pan141.py と同じ作り
（輪・凡例・パネルの形は依頼140 の pan140.py）。違うのは：病院 h1、ロボットの状態（台の前で待っている・積む所へ移る）、
行き先の名前を短く、道（緑・青（表）・青（裏口）・赤）、引きは西半分と東半分、問題を 廊下／部屋の中 に分けて帯に出す。

  python3 pan143.py stills <回のフォルダ> <出力フォルダ>
  python3 pan143.py video <回のフォルダ> flowday <出力.mp4> [並べる数]
  python3 pan143.py frame <回のフォルダ> flowday <コマ番号> <出力.png>
"""
import csv, json, math, os, subprocess, sys, tempfile
from multiprocessing import Pool
from PIL import Image, ImageDraw
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import pan140 as P
from cams149 import cams as cams143, still_cams

W, H, SW = P.W, P.H, P.SW
SHORT = {"救急室の処置室": "救急室", "内科外来の処置室": "内科（表）", "内科外来の処置室（裏口）": "内科（裏口）", "薬剤部の払い出し口": "薬剤部",
         "検査部の検体受付": "検体受付", "外来化学療法室の受付": "化学療法室"}
ROUTE_JA = {"green": "緑", "blue": "青（表）", "blue_back": "青（裏口）", "red": "赤"}
STATE = [("待ち", "台の前で待っている"), ("行き", "行き"), ("帰り", "帰り"), ("移る", "積む所へ移る")]
CAMJA = {c[0]: c[6] + ("" if c[0] == "top_all" else "・75°見下ろし・左30°") for c in cams143()}
CAMJA.update({n: CAMJA[c[0]] for n, c, tk in still_cams()})
RUN = None
DATA = {}


class PathCam:
    def __init__(self, proj, row, w, h, name):
        self.name = name
        self.w, self.h = w, h
        self.p = proj
        self.v = [[float(row["v%d%d" % (r, k)]) for k in range(4)] for r in range(4)]
        self.sx, self.sy = SW / w, H / h

    px = P.Cam.px
    ppm = P.Cam.ppm


def counts(ev, t0, t):
    c = {k: 0 for k in range(1, 7)}
    c["fast"] = c["wait2"] = c["room"] = 0
    for e in ev:
        te = float(e["t_s"])
        if te < t0 - 1e-6 or te > t + 1e-6: continue
        k = int(e["type"])
        if e["dir"] == "待ち":
            if k == 2: c["wait2"] += 1
            continue
        c[k] += 1
        if e.get("area") == "部屋の中" and k <= 5: c["room"] += 1
        if k == 2 and e.get("note") == "速いまま": c["fast"] += 1
    return c


def panel(im, row, ev, t, t0, title0, camja, summ, wide):
    d = ImageDraw.Draw(im)
    d.rectangle([SW, 0, W, H], fill=P.PANEL_BG)
    x0, xr = SW + 40, W - 40
    f_s, f_m, f_mb, f_big, f_num = P.font(False, 24), P.font(False, 27), P.font(True, 29), P.font(True, 54), P.font(True, 46)
    d.text((x0, 24), "病院 h1・人の動き・%s・%.1fm/秒・種%d" % (summ["plan"], summ["speed_mps"], summ["seed"]), font=f_s, fill=P.GRAY)
    d.text((x0, 56), "色 %s・車いすの場所 %s" % (P.SCHEME_JA["A"], "あり" if summ.get("wheelchair_marks") == "on" else "なし"), font=f_s, fill=P.GRAY)
    d.text((x0, 88), "カメラ %s" % camja, font=P.font(False, 22 if len(camja) > 18 else 24), fill=P.GRAY)
    d.text((x0, 122), P.clock(t), font=f_big, fill=P.INK)
    y = 206
    d.text((x0, y), "ロボット・スタッフがしていること", font=f_mb, fill=P.INK)
    y += 52
    d.text((x0, y), "ロボット 1台", font=f_m, fill=P.INK)
    y += 44
    cur = row["leg_dir"]
    for key, ja in STATE:
        on = key == cur
        d.text((x0 + 8, y), ja, font=f_m, fill=P.INK if on else (150, 150, 148))
        if on:
            d.ellipse([xr - 40, y + 2, xr - 4, y + 38], fill=P.WHITE, outline=(80, 80, 80), width=3)
            if key in ("行き", "帰り", "移る") and row["leg_from"]:
                d.text((xr - 52, y + 20), "%s→%s" % (SHORT.get(row["leg_from"], row["leg_from"]), SHORT.get(row["leg_to"], row["leg_to"])), font=f_s, fill=P.GRAY, anchor="rm")
            elif key == "待ち" and row["leg_from"]:
                d.text((xr - 52, y + 20), SHORT.get(row["leg_from"], row["leg_from"]), font=f_s, fill=P.GRAY, anchor="rm")
        y += 40
    y += 10
    d.text((x0, y), "スタッフ", font=f_m, fill=P.INK)
    y += 44
    rs = int(row["rescue_staff"])
    d.text((x0 + 8, y), "ロボットの助け", font=f_m, fill=P.INK if rs else (150, 150, 148))
    for i in range(rs):
        d.rectangle([xr - 36 - i * 44, y + 3, xr - 4 - i * 44, y + 35], fill=P.BLUE)
    y += 52
    d.line([x0, y, xr, y], fill=P.LINE, width=2)
    y += 14
    d.text((x0, y + 8), "大廊下の人（x0〜70）", font=f_m, fill=P.INK)
    d.text((xr, y + 28), row["hall_people"], font=f_num, fill=P.INK, anchor="rm")
    y += 66
    d.line([x0, y, xr, y], fill=P.LINE, width=2)
    y += 14
    d.text((x0, y), title0, font=f_mb, fill=P.INK)
    y += 46
    c = counts(ev, t0, t)
    for k in range(1, 6):
        d.ellipse([x0 + 6, y + 6, x0 + 30, y + 30], outline=P.COLORS[k], width=4)
        d.text((x0 + 44, y + 18), "%d %s" % (k, P.TYPES[k]), font=f_m, fill=P.INK, anchor="lm")
        d.text((xr, y + 18), str(c[k]), font=P.font(True, 34), fill=P.INK, anchor="rm")
        y += 42
    d.ellipse([x0 + 6, y + 6, x0 + 30, y + 30], outline=P.COLORS[2], width=4)
    d.text((x0 + 44, y + 18), "台の前で待っている間のひやり", font=f_s, fill=P.INK, anchor="lm")
    d.text((xr, y + 18), str(c["wait2"]), font=P.font(True, 34), fill=P.INK, anchor="rm")
    y += 48
    d.rectangle([x0 + 9, y + 5, x0 + 27, y + 23], outline=P.FAST_COL, width=3)
    d.rectangle([x0 + 4, y, x0 + 32, y + 28], outline=P.FAST_COL, width=2)
    d.text((x0 + 44, y + 14), "うち速いままのひやり（走っている間） %d" % c["fast"], font=f_s, fill=P.INK, anchor="lm")
    y += 36
    d.rectangle([x0 + 5, y + 1, x0 + 31, y + 27], outline=(255, 210, 0), width=4)
    d.line([x0 + 9, y + 5, x0 + 27, y + 23], fill=P.OVER_COL, width=5)
    d.line([x0 + 9, y + 23, x0 + 27, y + 5], fill=P.OVER_COL, width=5)
    d.text((x0 + 44, y + 14), "止まりきれなかった %d" % c[6], font=f_s, fill=P.INK, anchor="lm")
    y += 38
    note = "輪＝問題の起きた所（10秒）" + ("・白い輪＝ロボット" if wide else "")
    d.text((x0, min(y + 2, H - 34)), note, font=P.font(False, 21), fill=P.GRAY)
    return c


def legend_bottom(im):
    box = im.crop((0, H - 104, SW, H))
    P.legend(box)
    im.paste(box, (0, H - 104))


def load(run, cam):
    rows = [r for r in csv.DictReader(open(os.path.join(run, "look139", "look_frames.csv"), encoding="utf-8")) if r["kind"] == "path" and r["cam"] == cam]
    paths = [r for r in csv.DictReader(open(os.path.join(run, "look139", "look_path.csv"), encoding="utf-8")) if r["cam"] == cam]
    base = os.path.dirname(os.path.dirname(run))
    meta = [r for r in csv.DictReader(open(os.path.join(base, "camspath153_meta.csv"), encoding="utf-8")) if r["cam"] == cam]
    assert len(rows) == len(paths), (len(rows), len(paths))
    if cam == "flowday":
        assert len(rows) == len(meta), (len(rows), len(meta))
        for a, b in zip(rows, meta):
            assert abs(float(a["t_s"]) - float(b["t_s"])) < 0.051, (a["t_s"], b["t_s"])
    j = json.load(open(os.path.join(run, "look139", "cam_%s.json" % cam), encoding="utf-8"))
    ev = list(csv.DictReader(open(os.path.join(run, "events.csv"), encoding="utf-8")))
    summ = json.load(open(os.path.join(run, "summary.json"), encoding="utf-8"))
    trips = {r["trip_no"]: r for r in csv.DictReader(open(os.path.join(run, "trips.csv"), encoding="utf-8"))}
    return dict(rows=rows, paths=paths, meta=meta, proj=j["projectionMatrix"], w=j["width"], h=j["height"], ev=ev, summ=summ, cam=cam, trips=trips)


def compose(i):
    D = DATA
    r, pr = D["rows"][i], D["paths"][i]
    t = float(r["t_s"])
    src = Image.open(os.path.join(RUN, "look139", r["file"])).convert("RGB").resize((SW, H), Image.LANCZOS)
    im = Image.new("RGB", (W, H), P.PANEL_BG)
    im.paste(src, (0, 0))
    m = D["meta"][i]
    a = float(m["near_a"])
    wide = a < 0.5
    cam = PathCam(D["proj"], pr, D["w"], D["h"], "w_" if wide else "n_")
    P.WIDE.add("w_")
    P.overlay(im, cam, D["ev"], t, P.font(True, 22), (float(r["robot_x"]), float(r["robot_z"])))
    legend_bottom(im)
    sp = m["speed"]
    trip = r["trip_no"] if r["trip_no"] not in ("", "0") else ""
    sd = "西半分" if m["side"] == "west" else "東半分"
    camja = ("引き・%s・75°見下ろし・左30°" % sd) if a <= 0 else ("寄り・75°見下ろし・左30°・付いて行く" if a >= 1 else "引き⇄寄りに切り替え中")
    c = panel(im, r, D["ev"], t, 0.0, "7時30分からの1日の問題", camja, D["summ"], wide)
    route = ""
    if trip and trip in D["trips"]:
        route = "　%s便目・%s（%s→%s）" % (trip, ROUTE_JA.get(D["trips"][trip]["route"], ""), SHORT.get(D["trips"][trip]["from"], ""), SHORT.get(D["trips"][trip]["to"], ""))
    elif r["leg_dir"] == "移る":
        route = "　積む所へ移る（%s→%s）" % (SHORT.get(r["leg_from"], r["leg_from"]), SHORT.get(r["leg_to"], r["leg_to"]))
    t1 = "%s　店の時刻の%s%s" % (P.clock(t), sp, route if sp == "5倍" else "")
    t2 = "1日の問題（走っている間）　1 急に止まった %d・2 ひやり %d・3 人がよけさせられた %d・4 立ち往生 %d・5 横切り %d（うち部屋の中 %d）" % (
        c[1], c[2], c[3], c[4], c[5], c["room"])
    P_banner(im, t1, t2, "台の前で待っている間のひやり %d" % c["wait2"])
    return im


def P_banner(im, text1, text2, text3=""):
    lay = Image.new("RGBA", (SW, 140 if text3 else 104), (249, 247, 244, 225))
    d = ImageDraw.Draw(lay)
    d.text((24, 31), text1, font=P.font(True, 34 if len(text1) < 40 else 30), fill=P.INK, anchor="lm")
    d.text((24, 80), text2, font=P.font(False, 22), fill=P.INK, anchor="lm")
    if text3:
        d.text((24, 116), text3, font=P.font(False, 22), fill=P.INK, anchor="lm")
    im.paste(lay, (0, 0), lay)


def still(run, cam, out, what):
    D = load(run, cam)
    assert len(D["rows"]) == 1, (cam, len(D["rows"]))
    r, pr = D["rows"][0], D["paths"][0]
    t = float(r["t_s"])
    src = Image.open(os.path.join(run, "look139", r["file"])).convert("RGB").resize((SW, H), Image.LANCZOS)
    im = Image.new("RGB", (W, H), P.PANEL_BG)
    im.paste(src, (0, 0))
    wide = not cam.startswith("n_")
    pc = PathCam(D["proj"], pr, D["w"], D["h"], "w_" if wide else "n_")
    P.WIDE.add("w_")
    P.overlay(im, pc, D["ev"], t, P.font(True, 22), (float(r["robot_x"]), float(r["robot_z"])))
    legend_bottom(im)
    panel(im, r, D["ev"], t, 0.0, "7時30分からの1日の問題", CAMJA[cam], D["summ"], wide)
    P_banner(im, "%s　%s" % (P.clock(t), CAMJA[cam].split("・")[0] + "・" + CAMJA[cam].split("・")[1] if "・" in CAMJA[cam] else CAMJA[cam]), what)
    f = os.path.join(out, "still153_%s.png" % cam)
    im.save(f)
    return f, t


def init(run, cam):
    global RUN, DATA
    RUN = run
    DATA = load(run, cam)


def seg(args):
    run, cam, lo, hi, dst = args
    init(run, cam)
    p = subprocess.Popen([P.FFMPEG, "-y", "-loglevel", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", "%dx%d" % (W, H), "-framerate", "25",
                          "-i", "-", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-crf", "18", "-g", "50", dst], stdin=subprocess.PIPE)
    for i in range(lo, hi):
        p.stdin.write(compose(i).tobytes())
    p.stdin.close()
    p.wait()
    return dst


def main():
    mode, run = sys.argv[1], sys.argv[2]
    if mode == "stills":
        out = sys.argv[3]
        os.makedirs(out, exist_ok=True)
        base = os.path.dirname(os.path.dirname(run))
        whats = {r["cam"]: r["what"] for r in csv.DictReader(open(os.path.join(base, "camspath153_stills.csv"), encoding="utf-8"))}
        for c, _, _ in still_cams():
            if c not in whats: continue
            print(*still(run, c, out, whats[c]))
        return
    cam = sys.argv[3]
    if mode == "frame":
        init(run, cam)
        compose(int(sys.argv[4])).save(sys.argv[5])
        print(sys.argv[5], DATA["rows"][int(sys.argv[4])]["t_s"])
        return
    dst = sys.argv[4]
    nproc = int(sys.argv[5]) if len(sys.argv) > 5 else 8
    n = len(load(run, cam)["rows"])
    tmp = tempfile.mkdtemp(prefix="pan149_", dir=os.path.dirname(os.path.abspath(dst)))
    k = max(1, math.ceil(n / (nproc * 4)))
    jobs = [(run, cam, lo, min(n, lo + k), os.path.join(tmp, "s%04d.mp4" % (lo // k))) for lo in range(0, n, k)]
    with Pool(nproc) as pool:
        parts = pool.map(seg, jobs, chunksize=1)
    lst = os.path.join(tmp, "list.txt")
    open(lst, "w").write("".join("file '%s'\n" % q for q in parts))
    subprocess.check_call([P.FFMPEG, "-y", "-loglevel", "error", "-f", "concat", "-safe", "0", "-i", lst, "-c", "copy", "-movflags", "+faststart", dst])
    for q in parts: os.remove(q)
    os.remove(lst); os.rmdir(tmp)
    print(dst, n, "コマ", n / 25.0, "秒")


if __name__ == "__main__":
    main()
```

#### sheet153.py（依頼149 と依頼153 の静止画を並べる）

```python
#!/usr/bin/env python3
"""依頼153：並べた図（4段。左＝依頼149 の静止画、右＝依頼153 の静止画。10時ごろの 真上の全体・引き 西半分・寄り 総合受付と会計の待合・寄り 中待合）
  python3 sheet153.py <依頼149 の静止画のフォルダ> <依頼153 の静止画のフォルダ> <camspath153_stills.csv> <出力.png>"""
import csv, os, sys
from PIL import Image, ImageDraw
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import pan140 as P
from cams149 import cams, still_cams

old, new, stills, dst = sys.argv[1:5]
what = {r["cam"]: (float(r["t_s"]), r["what"]) for r in csv.DictReader(open(stills, encoding="utf-8"))}
ja = {c[0]: c[6] for c in cams()}
base = {n: c[0] for n, c, tk in still_cams()}
order = ["top_all_10", "w_west_10", "n_wait_10", "n_midwait_10"]
tw, th, cap = 1280, 720, 64
sheet = Image.new("RGB", (tw * 2 + 30, (th + cap) * len(order) + 130), (255, 255, 255))
d = ImageDraw.Draw(sheet)
d.text((10, 34), "依頼153　待合の椅子の色と車いすの場所の印（病院 h1・人の動き・廊下だけ・0.6m/秒・種0・色A）",
       font=P.font(True, 30), fill=P.INK, anchor="lm")
d.text((10 + tw // 2, 90), "左：依頼149（椅子 #9FD6CB・車いすの場所＝黄色 #F2C94C の面）", font=P.font(True, 26), fill=P.INK, anchor="mm")
d.text((20 + tw + tw // 2, 90), "右：依頼153（椅子 #4FAE8E・車いすの場所＝灰色 #5F5E5A の枠とバツ）", font=P.font(True, 26), fill=P.INK, anchor="mm")
mark = "①②③④"
for k, n in enumerate(order):
    y = 110 + k * (th + cap)
    for j, (folder, pre) in enumerate([(old, "still149_"), (new, "still153_")]):
        im = Image.open(os.path.join(folder, pre + n + ".png")).convert("RGB").resize((tw, th), Image.LANCZOS)
        sheet.paste(im, (10 + j * (tw + 10), y))
    t, w = what[n]
    d.text((14, y + th + 22), "%s　%s" % (mark[k], ja[base[n]]), font=P.font(True, 24), fill=P.INK, anchor="lm")
    d.text((14, y + th + 50), "%s（%.2f 秒）　%s" % (P.clock(t), t, w), font=P.font(False, 20), fill=P.GRAY, anchor="lm")
sheet.save(dst)
print(dst, sheet.size)
```


## 作業ログ ― 依頼149 で止まった7つを決め、だれがどこに入れるかを直す（サイト側）

### 依頼149 の「決めてほしいこと」7つと、決めた答え

依頼149 は、エレベーターとトイレを足して人の行き来を増やす回でした。報告まで済みましたが、Unity が決めてはいけないことに当たり、7つを書いて止まっていました。答えは次のとおりです。

| 番号 | 依頼149 で止まったこと | 決めた答え |
|---|---|---|
| 1 | フロアの形に、依頼に書いていない違いが3つあった | このままでよい（形を直したときに一緒に変わった所で、md5 も一致） |
| 2 | 救急の患者が、正面玄関から救急の待合へ歩いて行けない | 正面玄関の前の職員の中廊下を横切ってよい |
| 3 | 病棟から検査に来る患者が X線室に入れない／付き添いの看護師が生理検査の待合に入れない | どちらも入れる。看護師は検査の所まで一緒に行く |
| 4 | ほかの階の職員が、X線室・生理検査の検査室・外来化学療法室へ行けない | 待合や処置の場所を通り抜けてよい |
| 5 | 化学療法の患者が、全員約85m先の西のトイレへ行く | 化学療法室のトイレを使う |
| 6 | 大廊下の人は平均1.6人で、参考の60人・20人と大きく違う | 動きを足す前に、参考の人数の出し方を見直す |
| 7 | Unity で決めた値（トイレに立つ時刻・付き添いの待ち方など） | このままでよい |

### 大廊下の参考の60人は、この階の人の流れからは出なかった

参考にしていた「混む時間60人・空いている時間20人」は、前の前提の表で「仮置き」と書かれていて、根拠がありませんでした。そこで、この階の外来の人数から、大廊下に同時にいる人の数を出し直しました。使ったのは[リトルの法則](https://ja.wikipedia.org/wiki/%E3%83%AA%E3%83%88%E3%83%AB%E3%81%AE%E6%B3%95%E5%89%87)です。リトルの法則は「ある場所に同時にいる人の数は、そこに入ってくる人の速さに、そこにいる時間を掛けたもの」という関係で、待ち行列の計算でよく使います。

| 前提 | 値 |
|---|---|
| この階の外来の患者・付き添い | 1日 390人＋180人＝570人 |
| いちばん多い1時間に玄関から入る人 | その22%で約125人（午後は7%で約40人） |
| 1人が大廊下 x0〜70 を通る回数 | 2〜4回 |
| 1回に大廊下にいる時間 | 30〜60秒（30〜40m を秒速0.8〜1.0m） |
| 混む時間に同時にいる人 | 約2〜8人（真ん中は約5人） |
| 午後に同時にいる人 | 約1〜3人 |

エレベーターから来る職員や見舞いの人を足しても、1人ほど増えるだけです。**60人は、この階の患者の数からは出てこない数で、Unity の病院の大廊下が空いているのは、計算と合っていました。** そのため、大廊下に人を増やすための動きは足さないことにしました。参考の値（value）は依頼143 の形を同じに回すためにそのまま残し、前提の表 assumptions.json に、流れからの目安を flow_estimate として書き足しました。

- ねらい：Unity の大廊下の人数を、根拠のある数と比べられるようにする
- 変わったこと：比べる相手を、根拠のない60人・20人から、流れからの目安（混む時間2〜8人・午後1〜3人）に替えた
- 見込める効果：大廊下が「空きすぎ」かどうかを、人の動きを足すかどうかの判断に使える。病院の廊下で人を数えた公開データは見つからなかったので、現場で数えられたら置き換える

#### estimate_hall_people.py（大廊下に同時にいる人の目安を出す）

```python
"""外来の大廊下 x0〜70 に同時にいる人の数の目安を、この階の人の流れから出す（2026-11-11・LOG488）。
リトルの法則：同時にいる人 ＝ 1秒あたりに大廊下を通る回数 × 1回に大廊下にいる秒数
前提は p2m-site の tools/hospital_sim/hospital-unity-params.json の people_flow（読むだけ）。"""
import json
p = json.load(open('hospital-unity-params.json'))['people_flow']
op = p['outpatients_per_day']['value']          # 科ごとの1日の外来患者数
cr = p['companion_ratio']['value']              # 付き添いが付く割合（小児科は1.0）
comp = sum(v * (1.0 if k == '小児科' else cr) for k, v in op.items())
tot = sum(op.values()) + comp
print('外来の患者', sum(op.values()), '付き添い', comp, '合計', tot)
for share, label in [(0.22, '山場（いちばん多い1時間）'), (0.07, '午後')]:
    arr = tot * share                           # その1時間に玄関から入る人
    for legs, sec in [(2, 30), (3, 45), (4, 60)]:   # 大廊下を通る回数・1回に大廊下にいる秒数
        print(label, round(arr), '人/時', legs, '回', sec, '秒 →', round(arr * legs / 3600 * sec, 1), '人')
```

#### estimate_hall_people.py の出力

```text
外来の患者 390 付き添い 180.0 合計 570.0
山場（いちばん多い1時間） 125 人/時 2 回 30 秒 → 2.1 人
山場（いちばん多い1時間） 125 人/時 3 回 45 秒 → 4.7 人
山場（いちばん多い1時間） 125 人/時 4 回 60 秒 → 8.4 人
午後 40 人/時 2 回 30 秒 → 0.7 人
午後 40 人/時 3 回 45 秒 → 1.5 人
午後 40 人/時 4 回 60 秒 → 2.7 人
```

### フロアの形の「だれがどこに入れるか」に、通り抜けと付き添いの所を足した

病院のフロアの形は、サイト（p2m-site）の hospital_h1.json が正で、Unity はこれを読んで建てます。この中の access は「だれがどこに入れるか」の表で、外来の患者・看護師・医師など9つの役割ごとに、所ごとに「座る」か「入る」を決めています。cross_only は「通り抜けるだけ」の所で、指定した範囲だけを通れます。2〜5 の答えに合わせて、次を足しました。

| 所 | 足した役割 | 理由（答えの番号） |
|---|---|---|
| 職員の中廊下（正面玄関の前 x44〜52） | 救急の患者（通り抜けだけ） | 2 |
| 救急の待合・救急の処置の場所 | 外来の患者（通り抜けだけ。病棟から X線室へ来た患者） | 3 |
| X線室 | 外来の患者・看護師（入る） | 3 |
| 生理検査の待合・検査室 | 看護師（入る）、待合には医師も | 3・4 |
| 救急の処置の場所 | 検査技師（入る） | 4 |
| 化学療法の受付・待合 | 医師（入る） | 4 |
| トイレの行き先（条件の表） | 化学療法の患者は化学療法のトイレ | 5 |

変えたのは access とトイレの行き先だけで、ほかの部屋・家具・道の長さは変わっていません（作り直したあとの確かめで「問題なし」）。

#### c61174b の差分（make_hospital_h1.py・hospital-unity-params.json）

```diff
commit c61174b241fb1e6b4049d6216c5a79760cd2a2c6
Author: Claude <noreply@anthropic.com>
Date:   Wed Nov 11 18:57:40 2026 +0900

    病院 h1：依頼149 の決めてほしいこと2〜6を反映（access：救急の患者の玄関からの通り抜け・病棟の患者のX線室・付き添いの看護師・職員の通り抜け／化学療法の患者は化学療法のトイレ／大廊下の人数の流れからの目安 flow_estimate）
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01LevEXTHwTViAABX66t78yD

diff --git a/tools/hospital_layouts/make_hospital_h1.py b/tools/hospital_layouts/make_hospital_h1.py
index d0d3bcd..63e08d5 100644
--- a/tools/hospital_layouts/make_hospital_h1.py
+++ b/tools/hospital_layouts/make_hospital_h1.py
@@ -354,8 +354,8 @@ ACCESS = [  # 名前, 部屋, [rect...], {役割: 座る|入る}, 補足
     ("ほかの外来の裏の通路", "ほかの外来", [(42, 41.5, 97.95, 43.6)], {"外来の患者": E_, "看護師": E_, "医師": E_},
      "患者は処置・検査室へ行く人だけ、看護師が付き添って通る（2026-11-11 案A）"),
     ("ほかの外来の処置・検査室", "ほかの外来", [(42, 43.6, 97.95, 57.9)], {"外来の患者": E_, "看護師": E_, "医師": E_}, "処置を受ける患者だけ"),
-    ("化学療法の受付・待合", "外来化学療法室", [(100.05, 31, 120, 35)], {"化学療法の患者": S_, "看護師": E_, "薬剤師": E_, "ロボット": E_},
-     "ロボットは受付の台の前まで"),
+    ("化学療法の受付・待合", "外来化学療法室", [(100.05, 31, 120, 35)], {"化学療法の患者": S_, "看護師": E_, "医師": E_, "薬剤師": E_, "ロボット": E_},
+     "ロボットは受付の台の前まで。医師は点滴の場所へ通る（2026-11-11 依頼149 の4）"),
     ("化学療法の点滴の場所", "外来化学療法室", [(100.05, 35, 120, 52), (100.05, 52, 106, 60), (114, 52, 120, 53), (114, 53, 114.5, 60)],
      {"化学療法の患者": S_, "看護師": E_, "医師": E_, "薬剤師": E_}, "患者は点滴のいす・ベッドで数時間"),
     ("化学療法の準備室", "外来化学療法室", [(106, 52, 114, 60)], {"看護師": E_, "薬剤師": E_}, ""),
@@ -373,14 +373,17 @@ ACCESS = [  # 名前, 部屋, [rect...], {役割: 座る|入る}, 補足
     ("正面玄関", "正面玄関", [(44, 0, 52, 13)], {"外来の患者": E_, "化学療法の患者": E_, "救急の患者": E_, "事務": E_}, ""),
     ("医事・事務", "医事・事務", [(52, 0, 70, 13)], {"事務": S_}, ""),
     ("中央検査室", "中央検査室", [(70, 0, 98, 13)], {"検査技師": S_, "ロボット": E_}, "ロボットは検体受付の台の前まで"),
-    ("生理検査の待合", "生理検査", [(70, 21.9, 98, 27)], {"外来の患者": S_, "検査技師": E_}, ""),
+    ("生理検査の待合", "生理検査", [(70, 21.9, 98, 27)], {"外来の患者": S_, "看護師": E_, "医師": E_, "検査技師": E_},
+     "看護師は病棟から来た患者に付き添う・医師は検査室へ通る（2026-11-11 依頼149 の3・4）"),
     ("トイレ（東）", "生理検査", [(70 + 5 * 28.0 / 6, 15.1, 98, 21.9)], {"外来の患者": E_, "化学療法の患者": E_, "救急の患者": E_}, "付き添いも入る"),
-    ("生理検査の検査室", "生理検査", [(70, 15.1, 70 + 5 * 28.0 / 6, 21.9)], {"外来の患者": E_, "検査技師": S_, "医師": E_}, "患者は検査ベッドに寝る"),
+    ("生理検査の検査室", "生理検査", [(70, 15.1, 70 + 5 * 28.0 / 6, 21.9)], {"外来の患者": E_, "看護師": E_, "検査技師": S_, "医師": E_}, "患者は検査ベッドに寝る。看護師は病棟から来た患者に付き添う"),
     ("救急の待合", "救急室", [(100.1, 20, 120, 27)], {"救急の患者": S_, "看護師": E_}, "歩いて来た人"),
     ("救急の処置の場所", "救急室", [(100.1, 6, 120, 20), (108, 0, 110, 6)],
-     {"救急の患者": S_, "看護師": E_, "医師": E_, "ロボット": E_}, "患者は処置ベッド。ロボットは検体の台の前まで"),
+     {"救急の患者": S_, "看護師": E_, "医師": E_, "検査技師": E_, "ロボット": E_},
+     "患者は処置ベッド。ロボットは検体の台の前まで。検査技師は X線室へ通る（2026-11-11 依頼149 の4）"),
     ("初療室", "救急室", [(110, 0, 120, 6)], {"救急の患者": E_, "看護師": E_, "医師": E_}, "救急車で来た人"),
-    ("X線室", "救急室", [(100.1, 0, 108, 6)], {"救急の患者": E_, "医師": E_, "検査技師": E_}, ""),
+    ("X線室", "救急室", [(100.1, 0, 108, 6)], {"外来の患者": E_, "救急の患者": E_, "看護師": E_, "医師": E_, "検査技師": E_},
+     "外来の患者は病棟から検査に来た患者だけ（看護師が付き添う。2026-11-11 依頼149 の3）"),
     ("外来の大廊下", "廊下", [(0, 27, 120, 31)], {r: E_ for r in ROLES}, ""),
     ("玄関の通り", "廊下", [(44, 15.1, 52, 27)], {r: E_ for r in ROLES if r != "ロボット"}, ""),
     ("職員の中廊下", "廊下", [(0, 13, 100.1, 15.1)], {r: E_ for r in ["看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]},
@@ -390,7 +393,11 @@ ACCESS = [  # 名前, 部屋, [rect...], {役割: 座る|入る}, 補足
     ("外来の奥の職員通路", "廊下", [(0, 57.9, 100.05, 60), (97.95, 31, 100.05, 57.9)],
      {r: E_ for r in ["看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]}, "東の縦廊下（北）を含む"),
 ]
-CROSS_ONLY = {"職員の中廊下": {"外来の患者": (44, 13, 52, 15.1), "化学療法の患者": (44, 13, 52, 15.1)}}
+CROSS_ONLY = {"職員の中廊下": {"外来の患者": (44, 13, 52, 15.1), "化学療法の患者": (44, 13, 52, 15.1),
+                        "救急の患者": (44, 13, 52, 15.1)},  # 救急の患者は正面玄関から（2026-11-11 依頼149 の2）
+              # 病棟から X線室へ来た患者は、救急の待合と処置の場所を通り抜けるだけ（2026-11-11 依頼149 の3）
+              "救急の待合": {"外来の患者": (100.1, 20, 120, 27)},
+              "救急の処置の場所": {"外来の患者": (100.1, 6, 120, 20)}}
 
 
 def check(data):
diff --git a/tools/hospital_sim/hospital-unity-params.json b/tools/hospital_sim/hospital-unity-params.json
index 6d0e273..4dd8fa8 100644
--- a/tools/hospital_sim/hospital-unity-params.json
+++ b/tools/hospital_sim/hospital-unity-params.json
@@ -3,7 +3,7 @@
  "model": "h3",
  "layout": {
   "file": "tools/hospital_layouts/hospital_h1.json",
-  "md5": "0aa8db3cf7162cab06c7309403ee826b",
+  "md5": "85fa5f0614b9e82cf7671e993c8751b8",
   "name": "仮の病院 h1（外来の階・部屋の中まで）"
  },
  "route": {
@@ -354,7 +354,7 @@
  "east_hall_people_ratio": 0.25,
  "layout_back_door": {
   "file": "tools/hospital_layouts/hospital_h1.json",
-  "md5": "0aa8db3cf7162cab06c7309403ee826b",
+  "md5": "85fa5f0614b9e82cf7671e993c8751b8",
   "name": "仮の病院 h1（外来の階・部屋の中まで）",
   "blue_path": [
    [
@@ -10336,7 +10336,7 @@
      3,
      7
     ],
-    "where": "近い方のトイレ（西・東）"
+    "where": "近い方のトイレ（西・東）。化学療法の患者は化学療法のトイレ（2026-11-11 依頼149 の5：西まで約85m歩いていたため）"
    },
    "what": "外来の患者・付き添い・化学療法の患者がトイレに行く割合（病院にいる間に1回）と、とどまる時間",
    "basis": "仮置き",
```

#### c61174b の差分（assumptions.json。大廊下の目安とトイレの行き先）

```diff
commit c61174b241fb1e6b4049d6216c5a79760cd2a2c6
Author: Claude <noreply@anthropic.com>
Date:   Wed Nov 11 18:57:40 2026 +0900

    病院 h1：依頼149 の決めてほしいこと2〜6を反映（access：救急の患者の玄関からの通り抜け・病棟の患者のX線室・付き添いの看護師・職員の通り抜け／化学療法の患者は化学療法のトイレ／大廊下の人数の流れからの目安 flow_estimate）
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01LevEXTHwTViAABX66t78yD

diff --git a/tools/hospital_sim/assumptions.json b/tools/hospital_sim/assumptions.json
index 9f5ee18..b9d78b7 100644
--- a/tools/hospital_sim/assumptions.json
+++ b/tools/hospital_sim/assumptions.json
@@ -498,7 +498,25 @@
    "source": "エイイチと決めた（2026-11-09）",
    "n": null,
    "updated": "2026-11-09",
-   "note": "2026-11-11：依頼147 から人は行き先の順で動くので、Unity はこの人数を置かない。大廊下の人数と比べる参考の値として残す"
+   "note": "2026-11-11：依頼147 から人は行き先の順で動くので、Unity はこの人数を置かない。大廊下の人数と比べる参考の値として残す。2026-11-11：仮置きの 20・60 は人の流れから出ない（flow_estimate）。依頼143 の hall の形を同じに回すため value は変えない",
+   "flow_estimate": {
+    "value": {
+     "空いている（午後）": [
+      1,
+      3
+     ],
+     "混んでいる（8〜11時）": [
+      2,
+      8
+     ]
+    },
+    "unit": "人",
+    "what": "外来の大廊下 x0〜70 に同時にいる人の数を、この階の人の流れから出した目安（リトルの法則：同時にいる人＝1秒あたりに大廊下を通る回数×1回に大廊下にいる秒数）",
+    "basis": "推計",
+    "source": "この階の外来の患者 390人＋付き添い 180人＝1日 570人（people_flow の outpatients_per_day・companion_ratio）。いちばん多い1時間に入る人はその22%で約125人、午後は7%で約40人（arrival_by_hour）。1人が大廊下 x0〜70 を通るのは 2〜4回、1回に 30〜60秒（30〜40m を秒速 0.8〜1.0m）として、山場 約2〜8人（真ん中 約5人）・午後 約1〜3人。エレベーターから来る職員・見舞いの人を足しても1人ほど増えるだけ",
+    "updated": "2026-11-11",
+    "note": "依頼149 の Unity（見た目なし）は山場 平均1.7〜2.6人・午後 平均1.6〜2.1人で、この幅の中（山場は下の端）。60人・20人は根拠のない仮置きで、この流れからは出ない。病院の廊下で人を数えた公開データは見つからなかった。現場で数えられたら置き換える"
+   }
   },
   "people_mix": {
    "value": {
@@ -1090,7 +1108,7 @@
       3,
       7
      ],
-     "where": "近い方のトイレ（西・東）"
+     "where": "近い方のトイレ（西・東）。化学療法の患者は化学療法のトイレ（2026-11-11 依頼149 の5：西まで約85m歩いていたため）"
     },
     "what": "外来の患者・付き添い・化学療法の患者がトイレに行く割合（病院にいる間に1回）と、とどまる時間",
     "basis": "仮置き",
```

#### make_hospital_h1.py（直したあとの全文）

```python
# -*- coding: utf-8 -*-
"""仮の病院 h1：h0b の形に、全部の部屋の中（小部屋・扉・家具）と4つの道を足す。

2026-11-10 エイイチと決めた：病院のフロアと道を全部作る。部屋の中まで全部、道は 3ルート＋裏口の道。
- 部屋・廊下・積む所と届け先の点は h0b と同じ（h0・h0b の JSON は変えない）
- 部屋の中の並びは、外来・検査部・薬剤部・外来化学療法室・救急室の典型をコードで起こした仮置き
- 道は 緑（救急室→検査部）・青 表（内科外来の処置室→検査部、大廊下を通る）・青 裏口（職員通路を通る）・赤（薬剤部→外来化学療法室）
- 高さは配膳のベースに合わせる：部屋・小部屋の壁 2.4m

使い方：python3 make_hospital_h1.py 出力先
出力：hospital_h1.json・hospital_h1.png（全体）・hospital_h1_rooms.png（部屋ごとの寄り）
"""
import json
import sys
from pathlib import Path

import make_hospital as mh

WALL_H = 2.4
H = {"ベンチ": 0.45, "いす": 0.45, "机": 0.72, "ベッド": 0.6, "カウンター": 1.0, "棚": 1.8,
     "分析装置": 1.3, "リクライニングチェア": 1.0, "台": 0.9, "機器": 1.2, "精算機": 1.5,
     "置き台": 0.9, "台車": 1.0, "島": 1.0, "車いすの場所": 0.0}

SUB = []    # 小部屋：部屋の中を壁で区切った所
FUR = []    # 家具
OPEN = []   # 部屋の壁のうち、廊下へ開いている所（壁を建てない）
DOORS = []  # 部屋の壁の扉（廊下や別の部屋へ出る）
INNER = []  # 部屋の中の通路（壁で囲まれていない、人やロボットが通る所）


def sub(room, name, rect, doors=(), what=""):
    SUB.append({"room": room, "name": name, "rect": [round(v, 2) for v in rect],
                "doors": [{"at": [round(d[0], 2), round(d[1], 2)], "w": d[2]} for d in doors], "what": what})


def fur(room, kind, rect, name=""):
    FUR.append({"room": room, "kind": kind, "name": name or kind, "rect": [round(v, 2) for v in rect], "h": H[kind]})


def door(room, x, y, w, to, name=""):
    DOORS.append({"room": room, "at": [round(x, 2), round(y, 2)], "w": w, "to": to, "name": name})


def opening(room, side, a, b, to):
    OPEN.append({"room": room, "side": side, "span": [a, b], "to": to})


def inner(room, name, rect, w):
    INNER.append({"room": room, "name": name, "rect": [round(v, 2) for v in rect], "w": w})


def benches(room, x0, x1, y, n_rows, pitch=1.5, length=4.0, gap=1.0):
    """x0〜x1 に長さ length のベンチを並べ、y から pitch ごとに n_rows 列"""
    for r in range(n_rows):
        x = x0
        while x + length <= x1 + 1e-6:
            fur(room, "ベンチ", (x, y + r * pitch, x + length, y + r * pitch + 0.45))
            x += length + gap


# ---------------- 内科外来（x0〜42, y31〜57.9）----------------
R = "内科外来"
door(R, 12.0, 31.0, 1.6, "外来の大廊下", "内科外来の処置室（積む所）")
door(R, 12.0, 57.9, 1.6, "外来の奥の職員通路", "内科外来の処置室（裏口）")
sub(R, "処置室", (0, 31, 22, 46), [(12.0, 31.0, 1.6), (12.0, 46.0, 1.6), (22.0, 42.05, 1.2)],
    "採血・点滴・検体を出す所。南の扉が積む所（表）")
fur(R, "置き台", (12.8, 38.4, 13.6, 40.4), "検体の置き台（ロボットに積む）")
for i in range(4):
    fur(R, "ベッド", (0.4, 33 + i * 2.4, 2.4, 33.9 + i * 2.4), "処置ベッド")
fur(R, "島", (3.5, 36.0, 10.5, 37.5), "看護師の作業台")
for x0, x1 in ((1.0, 10.5), (13.5, 21.0)):
    fur(R, "カウンター", (x0, 44.6, x1, 45.4), "採血台")
    n = int((x1 - x0) // 1.9)
    for k in range(n):
        fur(R, "いす", (x0 + 0.5 + k * 1.9, 43.6, x0 + 1.0 + k * 1.9, 44.1), "採血のいす")
inner(R, "処置室の裏の通路", (11.0, 46.0, 13.1, 57.9), 2.1)
sub(R, "器材室", (0, 46, 11, 57.9), [(11.0, 52.0, 1.0)])
for i in range(3):
    fur(R, "棚", (0.4, 47.5 + i * 3.2, 9.5, 48.1 + i * 3.2))
sub(R, "検体の準備室", (13.1, 46, 22, 57.9), [(13.1, 52.0, 1.0)])
fur(R, "台", (14.0, 47.0, 21.0, 47.8), "検体の仕分け台")
fur(R, "棚", (21.2, 48.5, 21.8, 56.5), "冷蔵庫")
opening(R, "S", 22.0, 42.0, "外来の大廊下")
inner(R, "内科の中待合", (22, 31, 42, 35.5), 4.5)
benches(R, 23.0, 41.5, 32.2, 2, pitch=1.4, length=4.5, gap=0.5)
for i in range(4):
    x0 = 22 + 5 * i
    sub(R, f"診察室{i + 1}", (x0, 35.5, x0 + 5, 41.0), [(x0 + 2.5, 35.5, 1.0), (x0 + 2.5, 41.0, 0.9)])
    fur(R, "机", (x0 + 0.3, 38.0, x0 + 1.5, 40.6), "診察机")
    fur(R, "ベッド", (x0 + 3.6, 37.0, x0 + 4.5, 39.0), "診察ベッド")
inner(R, "診察室の裏の通路", (22, 41.0, 42, 43.1), 2.1)
for i, nm in enumerate(("内視鏡室1", "内視鏡室2")):
    x0 = 22 + 10 * i
    sub(R, nm, (x0, 43.1, x0 + 10, 51.0), [(x0 + 5, 43.1, 1.4)])
    fur(R, "ベッド", (x0 + 3.0, 46.0, x0 + 5.0, 46.9), "検査ベッド")
    fur(R, "機器", (x0 + 6.0, 45.5, x0 + 7.2, 47.5), "内視鏡の機器")
sub(R, "回復室", (22, 51.0, 42, 57.9), [(32.0, 51.0, 1.4), (32.0, 57.9, 1.4)])
for i in range(6):
    fur(R, "ベッド", (23.0 + i * 3.0, 55.0, 23.9 + i * 3.0, 57.0), "回復ベッド")

# ---------------- ほかの外来（x42〜97.95, y31〜57.9）----------------
R = "ほかの外来"
X0, X1 = 42.0, 97.95
depts = ["外科・整形外科", "小児科", "眼科・耳鼻科"]
dw = (X1 - X0) / 3
opening(R, "S", X0, X1, "外来の大廊下")
for j, dn in enumerate(depts):
    a, b = X0 + j * dw, X0 + (j + 1) * dw
    inner(R, f"{dn}の中待合", (a, 31, b, 36), 5.0)
    fur(R, "カウンター", (a + 0.6, 31.5, a + 4.0, 32.3), f"{dn}の受付")
    benches(R, a + 5.0, b - 0.5, 32.3, 2, pitch=1.4, length=4.0, gap=0.6)
    cw = dw / 3
    for k in range(3):
        c0 = a + k * cw
        sub(R, f"{dn}の診察室{k + 1}", (c0, 36, c0 + cw, 41.5), [(c0 + cw / 2, 36, 1.0), (c0 + cw / 2, 41.5, 0.9)])
        fur(R, "机", (c0 + 0.3, 38.4, c0 + 1.5, 41.1), "診察机")
        fur(R, "ベッド", (c0 + cw - 1.3, 37.5, c0 + cw - 0.4, 39.5), "診察ベッド")
    inner(R, f"{dn}の裏の通路", (a, 41.5, b, 43.6), 2.1)
    sub(R, f"{dn}の処置・検査室", (a, 43.6, b, 57.9), [((a + b) / 2, 43.6, 1.4), ((a + b) / 2, 57.9, 1.4)])
    for k in range(4):
        fur(R, "ベッド", (a + 1.0 + k * 4.2, 54.5, a + 1.9 + k * 4.2, 56.5), "処置ベッド")
    fur(R, "島", (a + 5.0, 47.0, b - 5.0, 48.4), "作業台")
    fur(R, "棚", (a + 0.4, 44.5, a + 1.0, 51.5), "器材の棚")
    door(R, (a + b) / 2, 57.9, 1.4, "外来の奥の職員通路")

# ---------------- 外来化学療法室（x100.05〜120, y31〜60）----------------
R = "外来化学療法室"
door(R, 104.0, 31.0, 1.6, "外来の大廊下", "外来化学療法室の受付（届け先）")
fur(R, "カウンター", (105.2, 31.8, 109.0, 32.6), "受付（薬を受け取る）")
benches(R, 111.8, 119.5, 32.0, 2, pitch=1.4, length=3.5, gap=0.5)
fur(R, "島", (106.0, 40.0, 114.0, 43.0), "ナースステーション")
for i in range(9):
    fur(R, "リクライニングチェア", (100.6, 36.0 + i * 2.3, 102.2, 36.8 + i * 2.3), "点滴のいす")
for i in range(6):
    fur(R, "ベッド", (117.6, 35.5 + i * 2.6, 119.6, 36.4 + i * 2.6), "点滴のベッド")
sub(R, "準備室", (106.0, 52.0, 114.0, 60.0), [(110.0, 52.0, 1.2)])
fur(R, "台", (107.0, 58.6, 113.0, 59.4), "点滴の準備台")
sub(R, "トイレ", (114.5, 53.0, 120.0, 60.0), [(114.5, 55.0, 1.0)])

# ---------------- 薬剤部（x0〜23, y0〜13）----------------
R = "薬剤部"
door(R, 13.0, 13.0, 1.6, "職員の中廊下", "薬剤部の払い出し口（積む所）")
fur(R, "置き台", (14.2, 11.8, 16.5, 12.6), "払い出し台（ロボットに積む）")
sub(R, "無菌調製室", (0, 0, 7, 6), [(7.0, 3.0, 0.9)], "抗がん剤を調製する所")
fur(R, "機器", (0.5, 0.5, 2.0, 4.0), "安全キャビネット")
sub(R, "前室", (7, 0, 10, 6), [(8.5, 6.0, 0.9)])
fur(R, "台", (11.0, 2.0, 21.0, 3.0), "調剤台")
fur(R, "台", (11.0, 5.0, 21.0, 6.0), "調剤台")
fur(R, "機器", (19.5, 0.4, 22.6, 1.6), "錠剤の分包機")
fur(R, "棚", (0.4, 7.5, 9.0, 8.1), "薬品棚")
fur(R, "棚", (0.4, 9.8, 9.0, 10.4), "薬品棚")
fur(R, "机", (17.0, 8.0, 22.0, 9.5), "事務机")

# ---------------- お薬の窓口（x0〜23, y15.1〜27）----------------
R = "お薬の窓口"
opening(R, "N", 1.0, 17.0, "外来の大廊下")
door(R, 6.0, 15.1, 0.9, "職員の中廊下")
fur(R, "カウンター", (1.0, 18.5, 16.5, 19.3), "お薬の窓口")
benches(R, 2.0, 16.5, 21.0, 3, pitch=1.6, length=4.5, gap=0.5)
# 2026-11-11 トイレを足した（患者が待ち時間に行く所。大廊下に人がほとんど歩かなかったため）。大廊下から入る
sub(R, "トイレ（西）", (17.5, 15.1, 22.95, 27.0), [(20.2, 27.0, 1.6)], "男女・多目的。大廊下に面する")

# ---------------- 総合受付・会計・待合（x25.1〜70, y15.1〜27。玄関の通り x44〜52 をはさむ）----------------
R = "総合受付・会計・待合"
opening(R, "N", 25.1, 70.0, "外来の大廊下")
# 玄関の通り（x44〜52）は部屋の外枠の内側なので壁は建たない。x70 の東の辺は生理検査との壁なので開けない（2026-11-10 依頼143 で見つかった間違いを直した）
door(R, 32.5, 15.1, 0.9, "職員の中廊下")
# 2026-11-11 エレベーターを足した（ほかの階との行き来。病棟から検査に来る患者・見舞いの人・職員）。大廊下に面したホール
sub(R, "エレベーター（3基）", (25.1, 15.1, 31.0, 21.0), [(26.1, 21.0, 1.1), (28.05, 21.0, 1.1), (30.0, 21.0, 1.1)], "ほかの階との出入り口")
inner(R, "エレベーターホール", (25.1, 21.0, 31.0, 27.0), 6.0)
door(R, 58.0, 15.1, 0.9, "職員の中廊下")
fur(R, "カウンター", (32.0, 16.6, 43.6, 17.4), "総合受付")
fur(R, "カウンター", (52.4, 16.6, 63.0, 17.4), "会計")
for i in range(4):
    fur(R, "精算機", (64.0 + i * 1.4, 15.5, 65.0 + i * 1.4, 16.3), "自動精算機")
benches(R, 31.8, 43.0, 19.5, 4, pitch=1.6, length=3.5, gap=0.5)
benches(R, 53.0, 69.5, 19.5, 4, pitch=1.6, length=4.0, gap=0.6)

# ---------------- 倉庫（x25.1〜44, y0〜13）----------------
R = "倉庫"
door(R, 35.0, 13.0, 1.8, "職員の中廊下")
for i in range(4):
    fur(R, "棚", (26.0, 1.0 + i * 2.4, 42.0, 1.6 + i * 2.4))
fur(R, "台車", (26.0, 10.6, 32.0, 12.4), "台車置き場")

# ---------------- 階段・設備（h0 で部屋のなかった所）----------------
EXTRA_ROOMS = [("階段・設備（西）", 23.0, 0.0, 25.1, 13.0, "その他"),
               ("階段・設備（東）", 98.0, 0.0, 100.1, 13.0, "その他")]

# ---------------- 正面玄関（x44〜52, y0〜13）----------------
R = "正面玄関"
opening(R, "N", 44.0, 52.0, "職員の中廊下・玄関の通り")
sub(R, "風除室", (44, 0, 52, 4), [(48.0, 0.0, 3.0), (48.0, 4.0, 3.0)], "自動扉が2つ")
fur(R, "台", (44.3, 5.0, 45.3, 10.0), "車いす置き場")
fur(R, "カウンター", (50.2, 6.0, 51.7, 9.0), "案内")

# ---------------- 医事・事務（x52〜70, y0〜13）----------------
R = "医事・事務"
door(R, 55.0, 13.0, 0.9, "職員の中廊下")
door(R, 67.0, 13.0, 0.9, "職員の中廊下")
fur(R, "机", (54.0, 3.0, 66.0, 4.6), "事務机の島")
fur(R, "机", (54.0, 6.5, 66.0, 8.1), "事務机の島")
fur(R, "机", (54.0, 9.6, 66.0, 11.2), "事務机の島")
fur(R, "棚", (68.4, 1.0, 69.6, 10.0), "書庫")

# ---------------- 中央検査室（x70〜98, y0〜13）----------------
R = "中央検査室"
door(R, 80.0, 13.0, 1.6, "職員の中廊下", "検査部の検体受付（届け先）")
door(R, 94.0, 13.0, 0.9, "職員の中廊下")
fur(R, "置き台", (81.3, 11.6, 85.0, 12.4), "検体受付の台（ロボットから受け取る）")
fur(R, "台", (78.0, 8.5, 84.0, 9.5), "検体の仕分け台")
fur(R, "分析装置", (72.0, 1.0, 96.0, 2.2), "分析装置（生化学・免疫）")
fur(R, "分析装置", (86.0, 5.0, 96.0, 6.4), "分析装置（血液）")
fur(R, "分析装置", (86.0, 8.5, 96.0, 9.9), "検体の搬送ライン")
fur(R, "棚", (70.4, 8.0, 71.2, 12.0), "冷蔵庫")
fur(R, "机", (72.0, 5.0, 78.0, 6.2), "検査技師の机")

# ---------------- 生理検査（x70〜98, y15.1〜27）----------------
R = "生理検査"
opening(R, "N", 70.0, 98.0, "外来の大廊下")
inner(R, "生理検査の待合", (70, 21.9, 98, 27), 5.1)
fur(R, "カウンター", (70.3, 22.8, 71.1, 25.8), "生理検査の受付")
benches(R, 76.0, 97.5, 24.2, 2, pitch=1.4, length=4.0, gap=0.6)
names = ["心電図", "心エコー", "腹部エコー", "肺機能", "脳波"]
cw = 28.0 / 6
# 2026-11-11 東の端（もとは血圧脈波の検査室）をトイレ（東）にした。生理検査の待合から入る
sub(R, "トイレ（東）", (70 + 5 * cw, 15.1, 98.0, 21.9), [(70 + 5.5 * cw, 21.9, 1.6)], "男女・多目的")
for k, nm in enumerate(names):
    c0 = 70 + k * cw
    sub(R, f"{nm}の検査室", (c0, 15.1, c0 + cw, 21.9), [(c0 + cw / 2, 21.9, 1.0)])
    fur(R, "ベッド", (c0 + 1.0, 16.0, c0 + 3.0, 16.9), "検査ベッド")
    fur(R, "機器", (c0 + 3.4, 16.0, c0 + 4.3, 17.2), "検査の機器")

# ---------------- 救急室（x100.1〜120, y0〜27）----------------
R = "救急室"
door(R, 100.1, mh.STAFF_C, 1.6, "職員の中廊下", "救急室の処置室（積む所）")
door(R, 110.0, 27.0, 1.6, "外来の大廊下", "歩いて来た人の入口")
door(R, 120.0, 5.0, 2.4, "外（救急車）", "救急車の入口")
fur(R, "置き台", (100.6, 15.4, 102.6, 16.2), "検体の置き台（ロボットに積む）")
sub(R, "初療室", (110, 0, 120, 6), [(115.0, 6.0, 1.8)], "重い人を最初に診る所")
fur(R, "ベッド", (113.5, 2.5, 116.5, 3.5), "初療のベッド")
sub(R, "X線室", (100.1, 0, 108, 6), [(104.0, 6.0, 1.4)])
fur(R, "機器", (102.0, 1.5, 106.0, 3.5), "X線の機器")
for i in range(6):
    fur(R, "ベッド", (117.5, 8.0 + i * 2.4, 119.5, 8.9 + i * 2.4), "処置ベッド")
fur(R, "島", (106.0, 11.0, 111.0, 15.0), "ナースステーション")
benches(R, 101.0, 109.5, 22.0, 2, pitch=1.4, length=4.0, gap=0.5)


# 車いすの場所（2026-11-10 エイイチと決めた）：待合ごとに、通路に面したベンチの列の端に 1.5m 四方を足す。
# 指針（東京都 UD チェックリスト：待合に車椅子使用者が利用しやすいスペースの確保に配慮）にならう。列の端に置く形は仮置き。
# 「ない」条件（車いすの人がいすの間・通路で待つ）は試算・Unity の条件で選ぶ。床の印だけで高さはない
WC = 1.5
WAITING = [  # 待合の名前, 部屋, 待合の範囲, 通路（開いている側）の向き, 数
    ("内科の中待合", "内科外来", (22, 31, 42, 35.5), "S", 1),
    ("外科・整形外科の中待合", "ほかの外来", (42, 31, 60.65, 36), "S", 1),
    ("小児科の中待合", "ほかの外来", (60.65, 31, 79.3, 36), "S", 1),
    ("眼科・耳鼻科の中待合", "ほかの外来", (79.3, 31, 97.95, 36), "S", 1),
    ("化学療法の受付・待合", "外来化学療法室", (109.5, 31, 120, 35), "S", 1),
    ("お薬の窓口の待合", "お薬の窓口", (0, 19.3, 17.5, 27), "N", 1),
    ("総合受付の待合", "総合受付・会計・待合", (31.0, 17.4, 44, 27), "N", 2),
    ("会計の待合", "総合受付・会計・待合", (52, 17.4, 70, 27), "N", 2),
    ("生理検査の待合", "生理検査", (70, 21.9, 98, 27), "N", 1),
    ("救急の待合", "救急室", (100.1, 20, 120, 27), "N", 1),
]


def place_wheelchair_spaces():
    placed = []
    for wname, room, (zx0, zy0, zx1, zy1), side, n in WAITING:
        benches_in = [f for f in FUR if f["kind"] == "ベンチ" and f["room"] == room
                      and zx0 <= f["rect"][0] and f["rect"][2] <= zx1 and zy0 <= f["rect"][1] and f["rect"][3] <= zy1]
        cands = []
        for b in benches_in:
            bx0, by0, bx1, by1 = b["rect"]
            for x0 in (bx1 + 0.15, bx0 - 0.15 - WC):
                for y0 in (by0 - (WC - (by1 - by0)) / 2,):
                    cands.append((x0, y0, x0 + WC, y0 + WC))
        doors = [d["at"] for d in DOORS] + [dd["at"] for sb in SUB for dd in sb["doors"]]
        stops = [p for pts in INSIDE.values() for p in pts]
        ok = []
        for c in cands:
            if c[0] < zx0 or c[1] < zy0 or c[2] > zx1 or c[3] > zy1:
                continue
            if any(c[0] < f["rect"][2] + 0.05 and c[2] > f["rect"][0] - 0.05 and c[1] < f["rect"][3] + 0.05
                   and c[3] > f["rect"][1] - 0.05 for f in FUR):
                continue
            if any(c[0] - 1.0 < x < c[2] + 1.0 and c[1] - 1.0 < y < c[3] + 1.0 for x, y in doors + stops):
                continue
            if any(c[0] < q[2] and c[2] > q[0] and c[1] < q[3] and c[3] > q[1] for q in placed):
                continue
            # 通路（開いている側）に近いほどよい。同じなら外側の端
            edge = (c[1] - zy0) if side == "S" else (zy1 - c[3])
            ok.append((round(edge, 2), -abs((c[0] + c[2]) / 2 - (zx0 + zx1) / 2), c))
        ok.sort(key=lambda t: (t[0], t[1]))
        got = 0
        for _, _, c in ok:
            if got >= n:
                break
            if any(c[0] < q[2] and c[2] > q[0] and c[1] < q[3] and c[3] > q[1] for q in placed):
                continue
            fur(room, "車いすの場所", c, f"車いすの場所（{wname}）")
            placed.append(c)
            got += 1
        if got < n:
            print("車いすの場所が置けない：", wname, got, "/", n)


# ロボットが部屋に入って止まる所（2026-11-10 エイイチ：搬送ロボットは部屋の中にも入っていく前提）
# 扉の点（廊下の側）から、部屋の中の台の前で止まる点までの道。縦横だけ
INSIDE = {
    "内科外来の処置室": [(12.0, 31.0), (12.0, 39.4)],
    "内科外来の処置室（裏口）": [(12.0, 57.9), (12.0, 46.0), (12.0, 39.4)],
    "薬剤部の払い出し口": [(13.0, 13.0), (13.0, 11.0), (15.35, 11.0)],
    "外来化学療法室の受付": [(104.0, 31.0), (104.0, 33.4), (107.1, 33.4)],
    "検査部の検体受付": [(80.0, 13.0), (80.0, 10.55), (83.15, 10.55)],
    "救急室の処置室": [(100.1, 14.05), (101.6, 14.05), (101.6, 14.75)],
}
ROBOT_R = 0.35  # ロボットの半径の目安（W3 の幅 0.7m の半分）


def check_inside():
    """部屋の中の道が家具に当たらないか（ロボットの半径＋0.1m の余白）"""
    probs = []
    for name, pts in INSIDE.items():
        for (ax, ay), (bx, by) in zip(pts, pts[1:]):
            lo_x, hi_x, lo_y, hi_y = min(ax, bx), max(ax, bx), min(ay, by), max(ay, by)
            for f in FUR:
                x0, y0, x1, y1 = f["rect"]
                m = ROBOT_R + (0.0 if f["kind"] == "置き台" else 0.1)
                if lo_x - m < x1 and hi_x + m > x0 and lo_y - m < y1 and hi_y + m > y0:
                    probs.append(f"部屋の中の道が家具に当たる：{name} {f['name']}")
    return probs


# だれがどこに入るか（2026-11-10 エイイチ：患者・看護師・医師などの入る所・入らない所・座る所を分ける）
# 役割ごとに「座る」（座る・寝る・腰を落ち着ける所）／「入る」（通る・立ち寄る）。書いていない役割は入らない
ROLES = ["外来の患者", "化学療法の患者", "救急の患者", "看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]
S_, E_ = "座る", "入る"
ACCESS = [  # 名前, 部屋, [rect...], {役割: 座る|入る}, 補足
    ("内科の処置室", "内科外来", [(0, 31, 22, 46)], {"外来の患者": S_, "看護師": E_, "医師": E_, "検査技師": E_, "ロボット": E_},
     "患者は採血のいす・処置ベッド。ロボットは台の前まで"),
    ("内科の処置室の裏の通路", "内科外来", [(11, 46, 13.1, 57.9)], {"看護師": E_, "医師": E_, "検査技師": E_, "ロボット": E_}, ""),
    ("内科の器材室", "内科外来", [(0, 46, 11, 57.9)], {"看護師": E_}, ""),
    ("内科の検体の準備室", "内科外来", [(13.1, 46, 22, 57.9)], {"看護師": E_, "検査技師": E_}, ""),
    ("内科の中待合", "内科外来", [(22, 31, 42, 35.5)], {"外来の患者": S_, "看護師": E_}, "看護師が呼び入れる"),
    ("内科の診察室", "内科外来", [(22, 35.5, 42, 41)], {"外来の患者": S_, "看護師": E_, "医師": S_}, ""),
    ("内科の診察室の裏の通路", "内科外来", [(22, 41, 42, 43.1)], {"外来の患者": E_, "看護師": E_, "医師": E_},
     "患者は内視鏡室へ行く人だけ、看護師が付き添って通る（2026-11-11 案A）"),
    ("内視鏡室", "内科外来", [(22, 43.1, 42, 51)], {"外来の患者": E_, "看護師": E_, "医師": E_}, "検査を受ける患者だけ"),
    ("回復室", "内科外来", [(22, 51, 42, 57.9)], {"外来の患者": S_, "看護師": E_}, "検査を受けた患者だけ。ベッドで休む"),
    ("ほかの外来の中待合", "ほかの外来", [(42, 31, 97.95, 36)], {"外来の患者": S_, "看護師": E_, "事務": E_}, "各科の受付は事務"),
    ("ほかの外来の診察室", "ほかの外来", [(42, 36, 97.95, 41.5)], {"外来の患者": S_, "看護師": E_, "医師": S_}, ""),
    ("ほかの外来の裏の通路", "ほかの外来", [(42, 41.5, 97.95, 43.6)], {"外来の患者": E_, "看護師": E_, "医師": E_},
     "患者は処置・検査室へ行く人だけ、看護師が付き添って通る（2026-11-11 案A）"),
    ("ほかの外来の処置・検査室", "ほかの外来", [(42, 43.6, 97.95, 57.9)], {"外来の患者": E_, "看護師": E_, "医師": E_}, "処置を受ける患者だけ"),
    ("化学療法の受付・待合", "外来化学療法室", [(100.05, 31, 120, 35)], {"化学療法の患者": S_, "看護師": E_, "医師": E_, "薬剤師": E_, "ロボット": E_},
     "ロボットは受付の台の前まで。医師は点滴の場所へ通る（2026-11-11 依頼149 の4）"),
    ("化学療法の点滴の場所", "外来化学療法室", [(100.05, 35, 120, 52), (100.05, 52, 106, 60), (114, 52, 120, 53), (114, 53, 114.5, 60)],
     {"化学療法の患者": S_, "看護師": E_, "医師": E_, "薬剤師": E_}, "患者は点滴のいす・ベッドで数時間"),
    ("化学療法の準備室", "外来化学療法室", [(106, 52, 114, 60)], {"看護師": E_, "薬剤師": E_}, ""),
    ("化学療法のトイレ", "外来化学療法室", [(114.5, 53, 120, 60)], {"化学療法の患者": E_}, "点滴をつけたまま行く"),
    ("薬剤部", "薬剤部", [(0, 0, 23, 13)], {"薬剤師": S_, "ロボット": E_}, "ロボットは払い出し台の前まで"),
    ("お薬の窓口の内側", "お薬の窓口", [(0, 15.1, 17.5, 19.3)], {"薬剤師": S_}, ""),
    ("お薬の窓口の待合", "お薬の窓口", [(0, 19.3, 17.5, 27)], {"外来の患者": S_}, ""),
    ("トイレ（西）", "お薬の窓口", [(17.5, 15.1, 22.95, 27)], {"外来の患者": E_, "化学療法の患者": E_, "救急の患者": E_}, "付き添いも入る"),
    ("エレベーター", "総合受付・会計・待合", [(25.1, 15.1, 31, 21)], {r: E_ for r in ROLES if r != "ロボット"}, "ほかの階との出入り口（ここで現れ・消える）"),
    ("エレベーターホール", "総合受付・会計・待合", [(25.1, 21, 31, 27)], {r: E_ for r in ROLES if r != "ロボット"}, ""),
    ("総合受付・会計の内側", "総合受付・会計・待合", [(31, 15.1, 44, 17.4), (52, 15.1, 70, 17.4)], {"事務": S_}, ""),
    ("総合受付・会計の待合", "総合受付・会計・待合", [(31, 17.4, 44, 27), (52, 17.4, 70, 27)],
     {"外来の患者": S_, "化学療法の患者": S_}, ""),
    ("倉庫", "倉庫", [(25.1, 0, 44, 13)], {"看護師": E_, "事務": E_}, ""),
    ("正面玄関", "正面玄関", [(44, 0, 52, 13)], {"外来の患者": E_, "化学療法の患者": E_, "救急の患者": E_, "事務": E_}, ""),
    ("医事・事務", "医事・事務", [(52, 0, 70, 13)], {"事務": S_}, ""),
    ("中央検査室", "中央検査室", [(70, 0, 98, 13)], {"検査技師": S_, "ロボット": E_}, "ロボットは検体受付の台の前まで"),
    ("生理検査の待合", "生理検査", [(70, 21.9, 98, 27)], {"外来の患者": S_, "看護師": E_, "医師": E_, "検査技師": E_},
     "看護師は病棟から来た患者に付き添う・医師は検査室へ通る（2026-11-11 依頼149 の3・4）"),
    ("トイレ（東）", "生理検査", [(70 + 5 * 28.0 / 6, 15.1, 98, 21.9)], {"外来の患者": E_, "化学療法の患者": E_, "救急の患者": E_}, "付き添いも入る"),
    ("生理検査の検査室", "生理検査", [(70, 15.1, 70 + 5 * 28.0 / 6, 21.9)], {"外来の患者": E_, "看護師": E_, "検査技師": S_, "医師": E_}, "患者は検査ベッドに寝る。看護師は病棟から来た患者に付き添う"),
    ("救急の待合", "救急室", [(100.1, 20, 120, 27)], {"救急の患者": S_, "看護師": E_}, "歩いて来た人"),
    ("救急の処置の場所", "救急室", [(100.1, 6, 120, 20), (108, 0, 110, 6)],
     {"救急の患者": S_, "看護師": E_, "医師": E_, "検査技師": E_, "ロボット": E_},
     "患者は処置ベッド。ロボットは検体の台の前まで。検査技師は X線室へ通る（2026-11-11 依頼149 の4）"),
    ("初療室", "救急室", [(110, 0, 120, 6)], {"救急の患者": E_, "看護師": E_, "医師": E_}, "救急車で来た人"),
    ("X線室", "救急室", [(100.1, 0, 108, 6)], {"外来の患者": E_, "救急の患者": E_, "看護師": E_, "医師": E_, "検査技師": E_},
     "外来の患者は病棟から検査に来た患者だけ（看護師が付き添う。2026-11-11 依頼149 の3）"),
    ("外来の大廊下", "廊下", [(0, 27, 120, 31)], {r: E_ for r in ROLES}, ""),
    ("玄関の通り", "廊下", [(44, 15.1, 52, 27)], {r: E_ for r in ROLES if r != "ロボット"}, ""),
    ("職員の中廊下", "廊下", [(0, 13, 100.1, 15.1)], {r: E_ for r in ["看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]},
     "患者は正面玄関の前（x44〜52）を横切るだけ"),
    ("西の縦廊下", "廊下", [(22.95, 15.1, 25.05, 27)], {r: E_ for r in ["看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]}, ""),
    ("東の縦廊下", "廊下", [(97.95, 15.1, 100.05, 27)], {r: E_ for r in ["看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]}, ""),
    ("外来の奥の職員通路", "廊下", [(0, 57.9, 100.05, 60), (97.95, 31, 100.05, 57.9)],
     {r: E_ for r in ["看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]}, "東の縦廊下（北）を含む"),
]
CROSS_ONLY = {"職員の中廊下": {"外来の患者": (44, 13, 52, 15.1), "化学療法の患者": (44, 13, 52, 15.1),
                        "救急の患者": (44, 13, 52, 15.1)},  # 救急の患者は正面玄関から（2026-11-11 依頼149 の2）
              # 病棟から X線室へ来た患者は、救急の待合と処置の場所を通り抜けるだけ（2026-11-11 依頼149 の3）
              "救急の待合": {"外来の患者": (100.1, 20, 120, 27)},
              "救急の処置の場所": {"外来の患者": (100.1, 6, 120, 20)}}


def check(data):
    """家具が部屋からはみ出していないか・道や扉の前をふさいでいないかを確かめる"""
    probs = []
    rooms = {r["name"]: r["rect"] for r in data["layout"]["rooms"]}
    for f in FUR:
        x0, y0, x1, y1 = rooms[f["room"]]
        a = f["rect"]
        if a[0] < x0 - 1e-6 or a[1] < y0 - 1e-6 or a[2] > x1 + 1e-6 or a[3] > y1 + 1e-6:
            probs.append(f"はみ出し：{f['room']} {f['name']} {a}")
    for d in DOORS + [{"room": s["room"], "at": dd["at"], "w": dd["w"], "name": s["name"]} for s in SUB for dd in s["doors"]]:
        x, y = d["at"]
        for f in FUR:
            a = f["rect"]
            if a[0] - 0.6 < x < a[2] + 0.6 and a[1] - 0.6 < y < a[3] + 0.6 and f["kind"] != "置き台":
                probs.append(f"扉の前に家具：{d['room']} {d.get('name','')} {d['at']} ← {f['name']}")
    return probs


def main(out_dir):
    place_wheelchair_spaces()
    mh.apply_variant("h0b")
    edges = mh.build_graph("h0b")
    out = Path(out_dir)
    out.mkdir(parents=True, exist_ok=True)
    rooms = [{"name": n, "rect": [x0, y0, x1, y1], "kind": k} for n, x0, y0, x1, y1, k in mh.ROOMS + EXTRA_ROOMS]
    routes = {}
    plan = {"green": ("救急室の処置室", "検査部の検体受付", "検体（救急・病棟）"),
            "blue": ("内科外来の処置室", "検査部の検体受付", "採血管・PCR/抗原・尿"),
            "blue_back": ("内科外来の処置室（裏口）", "検査部の検体受付", "採血管・PCR/抗原・尿"),
            "red": ("薬剤部の払い出し口", "外来化学療法室の受付", "抗がん剤・前投薬")}
    for key, (a, b, carries) in plan.items():
        d, path = mh.shortest(edges, mh.POINTS[a][:2], mh.POINTS[b][:2])
        hall_m = sum(mh.crowded_len(p[0], q[0]) for p, q in zip(path, path[1:]) if mh.on_hall(p, q))
        ia, ib = INSIDE[a][::-1], INSIDE[b]
        full = ia[:-1] + path + ib[1:]
        routes[key] = {"from": a, "to": b, "carries": carries, "distance_m": round(mh.true_len(full), 2),
                       "corridor_m": round(mh.true_len(path), 2),
                       "in_room_m": {"from": round(mh.true_len(INSIDE[a]), 2), "to": round(mh.true_len(INSIDE[b]), 2)},
                       "crowded_hall_m": round(hall_m, 2), "path": [[round(v, 2) for v in p] for p in full]}
    base = json.loads(Path(__file__).with_name("hospital_h0b.json").read_text(encoding="utf-8"))
    data = {
        "meta": dict(base["meta"], name="仮の病院 h1（外来の階・部屋の中まで）", variant="h1",
                     schema_version="hospital-layout/0.2",
                     based_on="hospital_h0b.json（部屋・廊下・点は同じ）",
                     interior_basis="仮置き（典型の並びをコードで起こした。実在の病院ではない）",
                     wall_h=WALL_H),
        "layout": dict(base["layout"], rooms=rooms),
        "interior": {"subrooms": SUB, "furniture": FUR, "openings": OPEN, "doors": DOORS, "inner_passages": INNER},
        "routes": routes,
        "distances": {k: round(v + mh.true_len(INSIDE[k.split("|")[0]]) + mh.true_len(INSIDE[k.split("|")[1]]), 2)
                      for k, v in base["distances"].items()},
        "stops": {n: [list(p) for p in pts] for n, pts in INSIDE.items()},
        "access": {"roles": ROLES, "levels": {"座る": "座る・寝る・腰を落ち着ける", "入る": "通る・立ち寄る"},
                   "zones": [{"name": n, "room": r, "rects": [list(x) for x in rs], "who": w, "note": nt}
                             for n, r, rs, w, nt in ACCESS],
                   "cross_only": {k: {r: list(v) for r, v in d.items()} for k, d in CROSS_ONLY.items()},
                   "basis": "仮置き（典型の動き方。病院ごとに違う）"},
    }
    data["meta"]["distance"] = "部屋の中の台の前で止まる点から、扉 → 廊下の中心線 → 扉 → 台の前まで（縦横だけ）。h0・h0b は扉から扉まで"
    probs = check(data) + check_inside()
    (out / "hospital_h1.json").write_text(json.dumps(data, ensure_ascii=False, indent=1), encoding="utf-8")
    draw(data, out / "hospital_h1.png")
    for k, r in routes.items():
        print(k, r["from"], "→", r["to"], r["distance_m"], "m（廊下", r["corridor_m"], "＋部屋の中", r["in_room_m"], "）")
    print("小部屋", len(SUB), "家具", len(FUR), "扉", len(DOORS) + sum(len(s["doors"]) for s in SUB))
    print("確かめ：", "問題なし" if not probs else "\n".join(probs))
    views = [("内科外来とお薬の窓口・総合受付の西", (-0.5, 12.5, 44.5, 60.5)),
             ("ほかの外来", (41.5, 26.0, 98.5, 60.5)),
             ("外来化学療法室と救急室", (96.0, -0.5, 120.5, 60.5)),
             ("薬剤部・倉庫・正面玄関（職員の中廊下の西）", (-0.5, -0.5, 53.0, 28.0)),
             ("医事・中央検査室・生理検査（職員の中廊下の東）", (51.5, -0.5, 101.0, 28.0))]
    files = []
    for i, (t, v) in enumerate(views):
        f = out / f"_view{i}.png"
        draw(data, f, view=v, title=t)
        files.append(f)
    try:
        from PIL import Image
        ims = [Image.open(f) for f in files]
        wmax = max(im.width for im in ims)
        canvas = Image.new("RGB", (wmax, sum(im.height for im in ims) + 40 * len(ims)), "white")
        y = 0
        for im in ims:
            canvas.paste(im, (0, y))
            y += im.height + 40
        canvas.save(out / "hospital_h1_rooms.png")
        for f in files:
            f.unlink()
    except ImportError:
        pass


def draw(data, path, view=None, title=None):
    import matplotlib
    matplotlib.use("Agg")
    import matplotlib.pyplot as plt
    from matplotlib import font_manager
    from matplotlib.patches import Rectangle
    for f in font_manager.findSystemFonts():
        if "NotoSansCJK-Regular" in f or "NotoSansJP" in f:
            font_manager.fontManager.addfont(f)
            plt.rcParams["font.family"] = font_manager.FontProperties(fname=f).get_name()
            break
    col = {"外来": "#f3f5f8", "届け先": "#e6effc", "出発": "#e8f5eb", "混む所": "#fff4e3", "その他": "#f5f5f5"}
    fcol = {"ベンチ": "#c9a46a", "いす": "#c9a46a", "机": "#9aa6b2", "ベッド": "#8fc7b8", "カウンター": "#7f8c99",
            "棚": "#6b7480", "分析装置": "#5d6b7a", "リクライニングチェア": "#8fc7b8", "台": "#9aa6b2",
            "機器": "#5d6b7a", "精算機": "#5d6b7a", "置き台": "#e0472f", "台車": "#9aa6b2", "島": "#7f8c99", "車いすの場所": "#f2b705"}
    rc = {"green": "#2e9d4f", "blue": "#2f6fd6", "blue_back": "#6c4fd6", "red": "#d24b3e"}
    big = view is None
    fig, ax = plt.subplots(figsize=(17, 9.4) if big else (10, 6.6), dpi=150)
    for r in data["layout"]["rooms"]:
        x0, y0, x1, y1 = r["rect"]
        ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc=col[r["kind"]], ec="#3c4650", lw=1.6))
    for c in data["layout"]["corridors"]:
        x0, y0, x1, y1 = c["rect"]
        ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="#ffffff", ec="none"))
    for p in data["interior"]["inner_passages"]:
        x0, y0, x1, y1 = p["rect"]
        ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="#fbfbfb", ec="none"))
    for s in data["interior"]["subrooms"]:
        x0, y0, x1, y1 = s["rect"]
        ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="none", ec="#3c4650", lw=1.0))
    for f in data["interior"]["furniture"]:
        x0, y0, x1, y1 = f["rect"]
        ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc=fcol[f["kind"]], ec="none"))
    # 開いている壁と扉は、壁の線の上に白い線を引いて見せる
    rooms = {r["name"]: r["rect"] for r in data["layout"]["rooms"]}
    for o in data["interior"]["openings"]:
        x0, y0, x1, y1 = rooms[o["room"]]
        a, b = o["span"]
        if o["side"] in "NS":
            y = y1 if o["side"] == "N" else y0
            ax.plot([a, b], [y, y], color="#ffffff", lw=2.6, solid_capstyle="butt")
        else:
            x = x1 if o["side"] == "E" else x0
            ax.plot([x, x], [a, b], color="#ffffff", lw=2.6, solid_capstyle="butt")
    alld = [(d["at"], d["w"]) for d in data["interior"]["doors"]] + \
           [(dd["at"], dd["w"]) for s in data["interior"]["subrooms"] for dd in s["doors"]]
    for (x, y), w in alld:
        # 扉がどちら向きの壁にあるか：近くの縦の壁か横の壁かで決める
        horiz = any(abs(y - r[1]) < 1e-3 or abs(y - r[3]) < 1e-3 for r in list(rooms.values()) +
                    [s["rect"] for s in data["interior"]["subrooms"]])
        vert = any(abs(x - r[0]) < 1e-3 or abs(x - r[2]) < 1e-3 for r in list(rooms.values()) +
                   [s["rect"] for s in data["interior"]["subrooms"]])
        if horiz and not (vert and not horiz):
            ax.plot([x - w / 2, x + w / 2], [y, y], color="#ffffff", lw=3.0, solid_capstyle="butt")
        else:
            ax.plot([x, x], [y - w / 2, y + w / 2], color="#ffffff", lw=3.0, solid_capstyle="butt")
    off = {"green": 0.35, "blue": 0.0, "blue_back": 0.0, "red": -0.35}
    for k, r in data["routes"].items():
        xs = [p[0] for p in r["path"]]
        ys = [p[1] + off[k] for p in r["path"]]
        ax.plot(xs, ys, color=rc[k], lw=2.4 if big else 3.0, solid_capstyle="round", alpha=0.95)
        ax.plot(xs[0], ys[0], "o", color=rc[k], ms=8)
        ax.plot(xs[-1], ys[-1], "s", color=rc[k], ms=8)
    fs = 10 if big else 9
    for r in data["layout"]["rooms"]:
        x0, y0, x1, y1 = r["rect"]
        if view and not (view[0] <= (x0 + x1) / 2 <= view[2] and view[1] <= (y0 + y1) / 2 <= view[3]):
            continue
        if x1 - x0 < 3:  # 階段・設備の細い所は斜線だけで名前は出さない
            ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="none", ec="#b9c0c7", hatch="///", lw=0))
            continue
        ax.text(x0 + 0.5, y1 - 0.5, r["name"], ha="left", va="top", fontsize=fs, color="#1f2933", weight="bold",
                bbox=dict(fc="white", ec="none", alpha=0.8, pad=1))
    if not big:
        for s in data["interior"]["subrooms"]:
            x0, y0, x1, y1 = s["rect"]
            if view[0] <= (x0 + x1) / 2 <= view[2] and view[1] <= (y0 + y1) / 2 <= view[3]:
                nm = s["name"]
                if nm.endswith("の検査室"):
                    nm = nm[:-4]
                elif "の" in nm and len(nm) > 6:
                    nm = nm.split("の")[-1]
                ax.text((x0 + x1) / 2, (y0 + y1) / 2, nm, ha="center", va="center",
                        fontsize=7.5 if x1 - x0 > 5.5 else 6.5, color="#3c4650",
                        bbox=dict(fc="white", ec="none", alpha=0.7, pad=0.5))
        for f in data["interior"]["furniture"]:
            if f["kind"] == "置き台":
                x0, y0, x1, y1 = f["rect"]
                if view[0] <= x0 <= view[2] and view[1] <= y0 <= view[3]:
                    ax.annotate(f["name"], ((x0 + x1) / 2, (y0 + y1) / 2), xytext=(8, 10), textcoords="offset points",
                                fontsize=8, color="#b8321e")
    ax.add_patch(Rectangle((0, 0), mh.W, mh.D, fc="none", ec="#1f2933", lw=2.2))
    if big:
        nm = {"green": "緑", "blue": "青（表）", "blue_back": "青（裏口）", "red": "赤"}
        for i, (k, r) in enumerate(data["routes"].items()):
            ax.text(1 + (i % 2) * 52, -3 - (i // 2) * 2.3, "●", color=rc[k], fontsize=12, va="center")
            ax.text(3 + (i % 2) * 52, -3 - (i // 2) * 2.3, f"{nm[k]}  {r['from']} → {r['to']}  {r['distance_m']:.0f}m",
                    fontsize=10.5, va="center")
        ax.text(1, -8.2, "■ 赤い小さな台＝ロボットに積む所・受け取る所　茶＝待合のベンチ　黄＝車いすの場所　緑＝ベッド・点滴のいす　灰＝机・台・機器・棚",
                fontsize=9.5, color="#555", va="center")
        ax.set_xlim(-1, mh.W + 1)
        ax.set_ylim(-9.5, mh.D + 1)
    else:
        ax.set_xlim(view[0], view[2])
        ax.set_ylim(view[1], view[3])
    ax.set_aspect("equal")
    ax.axis("off")
    ax.set_title(title or f"{data['meta']['name']}（120m×60m）と搬送の4つの道", fontsize=15 if big else 12, loc="left")
    fig.savefig(path, bbox_inches="tight", facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main(sys.argv[1] if len(sys.argv) > 1 else ".")
```


## 作業ログ ― 通れなかった41人を出し、病院の1日を撮り直す（依頼154）

### 行けない行き先は0人になり、看護師は検査の所の中で待つようになった

依頼154 では、直したフロアの形を Unity に読ませ、依頼149 で出せなかった人を出しました。救急の患者18人は正面玄関から職員の中廊下を横切って救急の待合へ、病棟の患者のうち17人は X線室へ、ほかの階の職員6人はそれぞれの持ち場へ歩きます。病棟の患者に付き添う看護師は、患者と同じ速さで一緒に歩き、検査の所の中で患者のそば（0.7m）に立って待ちます。

【画像】LOG488_03_er_10am.png 10時ごろの救急の待合の寄り。救急の患者（黄色）が待合に座り、中央の処置の場所のまわりに職員（青）がいる。待合の椅子は濃いミント、車いすの場所は灰色の枠とバツ

| 確かめたこと | 結果 |
|---|---|
| 同じ条件で2回回した記録 | 18ファイルとも md5 が同じ |
| 瞬間移動・便・立ち往生 | 瞬間移動0・便16/16・立ち往生と助け0 |
| 入れない所にいた時間 | ロボット・人とも、役割ごとに0秒 |
| 行けない行き先・出さなかった人 | 種類ごとに全部0人 |
| 病棟の看護師が待った所 | 検査の所の中91回・大廊下の端0回（依頼149 は大廊下の端74回） |
| 救急の待合・処置の場所を通り抜けた人 | X線室へ行き来する病棟の患者17人だけ。1人あたり中央値 待合18.4秒・処置の場所36.8秒 |
| 大廊下 x70〜120（看護師が待っていた所）の人 | 1日の平均 2.48人 → 0.69人 |

大廊下 x0〜70 の人は、混む時間（8〜11時）で平均2.2人、午後で1.3人で、どちらも流れからの目安の中でした。30分ごとに見ると、混む時間に目安の下の端（2人）を少し下回る時間帯が3つ（1.8〜1.9人）、午後に1人を下回る時間帯が2つ（0.7・0.8人）あります。

### ロボットの問題は1日84件から132件に増え、37件は大廊下の東の端で起きた

問題の数は、走っている間の「1 急に止まった／2 ひやり／3 人がよけさせられた／4 立ち往生／5 横切り／止まりきれなかった」です（1日・秒速0.6m・種0。見た目なしの回が正）。

| 廊下だけの道・走っている間 | 依頼154 | 依頼149 | 依頼147 | 依頼143 |
|---|---|---|---|---|
| 全部 | 17／54／51／0／0／10（132件） | 9／37／37／0／0／1（84件） | 8／42／35／0／0／3（88件） | 23／49／31／2／1／5（111件） |
| 外来の大廊下 | 5／12／7／0／0／2 | 1／5／4／0／0／0 | 1／7／2／0／0／0 | 21／48／28／2／1／5 |
| 職員の廊下 | 4／27／33／0／0／3 | 5／24／28／0／0／0 | 5／30／28／0／0／2 | 2／1／2／0／0／0 |
| 部屋の中 | 8／15／11／0／0／5 | 3／8／5／0／0／1 | 2／5／5／0／0／1 | 0／0／1／0／0／0 |

増えた48件のうち37件は、大廊下の東の端（化学療法の受付の台と、東の縦廊下の口のまわり）で起きました。依頼149 では、ここは1件でした。相手はほとんどが職員で、ひやり54件のうち46件、人がよけさせられた51件のうち48件、止まりきれなかった10件は全部が職員でした。救急の患者・病棟の患者が相手の問題は少なく、患者と付き添いが相手のひやりは7件です。

【画像】LOG488_04_east_10am.png 10時ごろの引き（東半分）。待合の椅子は濃いミント、車いすの場所は灰色の枠とバツ。問題が増えたのは、この絵の右側にある大廊下の東の端のまわり

### 増えた原因は、通り抜けのために足した所が、職員のふだんの行き先にもなったこと

Unity の決まりでは、職員が持ち場から動くとき、行き先を「その職員が入れる所」から選びます（依頼147 からの決まり）。付き添いと通り抜けのために「入る」を足したので、職員はその所へふだんの行き来でも行くようになりました。1日の行き来は、検査技師→救急の処置の場所が0回から46回、検査技師→X線室が0回から36回、医師→化学療法の受付・待合が0回から14回、看護師→X線室が0回から14回などです。その行き来が、化学療法の受付の前と東の縦廊下の口で、ロボットと会っていました。

**今回の目的は「通り抜けられるようにする」ことで、職員のふだんの行き先を増やすことではありませんでした。** そのため次の回で、ふだんの行き先はもとの持ち場の所だけにし、足した所は付き添いと通り抜けのときだけ入る形に分けます。

### 化学療法のトイレへの通り道は幅0.35m で、人が通れなかった

化学療法の患者を化学療法室のトイレへ行かせようとしたところ、トイレの入口の前が、準備室とトイレのあいだの幅0.5m の細い長方形だけで、壁の厚みを引くと0.35m しかなく、歩いて行けませんでした。サイト側でフロアの形を作ったときの間違いです。Claude Code はこの項目を止め、前と同じく近い方のトイレ（西。点滴の場所から約85m）へ行かせて数えています（1日8人）。次の回で、通り道を点滴の台を押して通れる幅（約0.9m 以上）に直します。

【画像】LOG488_02_flow154.png 依頼154 の静止画16枚。10時ごろと14時ごろの、真上・引き西・引き東と、エレベーターホール・中待合・総合受付と会計の待合・救急の待合・正面玄関から救急へ行く道の寄り

#### 依頼154 の依頼文

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 480
---
# 依頼154：病院の行けない所を直し、救急・病棟の患者と職員を通して、1日を撮り直す

依頼149 の報告（claude_code_reports/20261111_hospital_149.txt）の「決めてほしいこと」1〜7 に、エイイチと決めた答え（2026-11-11）：
1 h1 の書かれていなかった違い3つ（点滴の場所の 0.5 m の長方形・車いすの場所の 0.1 m・血圧脈波の検査室の家具が無い）：**このままでよい**
2 救急の患者：**正面玄関の前の職員の中廊下（x44〜52）を横切ってよい**ことにした（access の cross_only に救急の患者を足した）。1日 18 人を出す
3 病棟から検査に来る患者：**X線室に入れる**ようにした（外来の患者を X線室に「入る」、救急の待合・救急の処置の場所は cross_only で通り抜けるだけ）。
  付き添いの看護師は **生理検査の待合・生理検査の検査室・X線室に入れる**ようにした。看護師は大廊下の端で待たず、患者と一緒に検査の所まで行き、終わるまでその所の中で待つ
4 ほかの階の職員：**通り抜けてよい**ことにした（検査技師→救急の処置の場所、医師→生理検査の待合・化学療法の受付・待合を「入る」）。1日 6 人を出す
5 化学療法の患者のトイレ：**化学療法のトイレ**を使う（params の people_flow.toilet.where）
6 大廊下の人数：crowd_people（60・20）は根拠のない仮置きで、この階の人の流れからは出ないと分かった。人の流れからの目安は **山場 2〜8人・午後 1〜3人**（assumptions の crowd_people.flow_estimate。params には入っていない）。依頼149 の 1.7〜2.6人はこの幅の中なので、**大廊下に人を増やすための動きは足さない**。比べる相手を crowd_people から この目安に替える
7 Unity で決めた値（unity_only149）：**このままでよい**

**今回は行けなかった人を通して、確かめの回を撮って見せるところまで。** 数える問題・ロボットの止まり方・人がロボットをよける決まりは変えない。
**配膳の場面・配膳の実行ファイル・配膳の台帳、配膳と共通の部品には触らない。** 変えるのは Assets/ServiceSim/Hospital/ の中だけ。見た目は依頼153 の色（椅子 #4FAE8E・車いすの場所＝灰色の枠とバツ）・決めたカメラのまま。
**瞬間移動はさせない**（エレベーターの中で現れ・消えるのはよい）。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、その項目は止めて報告に書き、ほかは続ける。**

## 正（読むもの）
- p2m-site main（c61174b）
  - フロアの形：`tools/hospital_layouts/hospital_h1.json`（md5 85fa5f0614b9e82cf7671e993c8751b8）。前の写し（0aa8db3c）との違いは access だけのはず：
    化学療法の受付・待合に医師「入る」／生理検査の待合に看護師・医師「入る」／生理検査の検査室に看護師「入る」／救急の処置の場所に検査技師「入る」／X線室に外来の患者・看護師「入る」／
    cross_only：職員の中廊下に救急の患者（x44〜52）、救急の待合に外来の患者（全体）、救急の処置の場所に外来の患者（100.1,6〜120,20）
  - 条件：`tools/hospital_sim/hospital-unity-params.json`（md5 8cb6451a3845f82b63787f25efbf8549）。前の写し（6a4da8a0）との違いは layout・layout_back_door の md5 と people_flow.toilet.value.where だけのはず
  - 大廊下の目安：`tools/hospital_sim/assumptions.json` の crowd_people.flow_estimate（読むだけ）

## 始める前に確かめること（違えば回さずに報告して終わる）
作業場所の保留 0（cs:123 のあと）・Unity と回し係が動いていない・KIOXIA がマウント済み・上の md5 と違いの中身

## 1. 作る
- 上の 2〜5 を入れる。依頼149 で出さなかった人（救急の患者 18人・X線室へ行く病棟の患者 17人・ほかの階の職員 6人）を出す
- 病棟の患者の付き添いの看護師は、検査の所の中で待つ形に変える（大廊下の南の端で立って待つ形はやめる）
- check-in（保留0）して、Builds/hospital154/ を作る。前の実行ファイルは残す

## 2. 確かめ（違えば 3 は撮らずに止める）
- 依頼149 と同じ確かめ（廊下だけ×2 で1バイト一致、瞬間移動0、便16/16、立ち往生、ロボット・人が入れない所0秒（役割ごと）、時間で切替×1）
- 飛ばした行き先・出さなかった人が 0 人になったこと（種類ごとの人数）
- 時刻ごと（30分ごと）の所ごとの人の数（依頼149 の表と同じ形）。外来の大廊下 x0〜70 の人数を、依頼149 と flow_estimate（山場 2〜8・午後 1〜3）と並べる。依頼149 で看護師が待っていた大廊下 x70〜120 の人数がどう変わったか
- 救急の待合・救急の処置の場所を通り抜けた外来の患者の数と、そこにいた時間
- 問題の数を依頼143・147・149 と並べる（区間：外来の大廊下・職員の廊下・部屋の中・台の前）。数は見た目なしの回を正とする

## 3. 撮る（依頼149 と同じ）
- 静止画：依頼149 と同じ12枚（cams149.txt）に、寄り：救急の待合と正面玄関から救急へ行く道（10:00 ごろ・14:00 ごろ）を足す
- 動画：廊下だけ・0.6m/秒・種0 の1日の早回し（依頼149 と同じ作り）
- 並べた図
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital/20261111_flow154/。並べた図・静止画・動画は iCloud の …/サービスロボット導入シミュレーション/hospital/flow154/ にも写し、md5 を確かめる

## 報告
claude_code_reports/20261111_hospital_154.txt に：始める前の確かめ、1 で作ったもの、2 の確かめ（表）、3 のカメラの値と動画の長さ、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：止めた（決めてほしいことあり）」／「判定：失敗」
```

#### 依頼154 の結果（依頼箱の結果の全文）

```markdown
# 結果：20261111_154_病院の行けない所を直し救急と病棟の患者を通して1日を撮り直す

- 状態：完了
- 始めた時刻：2026-11-11 18:58
- 終わった時刻：2026-11-11 22:16（197分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 2a856e44-4991-42d8-b7cb-61a14438b33f`

## Claude Code の報告

**やったこと**
- 始める前の確かめ：作業場所は cs:123 で保留 0、Unity も回し係も動いていませんでした。KIOXIA はマウント済みです。サイト（c61174b）の h1 と params は md5 が一致し、前の写しとの違いも依頼に書かれたとおりでした（h1 は access だけ、params は md5 2つとトイレの行き先だけ）。
- 新しい引数は作らず、-h147People flow の中身を直しました。
  - 救急の患者 18 人を正面玄関から出し、職員の中廊下 x44〜52 を横切らせました。
  - X線室へ行く病棟の患者 17 人と、ほかの階の職員 6 人も出しました。
  - 付き添いの看護師は大廊下の端で待たず、患者と同じ速さで一緒に歩き、検査の所の中で患者のそばに立って待つ形にしました。
  - 化学療法の患者のトイレは、化学療法のトイレへ行く形にしました（ただし下のとおり行けませんでした）。
- cs:124 に check-in し、Builds/hospital154 を作りました（保留 0）。前の実行ファイルは残しています。
- 確かめの回を 5 本回し、撮る回、静止画 16 枚、並べた図、1日の早回しの動画まで作りました。

**結果**
- 依頼149 と同じ確かめは、すべて通りました。
  - 廊下だけの 2 回は、記録 18 ファイルの md5 がすべて同じでした。見た目ありの回も同じでした。hall の回は依頼143 と 9 ファイルとも同じでした。
  - 瞬間移動は 0、便は 16/16、立ち往生と助けは 0 でした。ロボット・人とも、入れない所にいた時間は役割ごとに 0 秒でした。時間で切替の回も同じです。
- 飛ばした行き先と出さなかった人は、種類ごとに全部 0 人になりました。病棟の看護師が待った所は、検査の所の中が 91 回、大廊下の端が 0 回です（依頼149 は大廊下の端で 74 回）。
- 救急の待合と救急の処置の場所を通り抜けたのは、X線室へ行き来する病棟の患者 17 人だけでした。いた時間は 1 人あたり中央値で待合 18.4 秒、処置の場所 36.8 秒です。外来の患者・付き添い・見舞いの人は 0 人でした。
- 外来の大廊下 x0〜70 の人数は次のとおりです（目安は flow_estimate）。
  - 混んでいる時間帯は平均 2.2 人で、目安 2〜8 の中です（依頼149 は 1.8 人）。
  - 午後は平均 1.3 人で、目安 1〜3 の中です（依頼149 は 1.6 人）。
  - 1日の平均は 1.64 人です（依頼149 は 1.58 人）。
  - 30分ごとに見ると、目安の下の端を少し下回る時間帯が混んでいる時間帯に 3 つ（1.8〜1.9 人）、午後に 2 つ（0.7・0.8 人）あります。
- 看護師が待っていた大廊下 x70〜120 は、1日の平均 2.48 人から 0.69 人に減りました。
- 問題の数（廊下だけ、走っている間）は 9／37／37 から 17／54／51 に増えました。増えた 48 件のうち 37 件は、大廊下の東の端（化学療法の受付の台と東の縦廊下の口のまわり）です。
- 撮る回は、見た目ありの確かめの回と頭からバイト一致しました（実時間 112.8 分）。
- 動画は 48,281 コマ、32 分 11 秒です。
- 新しく足した寄り 2 つのうち、正面玄関から救急へ行く道は道が x42〜121 に広がるため、距離が 99.7 m になりました。引きとほぼ同じ大きさで、歩いている救急の患者は小さく写ります。
- iCloud の hospital/flow154/ に写した 18 ファイルは、すべて md5 が一致しました。
- 依頼149 の報告は iCloud の報告の所に見当たらなかったので、KIOXIA にある写しを読みました。
- 報告は claude_code_reports/20261111_hospital_154.txt に書き、KIOXIA の 20261111_flow154/ にも写しを置きました。最後の行は「判定：止めた（決めてほしいことあり）」です。

**変えたファイル**（すべて Assets/ServiceSim/Hospital/ の中）
- 直したもの：HospitalRun.cs、HospitalRun147.cs、HospitalRun149.cs、Editor/HospitalSetup.cs、Hospital.unity
- 場面を作り直したときに書き直されたもの：Materials/H127_*.mat の 7 つ
- 足したもの：HospitalRun154.cs、Data/hospital_h1-154.json、Data/hospital-unity-params-154.json（それぞれ .meta も）

**決めてほしいこと**
1. 化学療法のトイレへは歩いて行けません。入口の前が幅 0.5 m の長方形だけで、壁の厚みを引くと 0.35 m しかなく、人の通れる幅がありません。この項目は止めました。今は依頼149 と同じく近い方のトイレへ行かせて、その数を数えています（1日 8 人とも西）。どうするか。
2. 職員がふだん持ち場から動く行き先は、その職員が「入る」所から選ぶ決まりです。そのため、付き添いや通り抜けのために足した「入る」の所（化学療法・生理検査・救急の処置の場所・X線室）にも、ふだんの行き来で行くようになりました。問題の数が増えたのはこのためです。これでよいか。
3. Unity で決めた値でよいか。
   - 看護師が待つ点：患者の行き先の所の中で、患者のそば 0.7 m の 4 方向。無ければその所の中の点。
   - 看護師は患者と同じ速さ（遅い方）で歩く。
   - X線室には座る家具が無いので、患者は立って待つ。
   - 化学療法のトイレへ行けないときは、近い方のトイレへ行く。
```

#### report154.txt（依頼154 の報告の全文）

```text
依頼154 報告　2026-11-11
病院の行けない所を直し、救急・病棟の患者と職員を通して、1日を撮り直す

■ 0. 始める前の確かめ（進めた）
- 作業場所：cs:123 が頭、保留の変更 0
- 動いている物：Unity のエディタ・画面なしの Unity・回し係はなし（動いていたのは Unity Hub だけ）
- KIOXIA：マウント済み
- p2m-site main：c61174b241fb…（依頼の c61174b）
  tools/hospital_layouts/hospital_h1.json　md5 85fa5f0614b9e82cf7671e993c8751b8（一致）
  tools/hospital_sim/hospital-unity-params.json　md5 8cb6451a3845f82b63787f25efbf8549（一致）
  tools/hospital_sim/assumptions.json の crowd_people.flow_estimate：混んでいる（8〜11時）2〜8 人・空いている（午後）1〜3 人（読むだけ）
- 前の写しとの違い（中身を項目ごとに比べた）
  h1（0aa8db3c → 85fa5f06）：違いは access だけで、依頼に書かれたものと同じだった
    化学療法の受付・待合に医師「入る」／生理検査の待合に看護師・医師「入る」／生理検査の検査室に看護師「入る」／救急の処置の場所に検査技師「入る」／X線室に外来の患者・看護師「入る」／
    cross_only：職員の中廊下に救急の患者 [44,13,52,15.1]、救急の待合に外来の患者 [100.1,20,120,27]（全体）、救急の処置の場所に外来の患者 [100.1,6,120,20]
    （ほかに、上の5つの所の note の文が書き足されていた。動きには使わない）
  params（6a4da8a0 → 8cb6451a）：layout.md5・layout_back_door.md5 と people_flow.toilet.value.where だけ（「化学療法の患者は化学療法のトイレ」が足された）

■ 1. 作ったもの
- 新しい引数は作らず、-h147People flow（既定）の中身を直した。flow は Data/ に足した hospital_h1-154.json（85fa5f06）と hospital-unity-params-154.json（8cb6451a）で建てる
  （中身はサイトと同じ md5。名前だけ変えた。依頼147・149 の写しは残した）。-h147People hall は依頼143 の形のまま
- 救急の患者（答え 2）：正面玄関から入り、職員の中廊下 x44〜52 を横切って玄関の通り → 大廊下 → 救急の待合 → 処置ベッド → 正面玄関へ戻る。1日 18 人を出した
  （正面玄関から救急の待合まで歩いた時間 中央値 82 秒・72〜93 秒）
- 病棟から検査に来る患者（答え 3）：X線室へ行く人（17 人）も出した。X線室は access の通り抜け（救急の待合・救急の処置の場所は cross_only）で入り、
  X線室の中の点に立って待つ（X線室には座る家具が無いので、依頼147 からの「立って待つ」の決まりのまま）
- 付き添いの看護師（答え 3）：大廊下の南の端で待つ形をやめた。患者と同じ速さ（遅い方）で一緒に歩き、
  患者の行き先の所（生理検査の待合・生理検査の検査室・X線室）の中で、患者のそば 0.7 m（4方向。同じ所の中・ロボットの帯の外）に立って待つ。
  無ければその所の中の点（種で選ぶ）。終わったら患者と一緒にエレベーターへ戻る
- ほかの階の職員（答え 4）：access の通り抜けで持ち場へ行けるようになったので、依頼149 で出さなかった 6 人も出した（作りは変えていない）
- 化学療法の患者のトイレ（答え 5）：化学療法のトイレへ行くようにした。ただし歩いて行けなかった（下の 2 の (8)・決めてほしいこと 1）。
  歩いて行けないときは依頼149 と同じ近い方のトイレ（西・東）へ行き、その数を summary の flow_chemo_toilet_fallback に数える形にした（この項目は止めた）
- 大廊下の人数（答え 6）：人を増やす動きは足していない。表の比べる相手を crowd_people から flow_estimate に替えた
- 数える問題・ロボットの止まり方・人がロボットをよける決まりは変えていない。配膳の場面・実行ファイル・台帳・共通の部品には触っていない。見た目（依頼153 の色・印）・カメラの決め方も同じ
- 記録に足したもの（flow だけ）：er_cross154.csv（救急の待合・救急の処置の場所に入った外来の患者の役割の人ごとに、所・いた秒・最初と最後の時刻）。
  summary に flow_er_cross_people・flow_chemo_toilet_fallback・flow_ward_nurse_inside・flow_ward_nurse_inside_random・unity_only154
- check-in：cs:124（保留 0）。実行ファイル Builds/hospital154/HospitalSim.app（build_info：unity_cs cs:124・pending_changes 空）。Builds/hospital127〜153 は残した。
  変えたのは Assets/ServiceSim/Hospital/ の中だけ：HospitalRun.cs・HospitalRun147.cs・HospitalRun149.cs・Editor/HospitalSetup.cs・Hospital.unity（場面を作り直し）・
  Materials/H127_*.mat 7つ（場面を作り直したときの書き直し。依頼149 と同じ）、足した HospitalRun154.cs・Data/hospital_h1-154.json・Data/hospital-unity-params-154.json（＋.meta）
  （check-in の前に同じ中身で作った試しの実行ファイルで1日を回した回 full1 も、check154a と記録 18 の md5 がすべて同じ）

■ 2. 確かめ（cs:124。廊下だけ・0.6m/秒・種0 を2回＝check154a・b、時間で切替＝check154c、見た目あり・カメラなし＝check154L、hall＝h143repro154。どれも completed）
(1) 同じ種の2回：check154a と check154b の記録 18（access147・build143・crowd・elevator149・er_cross154・events・legs・moves・people147・rescues・robot_track・
    robot_zones・seats147・staff147・staff_moves147・toilet149・trips・zones147）の md5 がすべて同じ。summary.json の違いは回の名前と wall_s だけ
    見た目あり（check154L）も check154a と記録 18 の md5 がすべて同じ（summary の違いは回の名前と wall_s だけ）
    -h147People hall：h143repro154 と依頼143 の check143a の記録 9（build143・crowd・events・legs・moves・rescues・robot_track・robot_zones・trips）の md5 がすべて同じ。
    summary.json の違いは回の名前・cs・wall_s だけ
(2) 瞬間移動：ロボット 0（1コマ最大 0.030 m）・人 0（最大 廊下だけ 0.084 m・時間で切替 0.128 m）。エレベーターの人はかごの中で現れ・消える
(3) 便：2回とも 16 便がすべて走り終わった（途中 0・走れなかった 0・遅れ 0）。最後の便が戻った 廊下だけ 31630.9 秒・時間で切替 31630.1 秒。移る 5 回
(4) 立ち往生：ロボット 0・助け 0（2回とも）。人が 180 秒動けず道を引き直した 0・0。行き先の近くで止まって待った 9・12
(5) ロボットの中心が access でロボットの入らない所に入った時間：0 秒（どの所にも入っていない時間も 0 秒）
(6) 人が access で入れない所に入った時間（人の中心。役割ごと。2回とも。cross_only の長方形は入れる所として数える）：
    外来の患者 0・化学療法の患者 0・救急の患者 0・看護師 0・医師 0・薬剤師 0・検査技師 0・事務 0 秒。どの所にも入っていない時間も全部 0 秒。
    付き添いのない外来の患者が裏の通路にいた時間 0 秒
(7) 飛ばした行き先・出さなかった人（種類ごと。2回とも同じ）
    種類              時刻表の人数  出した  出さなかった  飛ばした行き先   （依頼149 で出さなかった人）
    外来の患者           390        390        0             0
    付き添い             192        192        0             0
    化学療法の患者        20         20        0             0
    救急の患者            18         18        0             0             （18）
    病棟の患者            54         54        0             0             （17：X線室へ行く人）
    付き添いの看護師      54         54        0             0             （17）
    見舞いの人           105        105        0             0
    ほかの階の職員       180        180        0             0             （6：X線室の検査技師 3・生理検査の検査室の医師 2・外来化学療法室の医師 1）
    （summary：flow_not_spawned {}・flow_er_not_spawned 0・flow_skips {}）
    回の終わりにまだ中にいた人（廊下だけ）：外来の患者 15・付き添い 10・化学療法の患者 3・救急の患者 3・病棟の患者 2・看護師 2・見舞い 1・ほかの階の職員 1
    いた時間の中央値：救急の患者 84.7 分（66〜177 分）・病棟の患者 30.2 分・ほかの階の職員 3.5 分。病棟の患者の行き先 生理検査 37・X線室 17（歩く 28・車いす 26）
    病棟の看護師が待った所：検査の所の中で患者のそば 91 回・その所の中の点 0 回・大廊下の端 0 回（依頼149 は大廊下の端 74 回）
(8) 化学療法の患者のトイレ：化学療法の患者 8 人がトイレに行くと決まり、8 人とも化学療法のトイレへ歩いて行けず、トイレ（西）へ行った（2回とも）。
    化学療法のトイレ（小部屋「トイレ」x114.5〜120・z53〜60）の扉は西の辺 (114.5, 55)。その前は access「化学療法の点滴の場所」の幅 0.5 m の長方形 [114, 53, 114.5, 60]
    （依頼149 の決めてほしいこと 1 の①）だけで、東は準備室の壁（x114）、西はトイレの壁（x114.5）。壁の厚み 0.15 m を引くと通れる幅は 0.35 m で、
    人の歩く面（直径 0.5 m の円で焼く）が無い。人に聞かないと決められない（形を変えることになる）ので、この項目は止めた（決めてほしいこと 1）
(9) 時刻ごと（30分ごとの平均。1分ごとに数えた人数。患者・付き添い・職員・エレベーターから来た人の全部。廊下だけ check154a。形は依頼149 と同じ）
    エレベーターホール＝ホールとかごの中、トイレ＝西・東・化学療法のトイレの合計。部屋の中には持ち場の職員 約130人を含む
    時刻          大廊下x0〜70 依頼149 目安(flow_estimate) 大廊下x70〜120 依頼149  待合   中待合  部屋の中  職員の廊下  玄関・玄関の通り  エレベーターホール  トイレ   全部
    7:30〜8:00     1.1        1.2          1〜3               0.5        1.8       5.5   12.6    137.8       0.9           3.0              0.1          0.5   162.1
    8:00〜8:30     2.1        1.7          2〜8               0.9        2.3      14.1   51.9    147.2       1.1           3.9              0.1          2.4   223.7
    8:30〜9:00     1.9        1.1          2〜8               1.0        1.6      23.0   84.3    159.4       1.4           4.4              0.2          3.5   279.1
    9:00〜9:30     1.9        1.7          2〜8               0.8        2.4      37.1   90.4    168.0       1.8           4.8              0.0          4.8   309.6
    9:30〜10:00     1.8        2.6          2〜8               0.7        2.3      38.5  108.3    178.7       0.8           4.4              0.3          2.9   336.5
    10:00〜10:30     2.8        1.7          2〜8               1.0        3.0      44.3  114.3    177.3       1.1           3.5              0.1          2.9   347.3
    10:30〜11:00     2.7        2.0          2〜8               0.6        2.2      36.7  121.5    180.2       1.4           4.3              0.2          4.4   352.1
    11:00〜11:30     1.4        1.7          1〜3               0.9        2.6      37.2  131.6    178.8       1.4           3.5              0.1          3.7   358.6
    11:30〜12:00     1.7        1.4          1〜3               0.7        2.5      35.0  123.9    176.0       0.8           3.7              0.2          2.8   344.9
    12:00〜12:30     1.2        1.5          1〜3               0.5        4.5      40.1  105.5    179.9       1.1           3.1              0.2          1.7   333.4
    12:30〜13:00     1.9        0.9          1〜3               0.7        3.6      42.5   69.7    183.4       0.9           3.5              0.3          1.5   304.4
    13:00〜13:30     2.2        1.6          1〜3               0.7        2.4      42.6   54.8    179.1       1.2           3.6              0.2          1.5   285.8
    13:30〜14:00     1.0        1.6          1〜3               0.8        4.1      34.5   50.5    172.1       1.5           3.7              0.2          2.5   266.8
    14:00〜14:30     0.7        2.0          1〜3               0.3        2.9      24.5   39.9    162.4       0.7           4.2              0.2          1.4   234.3
    14:30〜15:00     1.7        2.1          1〜3               0.5        0.9      19.4   34.0    154.8       1.2           3.2              0.1          0.7   215.7
    15:00〜15:30     1.1        1.6          1〜3               0.8        2.8      16.3   29.4    155.7       1.0           2.9              0.3          1.3   209.0
    15:30〜16:00     0.8        0.9          1〜3               0.4        1.6      19.3   25.7    154.5       0.9           3.2              0.1          1.3   206.3
    16:00〜16:30     1.3        1.2          1〜3               0.5        1.0      12.5   17.6    152.6       0.8           2.9              0.1          0.4   188.7
    外来の大廊下 x0〜70：混んでいる時間帯（8:00〜11:00）平均 2.2 人・最大 6 人（依頼149 1.8 人・7 人／目安 2〜8 人）、
      空いている時間帯（7:30〜8:00・11:00〜16:30）平均 1.4 人・最大 6 人（依頼149 1.5 人・7 人）、午後（13:00〜16:30）平均 1.3 人・最大 6 人（依頼149 1.6 人・5 人／目安 1〜3 人）。
      1日の平均 1.64 人（依頼149 1.58 人）。30分ごとの平均は、混んでいる時間帯 1.8〜2.8 人（目安 2〜8 の中、ただし 2 を下回る時間帯が 3 つ：1.9・1.9・1.8）、
      午後 0.7〜2.2 人（目安 1〜3。下回るのは 14:00〜14:30 の 0.7 と 15:30〜16:00 の 0.8）
    時間で切替（check154c）：1日の平均 1.77 人（依頼149 の check149c 2.03 人）、混んでいる時間帯 平均 2.0 人・最大 6 人、空いている時間帯 平均 1.6 人・最大 6 人、午後 平均 1.5 人・最大 6 人
    大廊下 x70〜120（依頼149 で病棟の看護師が待っていた所）：1日の平均 2.48 → 0.69 人・最大 8 → 4 人（時間で切替 2.49 → 0.72 人）。30分ごとでは 0.9〜4.5 人 → 0.3〜1.0 人
    職員の廊下：平均 1.13 人・最大 5 人（依頼149 1.16 人・6 人）
(10) 救急の待合・救急の処置の場所を通り抜けた外来の患者（の役割の人。2回とも同じ）
    救急の待合：17 人（全員 X線室へ行き来する病棟の患者）。そこにいた時間 合計 279 秒・1人（行きと帰りの計）中央値 18.4 秒・最長 20.8 秒
    救急の処置の場所：17 人（同じ 17 人）。合計 552 秒（時間で切替 555 秒）・1人 中央値 36.8 秒・最長 38.0 秒
    外来の患者・付き添い・見舞いの人で、ここに入った人は 0 人（行き先が無いため）
(11) 問題の数を依頼143・147・149 と並べる（1日・0.6m/秒・種0。1 急に止まった／2 ひやり／3 人がよけさせられた／4 立ち往生／5 横切り／止まりきれなかった。区間はロボットの中心がいた所。見た目なしの回が正）
    廊下だけ                依頼154（check154a）   依頼149（check149a）   依頼147（check147a）   依頼143（check143a）
      走っている間 全部      17／54／51／0／0／10    9／37／37／0／0／1      8／42／35／0／0／3      23／49／31／2／1／5
        外来の大廊下         5／12／7／0／0／2       1／5／4／0／0／0        1／7／2／0／0／0        21／48／28／2／1／5
        職員の廊下           4／27／33／0／0／3      5／24／28／0／0／0      5／30／28／0／0／2      2／1／2／0／0／0
        部屋の中             8／15／11／0／0／5      3／8／5／0／0／1        2／5／5／0／0／1        0／0／1／0／0／0
      台の前で待っている間   0／64／12／0／0／0      0／65／11／0／0／0      0／66／9／0／0／0       0／0／0／0／0／0
      速いままのひやり       54                      37                      42                      35
      助け                   0                       0                       0                       2
    時間で切替              依頼154（check154c）   依頼149（check149c）   依頼147（check147c）   依頼143（check143c）
      走っている間 全部      11／52／54／0／0／5     15／39／52／0／0／4     14／49／51／0／0／5     25／60／16／1／1／4
        外来の大廊下         4／19／11／0／0／2      5／8／7／0／0／0        3／10／6／0／0／2       23／49／13／0／1／3
        職員の廊下           3／21／36／0／0／1      8／24／40／0／0／1      7／35／38／0／0／2      2／11／3／1／0／1
        部屋の中             4／12／7／0／0／2       2／7／5／0／0／3        4／4／7／0／0／1        0／0／0／0／0／0
      台の前で待っている間   0／57／12／0／0／0      0／58／11／0／0／0      0／57／5／0／0／0       0／0／0／0／0／0
      速いままのひやり       52                      39                      49                      52
      助け                   0                       0                       0                       1
    （玄関の通りは 0。職員の廊下＝職員の中廊下・西／東の縦廊下・外来の奥の職員通路・東の縦廊下（北））
    相手（廊下だけ・走っている間）：ひやり 54 のうち職員 46・患者と付き添い 7・ストレッチャー 1。よけさせられた 51 のうち職員 48。止まりきれなかった 10 は全部職員
    増えた所：廊下だけで走っている間の問題は 84 → 132。増えた 48 のうち 37 は大廊下の東の端（ロボットの中心 x95 以上・z27 以上。化学療法の受付の台と東の縦廊下の口のまわり）で、
      依頼149 はここで 1 だった（時間で切替は 11 → 27）。
      職員が持ち場から動く行き先は、その職員が access で入れる所から選ぶ（依頼147 からの決まり）ので、今回 access が広がった所への動きが増えた
      （廊下だけ・1日：医師→化学療法の受付・待合 0→14・点滴の場所 0→8・生理検査の待合 0→10・検査室 0→8、看護師→生理検査の待合 0→14・検査室 0→15・X線室 0→14、
      検査技師→救急の処置の場所 0→46・X線室 0→36 など）。その行き来が化学療法の受付の前・東の縦廊下の口でロボットと会うことが多い（決めてほしいこと 2）。
      救急の患者・病棟の患者が相手の問題は少ない（患者と付き添いが相手のひやりは 7）

■ 3. 撮ったもの（色A・パネル付き・1920×1080。撮る回 rec154＝廊下だけ・0.6m/秒・種0・cs:124・車いすの場所の印あり）
- 撮る回の動きは見た目なしの確かめの回と同じ：rec154 は check154L と、流して書く記録（crowd・elevator149・events・robot_track・staff_moves147・toilet149・zones147）が
  撮り終えた時刻まで頭からバイト一致。access147・build143・moves・rescues・staff147 は md5 も同じ。legs・people147・robot_zones・seats147・trips・er_cross154 は
  回の終わりに書く表なので、撮り終えたところで止めた分だけ違う（依頼149 と同じ）。撮る回の実時間 112.8 分（撮り終えて止める＝終了コード 5。記録はそろっている）
- カメラ（透視投影・縦の画角 40°・2720×2160 で撮って縮める。距離と注視点は依頼139 の camfit139.py と同じ決め方。上6つは依頼149 と同じ値）
  名前       見え方                                      方位  見下ろし  範囲                        注視点 (x, z)     距離
  top_all    真上の全体                                  0°    90°      x−0.5〜120.5・z−0.5〜60.5    (60.00, 30.00)   142.8 m
  w_west     引き・西半分                                左30° 75°      x−0.5〜72・z12.5〜31.5       (34.06, 21.26)    85.8 m
  w_east     引き・東半分                                左30° 75°      x48〜120.5・z12.5〜31.5      (82.56, 21.26)    85.8 m
  n_elev     寄り・エレベーターホールとトイレ（西）の前   左30° 75°      x16.5〜32・z14.5〜31.5       (24.33, 22.51)    34.0 m
  n_midwait  寄り・中待合（内科と診察室）                左30° 75°      x21〜43・z30〜42             (31.80, 35.73)    32.4 m
  n_wait     寄り・総合受付と会計の待合                  左30° 75°      x25〜70・z15〜27.5           (46.58, 20.95)    54.6 m
  n_er       寄り・救急の待合（新しい）                  左30° 75°      x98〜121・z4〜31.5           (109.48, 16.64)   51.9 m
  n_erway    寄り・正面玄関から救急へ行く道（新しい）     左30° 75°      x42〜121・z−0.5〜31.5        (79.76, 13.80)    99.7 m
  （n_erway は道が x42〜121 に広がるので、範囲に合わせると距離 99.7 m になり、引きと同じくらいの大きさになった。歩いている救急の患者は小さく写る）
- 静止画の時刻（確かめの回 check154L の zones147.csv・people147.csv から）：
  上の6つ：10時ごろ＝9540 秒（10時09分。9時30分〜10時30分で人がいちばん多い分・351 人）／14時ごろ＝21900 秒（13時35分。13時30分〜14時30分で人がいちばん多い分・283 人）
  n_er：同じ窓で救急の待合に救急の患者がいちばん多い分＝8820 秒（9時57分・3 人）／23400 秒（14時00分・2 人）
  n_erway：同じ窓で正面玄関から救急の待合へ歩いている救急の患者がいる分＝9120 秒（10時02分・1 人。9111 秒に正面玄関に出た人）／24180 秒（14時13分・1 人）
  開いて見て、救急の待合のベンチに救急の患者が座り、救急の処置の場所に職員がいることを確かめた。並べた図の文字の切れなし
  （n_er・n_erway の説明の文が長くて隣の絵の下にはみ出したので、文を短くして作り直した）
- 動画：hospital154_1日_早回し_30x.mp4　1920×1080・25コマ/秒・48281コマ・1931.2 秒（32分11.2秒）。依頼149 と同じ作り：
  7時30分（0.05 秒）から最後の便が戻った時刻（31630.9 秒＝16時17分11秒）まで、ふだん 1.2 秒ごと＝30倍で引き（ロボットのいる側：x<60 は西半分、それ以外は東半分）、
  ロボットが走っている間（16 区間）は 0.2 秒ごと＝5倍で付いて行く寄り。5倍 26312 コマ・30倍 21969 コマ
- 並べた図：hospital154_静止画16枚.png（上2段＝10時ごろ、下2段＝14時ごろ。各段4枚：真上・引き西・引き東・寄り エレベーターホール／寄り 中待合・総合受付と会計の待合・救急の待合・正面玄関から救急へ行く道）

■ 出力の場所
- KIOXIA：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital/20261111_flow154/
  runs/（check154a・check154b・check154c・check154L・h143repro154・rec154）・stills/（静止画16枚）・sheets/（並べた図）・videos/（動画）・
  agg154_out.txt（確かめのまとめ）・camspath154*.csv・cams154.txt・md5_icloud154.txt・logs/・report154.txt（この報告の写し）・
  tools/（run154.sh・rec154.sh・vid154.sh・test154.sh・agg154.py・cmp154.py・cams154.py・paths154.py・pan154.py・sheet154.py と、依頼139〜149 の写し）・
  test/（check-in の前の試しの実行ファイル hospital154_testbuild_cs123 と、それで回した smoke1（90分）・full1（1日））
- iCloud：…/サービスロボット導入シミュレーション/hospital/flow154/ に 並べた図・動画・stills/（静止画16枚）を写した。18 ファイルとも md5 が KIOXIA と同じ
  hospital154_静止画16枚.png 9ec1ba7998b0d77450cb36e43abdb853 ／ hospital154_1日_早回し_30x.mp4 1ba46d62bd2929282b015379bef1c5af（静止画は md5_icloud154.txt）

■ check-in の番号
cs:124（保留 0）。実行ファイル Builds/hospital154（cs:124）

■ 決めてほしいこと（直し方の候補は作っていない）
1. 化学療法の患者のトイレ（答え 5）：化学療法のトイレへの道が、準備室とトイレのあいだの幅 0.5 m の長方形 [114, 53, 114.5, 60] だけで、壁の厚みを引くと 0.35 m しかなく、
   人が歩いて行けない（2 の (8)）。人に聞かないと進めない所なのでこの項目は止め、依頼149 と同じ近い方のトイレ（西）へ行かせて数えた（1日 8 人とも西。点滴の場所から約 85 m）。どうするか
2. 問題の数が増えた（廊下だけ 走っている間 84 → 132。増えた 48 のうち 37 は大廊下の東の端、化学療法の受付の台と東の縦廊下の口のまわり）。
   職員が持ち場から動く行き先は、その職員が access で入れる所から選ぶ（依頼147 からの決まり）ので、今回「入る」が足された所
   （医師→化学療法の受付・待合・点滴の場所・生理検査の待合・検査室、看護師→生理検査の待合・検査室・X線室、検査技師→救急の処置の場所・X線室）へも
   ふだんの行き来で行くようになった。access の「入る」は付き添い・通り抜けのために足されたものだが、ふだんの行き来の行き先にもなっている。これでよいか
3. Unity で決めた値（summary の unity_only154）：病棟の看護師が待つ点（患者の行き先の所の中で患者のそば 0.7 m・4方向。無ければその所の中の点）、
   病棟の看護師は患者と同じ速さ（遅い方）で歩く、X線室では座る家具が無いので立って待つ、化学療法のトイレへ行けないときは近い方のトイレ（西・東）。これでよいか
（参考：外来の大廊下 x0〜70 は混んでいる時間帯の平均 2.2 人・午後の平均 1.3 人で目安の幅の中。30分ごとでは、混んでいる時間帯に 2 を少し下回る時間帯が 3 つ（1.8〜1.9 人）、
  午後に 1 を下回る時間帯が 2 つ（0.7・0.8 人）ある。答え 6 のとおり人を増やす動きは足していない）

判定：止めた（決めてほしいことあり）
```

#### agg154_out.txt（依頼154 の確かめのまとめ）

```text

== check154a_v06_hall_s0（廊下だけ）status completed／回の終わり 32400.0 秒（16:30）／最後の便が戻った 31630.9 秒／実時間 31.6 分
便 16（走った 16・途中 0・走れなかった 0）／移る 5（終えた 5）／出るのが遅れた 平均 0.0 秒・最大 0.0 秒
瞬間移動 ロボット 0（1コマ最大 0.030 m）・人 0（最大 0.084 m）／人の引き直し（180秒動けない）0／行き先の近くで止まって待った 9
立ち往生 0: 
助け 0: 
ロボットの入らない所に中心が入った時間 0.00 秒／どの所にも入っていない時間 0.00 秒
人が access で入れない所に入った時間（役割ごと）： 外来の患者 0.00 秒・化学療法の患者 0.00 秒・救急の患者 0.00 秒・看護師 0.00 秒・医師 0.00 秒・薬剤師 0.00 秒・検査技師 0.00 秒・事務 0.00 秒
どの所にも入っていない時間（役割ごと）： 外来の患者 0.00 秒・化学療法の患者 0.00 秒・救急の患者 0.00 秒・看護師 0.00 秒・医師 0.00 秒・薬剤師 0.00 秒・検査技師 0.00 秒・事務 0.00 秒
職員 141 人／持ち場から動いた 1249 回・看護師が呼びに出た 386 回（看護師なし 0・間に合わない 0）
席 465（使えない 0）・机などの横の点 1108・診察室 13／所 55 枚・区画 25／壁の箱 150・家具の箱 78
飛ばした行き先（理由）： {}
外来の患者 390 人：出た 375・回の終わりに中にいた 15・入らなかった 0／いた時間 中央値 103.9 分・最短 20.3・最長 217.0（10〜90%：52.0〜172.4）
付き添い 192 人：出た 182・回の終わりに中にいた 10・入らなかった 0／いた時間 中央値 103.7 分・最短 20.2・最長 207.9（10〜90%：57.9〜173.2）
化学療法の患者 20 人：出た 17・回の終わりに中にいた 3・入らなかった 0／いた時間 中央値 188.2 分・最短 128.5・最長 287.2（10〜90%：129.8〜280.1）
救急の患者 18 人：出た 15・回の終わりに中にいた 3・入らなかった 0／いた時間 中央値 84.7 分・最短 66.4・最長 176.6（10〜90%：67.6〜176.4）
病棟の患者 54 人：出た 52・回の終わりに中にいた 2・入らなかった 0／いた時間 中央値 30.2 分・最短 17.4・最長 42.7（10〜90%：20.5〜37.6）
病棟の看護師 54 人：出た 52・回の終わりに中にいた 2・入らなかった 0／いた時間 中央値 30.1 分・最短 17.4・最長 42.8（10〜90%：20.5〜37.6）
見舞いの人 105 人：出た 104・回の終わりに中にいた 1・入らなかった 0／いた時間 中央値 0.5 分・最短 0.4・最長 0.6（10〜90%：0.5〜0.6）
ほかの階の職員 180 人：出た 179・回の終わりに中にいた 1・入らなかった 0／いた時間 中央値 3.5 分・最短 1.8・最長 6.1（10〜90%：2.5〜4.8）
飛ばした行き先（人）： なし
初診・紹介 34 人（外来の患者 390 人）
トイレ：行くと決めた人 外来の患者 150・付き添い 83・化学療法の患者 8／行った人 付き添い→トイレ（東） 49・付き添い→トイレ（西） 32・化学療法の患者→トイレ（西） 8・外来の患者→トイレ（東） 64・外来の患者→トイレ（西） 85
   席を立ってから戻るまで 中央値 5.9 分（最短 3.6・最長 9.4）／席からトイレまで歩いた時間 中央値 28 秒・最長 127 秒
   どこで待っている間に行ったか： 中待合 166・会計の待合 55・点滴 8・内科の処置室 5・総合受付の待合 4
   行く時が来ないまま回が終わった 2・行けるトイレがない 0・戻るのを待って次へ行くのが遅れた 3 回（計 28 秒）
エレベーター（種類ごと）： ほかの階の職員 エレベーターから出る 180・ほかの階の職員 エレベーターへ入る 179・病棟の患者 エレベーターから出る 54・病棟の患者 エレベーターへ入る 52・病棟の看護師 エレベーターから出る 54・病棟の看護師 エレベーターへ入る 52・見舞いの人 エレベーターから出る 53・見舞いの人 エレベーターへ入る 52
   病棟の患者の形： 歩く患者・付き添い 28・車いす 26
出さなかった人（行き先へ歩いて行けない）： {} ／救急の患者 0 人（正面玄関から行ける 1）
救急の待合 を通り抜けた外来の患者の役割の人 17 人（病棟の患者 17）／そこにいた時間 合計 279 秒・1人 中央値 18.4 秒・最長 20.8 秒
救急の処置の場所 を通り抜けた外来の患者の役割の人 17 人（病棟の患者 17）／そこにいた時間 合計 552 秒・1人 中央値 36.8 秒・最長 38.0 秒
病棟の看護師：検査の所の中のそばに立った 91・所の中の点 0・大廊下の端で待った 0／化学療法のトイレへ行けず西・東にした 8
案A：付き添いを呼んだ 47・付き添った 47・間に合わない 0・時間切れ 0／付き添いなしで裏の通路にいた患者 0.0 秒
   処置・検査室・内視鏡室の行き先がある外来の患者 47 人・着いた 46 人
時刻ごと（30分ごとの平均。1分ごとに数えた人数。患者・付き添い・職員・エレベーターから来た人の全部）
時刻          大廊下x0〜70  依頼149  目安(flow_estimate)  大廊下x70〜120  依頼149  待合   中待合  部屋の中  職員の廊下  玄関・玄関の通り  エレベーターホール  トイレ   全部
7:30〜8:00     1.1        1.2          1〜3               0.5        1.8       5.5   12.6    137.8       0.9           3.0              0.1          0.5   162.1
8:00〜8:30     2.1        1.7          2〜8               0.9        2.3      14.1   51.9    147.2       1.1           3.9              0.1          2.4   223.7
8:30〜9:00     1.9        1.1          2〜8               1.0        1.6      23.0   84.3    159.4       1.4           4.4              0.2          3.5   279.1
9:00〜9:30     1.9        1.7          2〜8               0.8        2.4      37.1   90.4    168.0       1.8           4.8              0.0          4.8   309.6
9:30〜10:00     1.8        2.6          2〜8               0.7        2.3      38.5  108.3    178.7       0.8           4.4              0.3          2.9   336.5
10:00〜10:30     2.8        1.7          2〜8               1.0        3.0      44.3  114.3    177.3       1.1           3.5              0.1          2.9   347.3
10:30〜11:00     2.7        2.0          2〜8               0.6        2.2      36.7  121.5    180.2       1.4           4.3              0.2          4.4   352.1
11:00〜11:30     1.4        1.7          1〜3               0.9        2.6      37.2  131.6    178.8       1.4           3.5              0.1          3.7   358.6
11:30〜12:00     1.7        1.4          1〜3               0.7        2.5      35.0  123.9    176.0       0.8           3.7              0.2          2.8   344.9
12:00〜12:30     1.2        1.5          1〜3               0.5        4.5      40.1  105.5    179.9       1.1           3.1              0.2          1.7   333.4
12:30〜13:00     1.9        0.9          1〜3               0.7        3.6      42.5   69.7    183.4       0.9           3.5              0.3          1.5   304.4
13:00〜13:30     2.2        1.6          1〜3               0.7        2.4      42.6   54.8    179.1       1.2           3.6              0.2          1.5   285.8
13:30〜14:00     1.0        1.6          1〜3               0.8        4.1      34.5   50.5    172.1       1.5           3.7              0.2          2.5   266.8
14:00〜14:30     0.7        2.0          1〜3               0.3        2.9      24.5   39.9    162.4       0.7           4.2              0.2          1.4   234.3
14:30〜15:00     1.7        2.1          1〜3               0.5        0.9      19.4   34.0    154.8       1.2           3.2              0.1          0.7   215.7
15:00〜15:30     1.1        1.6          1〜3               0.8        2.8      16.3   29.4    155.7       1.0           2.9              0.3          1.3   209.0
15:30〜16:00     0.8        0.9          1〜3               0.4        1.6      19.3   25.7    154.5       0.9           3.2              0.1          1.3   206.3
16:00〜16:30     1.3        1.2          1〜3               0.5        1.0      12.5   17.6    152.6       0.8           2.9              0.1          0.4   188.7
外来の大廊下 x0〜70：混んでいる時間帯（8:00〜11:00）平均 2.2 人・最大 6 人（依頼149 1.8・7／目安 2〜8）／空いている時間帯 平均 1.4 人・最大 6 人（依頼149 1.5・7／目安 1〜3）
1日の平均 1.64 人（依頼149 1.58 人）
午後（13:00〜）平均 1.3 人・最大 6 人（依頼149 1.6・5／目安 1〜3）
大廊下 x70〜120：1日の平均 0.69 人・最大 4 人（依頼149 2.48 人・8 人）
職員の廊下の人数：平均 1.13 人・最大 5 人
問題の数（1 急に止まった／2 ひやり／3 人がよけさせられた／4 立ち往生／5 横切り／止まりきれなかった）  依頼154 ／ 依頼149 ／ 依頼147 ／ 依頼143
   走っている間 全部: 17／54／51／0／0／10 ／ 9／37／37／0／0／1 ／ 8／42／35／0／0／3 ／ 23／49／31／2／1／5
     外来の大廊下: 5／12／7／0／0／2 ／ 1／5／4／0／0／0 ／ 1／7／2／0／0／0 ／ 21／48／28／2／1／5
     職員の廊下: 4／27／33／0／0／3 ／ 5／24／28／0／0／0 ／ 5／30／28／0／0／2 ／ 2／1／2／0／0／0
     部屋の中: 8／15／11／0／0／5 ／ 3／8／5／0／0／1 ／ 2／5／5／0／0／1 ／ 0／0／1／0／0／0
     玄関の通り: 0／0／0／0／0／0 ／ 0／0／0／0／0／0 ／ 0／0／0／0／0／0 ／ 0／0／0／0／0／0
   台の前で待っている間: 0／64／12／0／0／0 ／ 0／65／11／0／0／0 ／ 0／66／9／0／0／0 ／ 0／0／0／0／0／0
   速いままのひやり: 54 ／ 37 ／ 42 ／ 35
   助け: 0 ／ 0 ／ 0 ／ 2
   区間ごとの数（全部の種類）： 待ち 76・行き 43・移る 21・帰り 68
   相手 1 急に止まった: なし 13・歩く患者・付き添い 3・スタッフ 1
   相手 2 ひやり: スタッフ 46・歩く患者・付き添い 7・ストレッチャー 1
   相手 3 人がよけさせられた: スタッフ 48・歩く患者・付き添い 3
   相手 6 止まりきれなかった: スタッフ 10

== check154c_v06_switch_s0（時間で切替）status completed／回の終わり 32400.0 秒（16:30）／最後の便が戻った 31630.1 秒／実時間 31.8 分
便 16（走った 16・途中 0・走れなかった 0）／移る 5（終えた 5）／出るのが遅れた 平均 0.0 秒・最大 0.0 秒
瞬間移動 ロボット 0（1コマ最大 0.030 m）・人 0（最大 0.128 m）／人の引き直し（180秒動けない）0／行き先の近くで止まって待った 12
立ち往生 0: 
助け 0: 
ロボットの入らない所に中心が入った時間 0.00 秒／どの所にも入っていない時間 0.00 秒
人が access で入れない所に入った時間（役割ごと）： 外来の患者 0.00 秒・化学療法の患者 0.00 秒・救急の患者 0.00 秒・看護師 0.00 秒・医師 0.00 秒・薬剤師 0.00 秒・検査技師 0.00 秒・事務 0.00 秒
どの所にも入っていない時間（役割ごと）： 外来の患者 0.00 秒・化学療法の患者 0.00 秒・救急の患者 0.00 秒・看護師 0.00 秒・医師 0.00 秒・薬剤師 0.00 秒・検査技師 0.00 秒・事務 0.00 秒
職員 141 人／持ち場から動いた 1224 回・看護師が呼びに出た 386 回（看護師なし 0・間に合わない 0）
席 465（使えない 0）・机などの横の点 1108・診察室 13／所 55 枚・区画 25／壁の箱 150・家具の箱 78
飛ばした行き先（理由）： {}
外来の患者 390 人：出た 375・回の終わりに中にいた 15・入らなかった 0／いた時間 中央値 103.8 分・最短 20.3・最長 223.7（10〜90%：52.0〜172.3）
付き添い 192 人：出た 182・回の終わりに中にいた 10・入らなかった 0／いた時間 中央値 103.8 分・最短 20.2・最長 207.9（10〜90%：57.9〜173.1）
化学療法の患者 20 人：出た 17・回の終わりに中にいた 3・入らなかった 0／いた時間 中央値 188.1 分・最短 128.5・最長 287.2（10〜90%：129.9〜280.3）
救急の患者 18 人：出た 14・回の終わりに中にいた 4・入らなかった 0／いた時間 中央値 84.7 分・最短 66.2・最長 176.6（10〜90%：67.5〜176.5）
病棟の患者 54 人：出た 52・回の終わりに中にいた 2・入らなかった 0／いた時間 中央値 30.2 分・最短 17.4・最長 43.1（10〜90%：20.5〜37.6）
病棟の看護師 54 人：出た 52・回の終わりに中にいた 2・入らなかった 0／いた時間 中央値 30.2 分・最短 17.4・最長 43.1（10〜90%：20.5〜37.5）
見舞いの人 105 人：出た 104・回の終わりに中にいた 1・入らなかった 0／いた時間 中央値 0.5 分・最短 0.4・最長 0.6（10〜90%：0.5〜0.6）
ほかの階の職員 180 人：出た 179・回の終わりに中にいた 1・入らなかった 0／いた時間 中央値 3.6 分・最短 1.9・最長 6.2（10〜90%：2.4〜4.7）
飛ばした行き先（人）： なし
初診・紹介 34 人（外来の患者 390 人）
トイレ：行くと決めた人 外来の患者 150・付き添い 83・化学療法の患者 8／行った人 付き添い→トイレ（東） 49・付き添い→トイレ（西） 32・化学療法の患者→トイレ（西） 8・外来の患者→トイレ（東） 65・外来の患者→トイレ（西） 84
   席を立ってから戻るまで 中央値 5.9 分（最短 3.6・最長 9.8）／席からトイレまで歩いた時間 中央値 29 秒・最長 127 秒
   どこで待っている間に行ったか： 中待合 166・会計の待合 55・点滴 8・内科の処置室 5・総合受付の待合 4
   行く時が来ないまま回が終わった 2・行けるトイレがない 0・戻るのを待って次へ行くのが遅れた 3 回（計 26 秒）
エレベーター（種類ごと）： ほかの階の職員 エレベーターから出る 180・ほかの階の職員 エレベーターへ入る 179・病棟の患者 エレベーターから出る 54・病棟の患者 エレベーターへ入る 52・病棟の看護師 エレベーターから出る 54・病棟の看護師 エレベーターへ入る 52・見舞いの人 エレベーターから出る 53・見舞いの人 エレベーターへ入る 52
   病棟の患者の形： 歩く患者・付き添い 28・車いす 26
出さなかった人（行き先へ歩いて行けない）： {} ／救急の患者 0 人（正面玄関から行ける 1）
救急の待合 を通り抜けた外来の患者の役割の人 17 人（病棟の患者 17）／そこにいた時間 合計 279 秒・1人 中央値 18.4 秒・最長 20.8 秒
救急の処置の場所 を通り抜けた外来の患者の役割の人 17 人（病棟の患者 17）／そこにいた時間 合計 555 秒・1人 中央値 36.9 秒・最長 39.5 秒
病棟の看護師：検査の所の中のそばに立った 91・所の中の点 0・大廊下の端で待った 0／化学療法のトイレへ行けず西・東にした 8
案A：付き添いを呼んだ 47・付き添った 47・間に合わない 0・時間切れ 0／付き添いなしで裏の通路にいた患者 0.0 秒
   処置・検査室・内視鏡室の行き先がある外来の患者 47 人・着いた 46 人
時刻ごと（30分ごとの平均。1分ごとに数えた人数。患者・付き添い・職員・エレベーターから来た人の全部）
時刻          大廊下x0〜70  依頼149  目安(flow_estimate)  大廊下x70〜120  依頼149  待合   中待合  部屋の中  職員の廊下  玄関・玄関の通り  エレベーターホール  トイレ   全部
7:30〜8:00     1.1        1.2          1〜3               0.5        1.8       5.5   12.6    137.8       0.9           3.0              0.1          0.5   162.1
8:00〜8:30     2.1        1.7          2〜8               0.9        2.3      14.1   51.9    147.2       1.1           3.9              0.1          2.4   223.7
8:30〜9:00     1.9        1.1          2〜8               1.0        1.6      23.0   84.3    159.4       1.4           4.4              0.2          3.5   279.1
9:00〜9:30     1.9        1.7          2〜8               0.8        2.4      37.1   90.4    168.0       1.8           4.8              0.0          4.8   309.6
9:30〜10:00     1.8        2.6          2〜8               0.7        2.3      38.5  108.3    178.7       0.8           4.4              0.3          2.9   336.5
10:00〜10:30     2.3        1.5          2〜8               1.0        2.9      43.8  114.1    178.3       1.3           3.4              0.1          3.0   347.3
10:30〜11:00     1.9        1.4          2〜8               0.7        2.2      36.9  121.4    180.8       1.4           4.2              0.1          4.4   351.8
11:00〜11:30     1.4        1.6          1〜3               0.8        2.6      36.4  131.2    180.5       0.9           3.5              0.2          3.7   358.7
11:30〜12:00     2.4        2.8          1〜3               0.9        2.6      35.4  123.2    175.1       1.6           3.3              0.2          2.8   344.8
12:00〜12:30     2.2        3.0          1〜3               0.6        4.7      39.4  104.6    180.1       1.2           3.5              0.1          1.6   333.4
12:30〜13:00     2.2        2.8          1〜3               0.7        3.4      42.9   70.7    182.1       1.3           3.4              0.3          1.5   305.0
13:00〜13:30     2.4        2.7          1〜3               0.8        2.6      42.4   55.0    179.0       1.1           3.2              0.2          1.5   285.7
13:30〜14:00     2.1        2.2          1〜3               0.7        3.8      34.3   49.0    173.1       1.0           3.4              0.2          2.6   266.3
14:00〜14:30     1.6        2.3          1〜3               0.7        3.2      24.6   39.4    162.0       1.0           3.6              0.1          1.4   234.4
14:30〜15:00     1.1        2.4          1〜3               0.6        0.8      19.1   34.0    154.9       1.1           4.1              0.1          0.7   215.6
15:00〜15:30     1.0        2.0          1〜3               0.7        2.8      15.4   29.8    156.6       1.1           2.9              0.3          1.4   209.1
15:30〜16:00     0.9        2.2          1〜3               0.5        1.6      19.3   25.9    153.2       1.3           3.9              0.1          1.2   206.3
16:00〜16:30     1.4        1.4          1〜3               0.4        1.1      12.4   17.8    151.4       1.2           3.6              0.2          0.4   188.8
外来の大廊下 x0〜70：混んでいる時間帯（8:00〜11:00）平均 2.0 人・最大 6 人（依頼149 1.7・6／目安 2〜8）／空いている時間帯 平均 1.6 人・最大 6 人（依頼149 2.2・7／目安 1〜3）
1日の平均 1.77 人（依頼149 2.03 人）
午後（13:00〜）平均 1.5 人・最大 6 人（依頼149 2.2・7／目安 1〜3）
大廊下 x70〜120：1日の平均 0.72 人・最大 4 人（依頼149 2.49 人・7 人）
職員の廊下の人数：平均 1.20 人・最大 5 人
問題の数（1 急に止まった／2 ひやり／3 人がよけさせられた／4 立ち往生／5 横切り／止まりきれなかった）  依頼154 ／ 依頼149 ／ 依頼147 ／ 依頼143
   走っている間 全部: 11／52／54／0／0／5 ／ 15／39／52／0／0／4 ／ 14／49／51／0／0／5 ／ 25／60／16／1／1／4
     外来の大廊下: 4／19／11／0／0／2 ／ 5／8／7／0／0／0 ／ 3／10／6／0／0／2 ／ 23／49／13／0／1／3
     職員の廊下: 3／21／36／0／0／1 ／ 8／24／40／0／0／1 ／ 7／35／38／0／0／2 ／ 2／11／3／1／0／1
     部屋の中: 4／12／7／0／0／2 ／ 2／7／5／0／0／3 ／ 4／4／7／0／0／1 ／ 0／0／0／0／0／0
     玄関の通り: 0／0／0／0／0／0 ／ 0／0／0／0／0／0 ／ 0／0／0／0／0／0 ／ 0／0／0／0／0／0
   台の前で待っている間: 0／57／12／0／0／0 ／ 0／58／11／0／0／0 ／ 0／57／5／0／0／0 ／ 0／0／0／0／0／0
   速いままのひやり: 52 ／ 39 ／ 49 ／ 52
   助け: 0 ／ 0 ／ 0 ／ 1
   区間ごとの数（全部の種類）： 待ち 69・行き 59・移る 35・帰り 28
   相手 1 急に止まった: なし 9・歩く患者・付き添い 1・スタッフ 1
   相手 2 ひやり: スタッフ 49・歩く患者・付き添い 2・ストレッチャー 1
   相手 3 人がよけさせられた: スタッフ 52・歩く患者・付き添い 2
   相手 6 止まりきれなかった: スタッフ 4・歩く患者・付き添い 1
```

#### cams154.txt（静止画のカメラの値）

```text
flowday,30,75,82.5595,21.2606,85.8475,40,2720,2160
top_all_10,0,90,60.00,30.00,142.8,40,2720,2160
w_west_10,30,75,34.06,21.26,85.8,40,2720,2160
w_east_10,30,75,82.56,21.26,85.8,40,2720,2160
n_elev_10,30,75,24.33,22.51,34.0,40,2720,2160
n_midwait_10,30,75,31.80,35.73,32.4,40,2720,2160
n_wait_10,30,75,46.58,20.95,54.6,40,2720,2160
n_er_10,30,75,109.48,16.64,51.9,40,2720,2160
n_erway_10,30,75,79.76,13.80,99.7,40,2720,2160
top_all_14,0,90,60.00,30.00,142.8,40,2720,2160
w_west_14,30,75,34.06,21.26,85.8,40,2720,2160
w_east_14,30,75,82.56,21.26,85.8,40,2720,2160
n_elev_14,30,75,24.33,22.51,34.0,40,2720,2160
n_midwait_14,30,75,31.80,35.73,32.4,40,2720,2160
n_wait_14,30,75,46.58,20.95,54.6,40,2720,2160
n_er_14,30,75,109.48,16.64,51.9,40,2720,2160
n_erway_14,30,75,79.76,13.80,99.7,40,2720,2160
```

#### run154.sh（確かめの回を回す）

```bash
#!/bin/bash
# 依頼154：確かめの回（h1・人の動き flow・0.6m/秒・種0）。廊下だけ 2回（check154a・b）、時間で切替 1回（check154c）、
# 見た目あり・カメラなしの廊下だけ 1回（check154L。見た目なしの check154a と同じ記録になるか）、
# -h147People hall（依頼143 の形）で廊下だけ 1回（h143repro154。依頼143 の check143a と同じ記録になるか）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim
O=/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital/20261111_flow154
EXE=Builds/hospital154/HospitalSim.app/Contents/MacOS/ServiceSim
CS=$(python3 -c "import json;print(json.load(open('Builds/hospital154/build_info.json'))['unity_cs'])")
run() { L=$1; shift; $EXE -batchmode -nographics -h143Layout h1 -h127Speed 0.6 -h127Seed 0 -h127Out $O/runs -h127Cs $CS -h136Mode day -h138Mode 138 -h127Label $L -h127MaxWallMin 200 "$@" -logFile $O/logs/$L.log; echo "$L exit $?" >> $O/logs/run154.exit; }
run check154a -h147People flow -h136Plan hall &
run check154b -h147People flow -h136Plan hall &
run check154c -h147People flow -h136Plan switch &
run check154L -h147People flow -h136Plan hall -h139Look on -h140Scheme A -h143Wheelchair on &
run h143repro154 -h147People hall -h136Plan hall &
wait
echo done >> $O/logs/run154.exit
```

#### test154.sh（試しの実行ファイルで回す）

```bash
#!/bin/bash
# 依頼154：試しの回（試しの実行ファイル）。smoke＝90分、full＝1日（見た目なし・廊下だけ・0.6m/秒・種0）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim
O=/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital/20261111_flow154
EXE=Builds/hospital154/HospitalSim.app/Contents/MacOS/ServiceSim
L=$1; shift
B="-batchmode -nographics -h143Layout h1 -h147People flow -h127Speed 0.6 -h127Seed 0 -h127Out $O/test -h127Cs test -h136Mode day -h136Plan hall -h138Mode 138"
$EXE $B -h127Label $L "$@" -logFile $O/logs/$L.log
echo "$L exit $?" >> $O/logs/test154.exit
```

#### rec154.sh（撮る回を回す）

```bash
#!/bin/bash
# 依頼154：撮る回（h1・人の動き flow・廊下だけ・0.6m/秒・種0・色A・車いすの場所の印あり）。flowday＝1日の早回し、ほか16＝静止画16（1行だけの道）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim
O=/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital/20261111_flow154
EXE=Builds/hospital154/HospitalSim.app/Contents/MacOS/ServiceSim
CS=$(python3 -c "import json;print(json.load(open('Builds/hospital154/build_info.json'))['unity_cs'])")
CAMS=()
while read -r c; do CAMS+=(-h139Cam "$c"); done < $O/cams154.txt
$EXE -batchmode -h143Layout h1 -h147People flow -h143Wheelchair on -h127Speed 0.6 -h127Seed 0 -h127Out $O/runs -h127Cs $CS -h136Mode day -h136Plan hall -h138Mode 138 -h139Look on -h140Scheme A \
  -h127Label rec154 "${CAMS[@]}" -h141CamPath $O/camspath154.csv -h139EndAfterShots on -h127MaxWallMin 400 -logFile $O/logs/rec154.log
echo "rec exit $?" > $O/logs/rec154.exit
```

#### vid154.sh（1日の早回しの動画を作る）

```bash
#!/bin/bash
O=/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital/20261111_flow154
cd $O
export PATH=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin:$PATH
python3 tools/pan154.py video runs/rec154_v06_hall_s0 flowday videos/hospital154_1日_早回し_30x.mp4 8 > logs/vid154.out 2>&1
echo "vid exit $?" > logs/vid154.exit
```

#### agg154.py（確かめの回をまとめる）

```python
#!/usr/bin/env python3
"""依頼154：確かめの回のまとめ（依頼143・147・149 の check と並べる）。
  python3 agg154.py <回のフォルダ> [<回のフォルダ> ...]"""
import csv, json, os, sys, statistics
from collections import Counter, defaultdict

HOSP = "/Volumes/KIOXIA/r2r2r-data/service_sim/unity/hospital"
R143 = {"hall": HOSP + "/20261110_floor143/runs/check143a_v06_hall_s0", "switch": HOSP + "/20261110_floor143/runs/check143c_v06_switch_s0"}
R147 = {"hall": HOSP + "/20261110_people147/runs/check147a_v06_hall_s0", "switch": HOSP + "/20261110_people147/runs/check147c_v06_switch_s0"}
R149 = {"hall": HOSP + "/20261111_flow149/runs/check149a_v06_hall_s0", "switch": HOSP + "/20261111_flow149/runs/check149c_v06_switch_s0"}
EST = {"busy": (2, 8), "quiet": (1, 3)}   # assumptions.json crowd_people.flow_estimate
H1 = "/Users/shimazueiichi/Robotics_UX/ServiceSim/Assets/ServiceSim/Hospital/Data/hospital_h1-154.json"
TN = {1: "急に止まった", 2: "ひやり", 3: "人がよけさせられた", 4: "立ち往生", 5: "横切り", 6: "止まりきれなかった"}
LAY = json.load(open(H1, encoding="utf-8"))
COR = {c["name"]: c["rect"] for c in LAY["layout"]["corridors"]}
STAFF_COR = ["職員の中廊下", "西の縦廊下", "東の縦廊下", "外来の奥の職員通路", "東の縦廊下（北）"]
WAIT = ["総合受付・会計の待合", "お薬の窓口の待合", "生理検査の待合", "化学療法の受付・待合", "救急の待合"]
MIDW = ["内科の中待合", "ほかの外来の中待合"]
SCOR = ["職員の中廊下", "西の縦廊下", "東の縦廊下", "外来の奥の職員通路"]
ENT = ["正面玄関", "玄関の通り"]
ELV = ["エレベーターホール", "エレベーター"]
TOI = ["トイレ（西）", "トイレ（東）", "化学療法のトイレ"]
ROLES = ("外来の患者", "化学療法の患者", "救急の患者", "看護師", "医師", "薬剤師", "検査技師", "事務")


def inr(r, x, z, e=1e-4):
    return r[0] - e <= x <= r[2] + e and r[1] - e <= z <= r[3] + e


def area3(x, z):
    if inr(COR["外来の大廊下"], x, z): return "外来の大廊下"
    if any(inr(COR[n], x, z) for n in STAFF_COR): return "職員の廊下"
    if inr(COR["玄関の通り"], x, z): return "玄関の通り"
    return "部屋の中"


def rd(run, f):
    return list(csv.DictReader(open(os.path.join(run, f), encoding="utf-8")))


def one(run):
    s = json.load(open(os.path.join(run, "summary.json"), encoding="utf-8"))
    ev = rd(run, "events.csv")
    legs = {l["leg_id"]: l for l in rd(run, "legs.csv")}
    run_c, wait_c, ar = Counter(), Counter(), defaultdict(Counter)
    fast = 0
    kinds = defaultdict(Counter)
    for e in ev:
        k = int(e["type"])
        d = legs[e["leg_id"]]["dir"] if e["leg_id"] in legs else ""
        if d == "待ち":
            wait_c[k] += 1
            continue
        run_c[k] += 1
        kinds[k][e["partner_kind"] or "なし"] += 1
        if k == 2 and e["note"] == "速いまま": fast += 1
        ar[area3(float(e["robot_x"]), float(e["robot_z"]))][k] += 1
    return dict(s=s, ev=ev, legs=legs, run=run_c, wait=wait_c, area=ar, fast=fast, kinds=kinds,
                trips=rd(run, "trips.csv"), moves=rd(run, "moves.csv"), resc=rd(run, "rescues.csv"))


def line(c, ks=range(1, 7)):
    return "／".join("%d" % c[k] for k in ks)


def hm(t):
    t = int(round(t)) + 7 * 3600 + 1800
    return "%d:%02d" % (t // 3600, (t % 3600) // 60)


def halls(run):
    return {float(r["t_s"]): int(r["hall_people"]) for r in rd(run, "crowd.csv")}


def zones_table(run, s, run149):
    zl = rd(run, "zones147.csv")
    byt = defaultdict(lambda: Counter())
    for r in zl:
        t = float(r["t_s"]); n = int(r["patients"]) + int(r["staff"])
        z = r["zone"]
        if z in WAIT: c = "待合"
        elif z in MIDW: c = "中待合"
        elif z in SCOR: c = "職員の廊下"
        elif z == "外来の大廊下": c = None
        elif z in ENT: c = "玄関"
        elif z in ELV: c = "エレベーターホール"     # ホールと中（かご）を合わせる
        elif z in TOI: c = "トイレ"
        else: c = "部屋の中"
        if c: byt[t][c] += n
        byt[t]["all"] += n
    crowd = rd(run, "crowd.csv")
    hall = {float(r["t_s"]): (int(r["hall_people"]), int(r["hall_target"]), int(r["east_people"])) for r in crowd}
    h147 = halls(run149)
    e149 = {float(r["t_s"]): int(r["east_people"]) for r in rd(run149, "crowd.csv")}
    end = s["sim_s"]
    print("時刻ごと（30分ごとの平均。1分ごとに数えた人数。患者・付き添い・職員・エレベーターから来た人の全部）")
    print("時刻          大廊下x0〜70  依頼149  目安(flow_estimate)  大廊下x70〜120  依頼149  待合   中待合  部屋の中  職員の廊下  玄関・玄関の通り  エレベーターホール  トイレ   全部")
    t0 = 0.0
    while t0 < end - 1:
        ts = [t for t in hall if t0 <= t < t0 + 1800]
        if not ts: break
        avg = lambda f: sum(f(t) for t in ts) / len(ts)
        hh = avg(lambda t: hall[t][0]); ee = avg(lambda t: hall[t][2])
        tg = "%d〜%d" % (EST["busy"] if 1800 <= t0 < 12600 else EST["quiet"])
        x49 = [e149[t] for t in e149 if t0 <= t < t0 + 1800]
        e49 = sum(x49) / len(x49) if x49 else float("nan")
        t47 = [h147[t] for t in h147 if t0 <= t < t0 + 1800]
        h47 = sum(t47) / len(t47) if t47 else float("nan")
        cs = {c: avg(lambda t: byt[t][c]) for c in ("待合", "中待合", "部屋の中", "職員の廊下", "玄関", "エレベーターホール", "トイレ", "all")}
        print("%s〜%s  %6.1f     %6.1f     %8s            %6.1f     %6.1f    %6.1f %6.1f  %7.1f   %7.1f       %7.1f          %7.1f       %6.1f  %6.1f" % (
            hm(t0), hm(t0 + 1800), hh, h47, tg, ee, e49, cs["待合"], cs["中待合"], cs["部屋の中"], cs["職員の廊下"], cs["玄関"], cs["エレベーターホール"], cs["トイレ"], cs["all"]))
        t0 += 1800
    busy = [hall[t][0] for t in hall if 1800 <= t < 12600]
    quiet = [hall[t][0] for t in hall if t < 1800 or t >= 12600]
    b47 = [h147[t] for t in h147 if 1800 <= t < 12600]; q47 = [h147[t] for t in h147 if t < 1800 or t >= 12600]
    allh = list(hall.values())
    print("外来の大廊下 x0〜70：混んでいる時間帯（8:00〜11:00）平均 %.1f 人・最大 %d 人（依頼149 %.1f・%d／目安 2〜8）／空いている時間帯 平均 %.1f 人・最大 %d 人（依頼149 %.1f・%d／目安 1〜3）" % (
        sum(busy) / len(busy), max(busy), sum(b47) / len(b47), max(b47), sum(quiet) / len(quiet), max(quiet), sum(q47) / len(q47), max(q47)))
    print("1日の平均 %.2f 人（依頼149 %.2f 人）" % (sum(h[0] for h in allh) / len(allh), sum(h147.values()) / len(h147)))
    pm = [hall[t][0] for t in hall if t >= 19800]          # 13:00 から（午後）
    pm49 = [h147[t] for t in h147 if t >= 19800]
    print("午後（13:00〜）平均 %.1f 人・最大 %d 人（依頼149 %.1f・%d／目安 1〜3）" % (sum(pm) / len(pm), max(pm), sum(pm49) / len(pm49), max(pm49)))
    ea = [hall[t][2] for t in hall]; e49a = list(e149.values())
    print("大廊下 x70〜120：1日の平均 %.2f 人・最大 %d 人（依頼149 %.2f 人・%d 人）" % (sum(ea) / len(ea), max(ea), sum(e49a) / len(e49a), max(e49a)))
    sc = [byt[t]["職員の廊下"] for t in byt]
    print("職員の廊下の人数：平均 %.2f 人・最大 %d 人" % (sum(sc) / len(sc), max(sc)))


def people(run, s):
    rows = rd(run, "people147.csv")
    for what in ("外来の患者", "付き添い", "化学療法の患者", "救急の患者", "病棟の患者", "病棟の看護師", "見舞いの人", "ほかの階の職員"):
        rs = [r for r in rows if r["what"] == what]
        st = sorted(float(r["stay_s"]) / 60 for r in rs if r["stay_s"])
        inside = sum(1 for r in rs if r["spawn_s"] and not r["exit_s"])
        never = sum(1 for r in rs if not r["spawn_s"])
        if len(st) >= 2:
            q = statistics.quantiles(st, n=10)
            print("%s %d 人：出た %d・回の終わりに中にいた %d・入らなかった %d／いた時間 中央値 %.1f 分・最短 %.1f・最長 %.1f（10〜90%%：%.1f〜%.1f）" % (
                what, len(rs), len(st), inside, never, statistics.median(st), st[0], st[-1], q[0], q[-1]))
        else:
            print("%s %d 人：出た %d・回の終わりに中にいた %d・入らなかった %d" % (what, len(rs), len(st), inside, never))
    pat = [r for r in rows if r["what"] in ("外来の患者",)]
    sk = Counter()
    for r in rows:
        for x in r["skipped"].split("|"):
            if x: sk[r["what"] + " " + x] += 1
    print("飛ばした行き先（人）：", "・".join("%s %d 人" % kv for kv in sk.most_common()) or "なし")
    print("初診・紹介 %d 人（外来の患者 %d 人）" % (sum(1 for r in pat if r["first_visit"] == "1"), len(pat)))
    # トイレ
    tw = Counter(r["what"] for r in rows if r["toilet_want"] == "1")
    td = Counter((r["what"], r["toilet"]) for r in rows if r["toilet"])
    tl = rd(run, "toilet149.csv")
    print("トイレ：行くと決めた人 %s／行った人 %s" % ("・".join("%s %d" % kv for kv in tw.items()),
          "・".join("%s→%s %d" % (k[0], k[1], v) for k, v in sorted(td.items()))))
    if tl:
        dur = [float(r["back_s"]) - float(r["leave_seat_s"]) for r in tl]
        walk = [float(r["arrive_s"]) - float(r["leave_seat_s"]) for r in tl]
        print("   席を立ってから戻るまで 中央値 %.1f 分（最短 %.1f・最長 %.1f）／席からトイレまで歩いた時間 中央値 %.0f 秒・最長 %.0f 秒" % (
            statistics.median(dur) / 60, min(dur) / 60, max(dur) / 60, statistics.median(walk), max(walk)))
        st = Counter(r["step"] for r in tl)
        print("   どこで待っている間に行ったか：", "・".join("%s %d" % kv for kv in st.most_common()))
    print("   行く時が来ないまま回が終わった %d・行けるトイレがない %d・戻るのを待って次へ行くのが遅れた %d 回（計 %.0f 秒）" % (
        s["flow_toilet_not_due"], s["flow_toilet_no_way"], s["flow_toilet_extend_n"], s["flow_toilet_extend_s"]))
    # エレベーター
    el = rd(run, "elevator149.csv")
    ec = Counter((r["what"], r["dir"]) for r in el)
    print("エレベーター（種類ごと）：", "・".join("%s %s %d" % (k[0], k[1], v) for k, v in sorted(ec.items())))
    wk = Counter(r["kind"] for r in el if r["what"] == "病棟の患者" and r["dir"] == "エレベーターから出る")
    print("   病棟の患者の形：", "・".join("%s %d" % kv for kv in wk.items()))
    print("出さなかった人（行き先へ歩いて行けない）：", s.get("flow_not_spawned"), "／救急の患者 %d 人（正面玄関から行ける %d）" % (s["flow_er_not_spawned"], s["flow_er_reach_from_main"]))
    xl = rd(run, "er_cross154.csv")
    for z in ("救急の待合", "救急の処置の場所"):
        rs = [r for r in xl if r["zone"] == z]
        byw = Counter(r["what"] for r in rs)
        sec = sorted(float(r["seconds"]) for r in rs)
        if sec:
            print("%s を通り抜けた外来の患者の役割の人 %d 人（%s）／そこにいた時間 合計 %.0f 秒・1人 中央値 %.1f 秒・最長 %.1f 秒" % (
                z, len(rs), "・".join("%s %d" % kv for kv in byw.items()), sum(sec), statistics.median(sec), sec[-1]))
        else: print("%s を通り抜けた外来の患者の役割の人 0 人" % z)
    print("病棟の看護師：検査の所の中のそばに立った %d・所の中の点 %d・大廊下の端で待った %d／化学療法のトイレへ行けず西・東にした %d" % (
        s.get("flow_ward_nurse_inside", -1), s.get("flow_ward_nurse_inside_random", -1), s["flow_ward_nurse_hall_waits"], s.get("flow_chemo_toilet_fallback", -1)))
    print("案A：付き添いを呼んだ %d・付き添った %d・間に合わない %d・時間切れ %d／付き添いなしで裏の通路にいた患者 %.1f 秒" % (
        s["flow_escort_calls"], s["flow_escorts"], s["flow_escort_late"], s["flow_escort_timeouts"], s["flow_back_no_escort_s"]))
    pr = [r for r in pat if "処置・検査室" in r["steps"]]
    went = sum(1 for r in pr if any(x.startswith("処置・検査室:") and x.split(":")[3] for x in r["steps"].split("|")))
    print("   処置・検査室・内視鏡室の行き先がある外来の患者 %d 人・着いた %d 人" % (len(pr), went))


def prob(a, b143, b147, b149):
    print("問題の数（1 急に止まった／2 ひやり／3 人がよけさせられた／4 立ち往生／5 横切り／止まりきれなかった）  依頼154 ／ 依頼149 ／ 依頼147 ／ 依頼143")
    print("   走っている間 全部: %s ／ %s ／ %s ／ %s" % (line(a["run"]), line(b149["run"]), line(b147["run"]), line(b143["run"])))
    for ar in ("外来の大廊下", "職員の廊下", "部屋の中", "玄関の通り"):
        print("     %s: %s ／ %s ／ %s ／ %s" % (ar, line(a["area"][ar]), line(b149["area"][ar]), line(b147["area"][ar]), line(b143["area"][ar])))
    print("   台の前で待っている間: %s ／ %s ／ %s ／ %s" % (line(a["wait"]), line(b149["wait"]), line(b147["wait"]), line(b143["wait"])))
    print("   速いままのひやり: %d ／ %d ／ %d ／ %d" % (a["fast"], b149["fast"], b147["fast"], b143["fast"]))
    print("   助け: %d ／ %d ／ %d ／ %d" % (len(a["resc"]), len(b149["resc"]), len(b147["resc"]), len(b143["resc"])))


def main():
    b143 = {k: one(v) for k, v in R143.items()}
    b147 = {k: one(v) for k, v in R147.items()}
    b149 = {k: one(v) for k, v in R149.items()}
    for run in sys.argv[1:]:
        a = one(run)
        s = a["s"]
        key = "switch" if s["plan_key"] == "switch" else "hall"
        print()
        print("== %s（%s）status %s／回の終わり %.1f 秒（%s）／最後の便が戻った %.1f 秒／実時間 %.1f 分" % (
            os.path.basename(run), s["plan"], s["status"], s["sim_s"], hm(s["sim_s"]), s["last_trip_end_s"], s["wall_s"] / 60))
        print("便 %d（走った %d・途中 %d・走れなかった %d）／移る %d（終えた %d）／出るのが遅れた 平均 %.1f 秒・最大 %.1f 秒" % (
            s["trips_scheduled"], s["trips_done"], s["trips_unfinished_at_end"], s["trips_not_run_after_end"] + s["trips_not_run_other"],
            s["moves"], s["moves_done"], s["delay_mean_s"], s["delay_max_s"]))
        print("瞬間移動 ロボット %d（1コマ最大 %.3f m）・人 %d（最大 %.3f m）／人の引き直し（180秒動けない）%d／行き先の近くで止まって待った %d" % (
            s["robot_jumps"], s["robot_max_step_m"], s["person_jumps"], s["person_max_step_m"], s.get("flow_stuck_regoes", 0), s.get("flow_near_stops", 0)))
        st = [e for e in a["ev"] if e["type"] == "4"]
        print("立ち往生 %d: %s" % (len(st), "／".join("%s〜%s秒 %s秒 (%s,%s) %s" % (e["t_s"], e["end_s"], e["value"], e["robot_x"], e["robot_z"], e["note"]) for e in st)))
        print("助け %d: %s" % (len(a["resc"]), "／".join("%s秒 (%s,%s) %s・%s" % (r["call_s"], r["x"], r["z"], r["partner"], r["outcome"]) for r in a["resc"])))
        print("ロボットの入らない所に中心が入った時間 %.2f 秒／どの所にも入っていない時間 %.2f 秒" % (s["robot_forbidden_s"], s["robot_no_zone_s"]))
        if s.get("people_mode") == "flow":
            print("人が access で入れない所に入った時間（役割ごと）：", "・".join("%s %.2f 秒" % (r, s["flow_forbidden_s_" + r]) for r in ROLES))
            print("どの所にも入っていない時間（役割ごと）：", "・".join("%s %.2f 秒" % (r, s["flow_no_zone_s_" + r]) for r in ROLES))
            print("職員 %d 人／持ち場から動いた %d 回・看護師が呼びに出た %d 回（看護師なし %d・間に合わない %d）" % (
                s["flow_staff_total"], s["flow_staff_moves"], s["flow_nurse_calls"], s["flow_nurse_calls_no_nurse"], s["flow_nurse_late"]))
            print("席 %d（使えない %d）・机などの横の点 %d・診察室 %d／所 %d 枚・区画 %d／壁の箱 %d・家具の箱 %d" % (
                s["flow_seats"], s["flow_seats_dropped"], s["flow_spots"], s["flow_consult_rooms"], s["flow_pieces"], s["flow_areas"], s["flow_wall_boxes"], s["flow_furniture_boxes"]))
            print("飛ばした行き先（理由）：", s["flow_skips"])
            people(run, s)
            zones_table(run, s, R149[key])
            prob(a, b143[key], b147[key], b149[key])
            lc = Counter()
            for e in a["ev"]:
                d = a["legs"][e["leg_id"]]["dir"] if e["leg_id"] in a["legs"] else ""
                lc[d] += 1
            print("   区間ごとの数（全部の種類）：", "・".join("%s %d" % kv for kv in lc.items()))
            for k in range(1, 7):
                if a["kinds"][k]: print("   相手 %d %s: %s" % (k, TN[k], "・".join("%s %d" % kv for kv in a["kinds"][k].most_common())))


if __name__ == "__main__":
    main()
```

#### cmp154.py（記録を依頼149 と比べる）

```python
#!/usr/bin/env python3
"""依頼149：2つの回の記録を比べる（同じ名前の CSV の md5・summary.json の違う項目）。
  python3 cmp149.py <回A> <回B> [prefix]   prefix を付けると、短い方の長さまでのバイト列の一致も見る"""
import hashlib, json, os, sys
a, b = sys.argv[1], sys.argv[2]
prefix = len(sys.argv) > 3
fa = sorted(f for f in os.listdir(a) if f.endswith(".csv") and not f.startswith("._"))
fb = sorted(f for f in os.listdir(b) if f.endswith(".csv") and not f.startswith("._"))
md = lambda p: hashlib.md5(open(p, "rb").read()).hexdigest()
same, diff = [], []
for f in sorted(set(fa) | set(fb)):
    if f not in fa or f not in fb: diff.append(f + "（片方だけ）"); continue
    x, y = open(os.path.join(a, f), "rb").read(), open(os.path.join(b, f), "rb").read()
    if x == y: same.append(f)
    elif prefix:
        n = min(len(x), len(y))
        k = next((i for i in range(n) if x[i] != y[i]), n)
        diff.append("%s（頭から %d / %d・%d バイト一致%s）" % (f, k, len(x), len(y), "・短い方は全部一致" if k == n else ""))
    else: diff.append(f)
print("同じ %d：%s" % (len(same), "・".join(same)))
print("違う %d：%s" % (len(diff), "・".join(diff)))
sa = json.load(open(os.path.join(a, "summary.json"), encoding="utf-8")); sb = json.load(open(os.path.join(b, "summary.json"), encoding="utf-8"))
ks = [k for k in sorted(set(sa) | set(sb)) if sa.get(k) != sb.get(k)]
print("summary.json の違う項目：", ", ".join(ks))
```

#### paths154.py（人の通った所と時間を数える）

```python
#!/usr/bin/env python3
"""依頼154（paths149.py の写し：カメラは cams154。寄り 救急の待合・正面玄関から救急へ行く道 は、10:00 ごろ・14:00 ごろの窓の中で、救急の患者が正面玄関から救急の待合へ歩いている人数がいちばん多い分）。もとの説明：依頼149（paths147.py の写しを直した：静止画は cams149 の6つを 10:00 ごろ・14:00 ごろの2回）。もとの説明：依頼147（paths143.py の写しを直した）：撮る回のカメラの道（-h141CamPath の CSV）を、確かめの回 check143a（同じ種・同じ実行ファイル）から作る。

flowday（1日の早回し。依頼141 の paths141.py と同じ作り）：0.05 秒から、ふだん 1.2 秒ごと（30倍）で引き。
  ロボットが走っている間（legs の 行き・帰り・移る。続いている区間はつなぐ）は 0.2 秒ごと（5倍）で付いて行く寄り
  （左30°・75°見下ろし・距離 27.9 m・注視点＝ロボットの位置を σ1.5 秒でならした点）。最初と最後の 25 コマで引き⇄寄り（smoothstep）。
  引きは、そのときロボットのいる側（x<60 は西半分、それ以外は東半分。cams143.py の w_west・w_east）。最後の便が戻ったところで終わる
静止画（cams143.py の8つ）：1行だけの道（その時刻に1枚）。時刻は下の STILLS（ロボットが台の前に止まっている時刻など）

  python3 paths143.py <check143a> <出力.csv> <cams.txt>
"""
import csv, math, os, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from paths141 import Follow, load_track, smooth, g, NEAR, FOV, W, H, TRANS
from cams143 import cams as cams143
from cams154 import cams as cams149, still_cams
from collections import defaultdict

SPLIT_X = 60.0


def main():
    run, dst, camtxt = sys.argv[1], sys.argv[2], sys.argv[3]
    T, X, Z = load_track(run)
    fo = Follow(T, X, Z)
    C = {c[0]: c for c in cams143()}
    WIDE = {"west": dict(tx=C["w_west"][3], tz=C["w_west"][4], dist=C["w_west"][5]),
            "east": dict(tx=C["w_east"][3], tz=C["w_east"][4], dist=C["w_east"][5])}
    legs = list(csv.DictReader(open(run + "/legs.csv", encoding="utf-8")))
    trips = list(csv.DictReader(open(run + "/trips.csv", encoding="utf-8")))
    last = max(float(t["end_s"]) for t in trips if t["end_s"])
    # 走っている区間（行き・帰り・移る）をつなぐ
    wins = []
    for l in legs:
        if l["dir"] not in ("行き", "帰り", "移る"): continue
        s, e = float(l["start_s"]), float(l["end_s"])
        if wins and s - wins[-1][1] < 2.0: wins[-1][1] = e
        else: wins.append([s, e])
    side = lambda t: "west" if fo.raw(t)[0] < SPLIT_X else "east"

    def blend(a, wide, near_xz):
        tx = wide["tx"] + a * (near_xz[0] - wide["tx"])
        tz = wide["tz"] + a * (near_xz[1] - wide["tz"])
        d = math.exp(math.log(wide["dist"]) + a * (math.log(NEAR["dist"]) - math.log(wide["dist"])))
        return tx, tz, d

    rows = []
    t, wi = 0.05, 0
    while True:
        if wi < len(wins) and t >= g(wins[wi][0]) - 1e-6:
            s0, e0 = g(wins[wi][0]), wins[wi][1]
            n = int(math.ceil((g(e0) - s0) / 0.2 - 1e-6))
            ts = [g(s0 + 0.2 * k) for k in range(n + 1)]
            w_in, w_out = WIDE[side(s0)], WIDE[side(e0)]
            for k, tt in enumerate(ts):
                a_in = smooth((k + 1) / TRANS); a_out = smooth((len(ts) - k) / TRANS)
                a = min(a_in, a_out)
                wide = w_in if k < len(ts) / 2 else w_out
                tx, tz, d = blend(a, wide, fo.at(tt))
                rows.append(("flowday", tt, tx, tz, d, "5倍", a, side(s0) if k < len(ts) / 2 else side(e0)))
            t = ts[-1]
            wi += 1
            if t >= last - 1e-6 or wi == len(wins): break
            t = g(t + 1.2)
            continue
        sd = side(t)
        rows.append(("flowday", t, WIDE[sd]["tx"], WIDE[sd]["tz"], WIDE[sd]["dist"], "30倍", 0.0, sd))
        nt = g(t + 1.2)
        if wi < len(wins) and nt > g(wins[wi][0]): nt = g(wins[wi][0])
        t = nt
    # 静止画の時刻（依頼149）：確かめの回の zones147.csv（1分ごとの所ごとの人数）から、9:30〜10:30・13:30〜14:30 で人がいちばん多い分
    zl = list(csv.DictReader(open(run + "/zones147.csv", encoding="utf-8")))
    tot = defaultdict(int)
    for r in zl:
        tot[float(r["t_s"])] += int(r["patients"]) + int(r["staff"])
    pick = lambda d, a, b, c: max((t for t in d if a <= t <= b), key=lambda t: (d[t], -abs(t - c)))
    t10 = pick(tot, 7200.0, 10800.0, 9000.0)
    t14 = pick(tot, 21600.0, 25200.0, 23400.0)
    # 救急の患者：正面玄関に出てから救急の待合に着くまで（歩いている）・救急の待合にいる間
    walk, wait = [], []
    for p in csv.DictReader(open(run + "/people147.csv", encoding="utf-8")):
        if p["what"] != "救急の患者" or not p["spawn_s"]: continue
        st = p["steps"].split("|")[0].split(":")
        if len(st) >= 4 and st[2]:
            walk.append((float(p["spawn_s"]), float(st[2])))
            wait.append((float(st[2]), float(st[3]) if st[3] else 1e9))
    er = {}
    for t in tot:
        er[t] = (sum(1 for a, b in walk if a <= t < b), sum(1 for a, b in wait if a <= t < b))
    def pick_er(a, b, c, k):
        ts = [t for t in er if a <= t <= b]
        best = max(ts, key=lambda t: (er[t][k], er[t][1 - k], -abs(t - c)))
        return best
    STILLS = {}
    for n, c, tk in still_cams():
        lo, hi, mid, lab = (7200.0, 10800.0, 9000.0, "9時30分〜10時30分") if tk == "10" else (21600.0, 25200.0, 23400.0, "13時30分〜14時30分")
        if n.startswith("n_er"):
            k = 0 if n.startswith("n_erway") else 1
            tt = pick_er(lo, hi, mid, k)
            STILLS[n] = (g(tt), ("%s で救急の待合に救急の患者がいちばん多い分（%d 人）" % (lab, er[tt][1])) if k == 1 else
                         ("%s で正面玄関から救急へ歩く救急の患者がいる分（%d 人）" % (lab, er[tt][0])))
            continue
        tt = t10 if tk == "10" else t14
        STILLS[n] = (g(tt), "%s で人がいちばん多い分（%d 人）" % (lab, tot[tt]))
    SC = {n: c for n, c, tk in still_cams()}
    with open(dst, "w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["cam", "t_s", "tx", "tz", "dist"])
        for c, tt, x, z, d, sp, a, sd in rows:
            w.writerow([c, "%.2f" % tt, "%.4f" % x, "%.4f" % z, "%.4f" % d])
        for n in STILLS:
            c = SC[n]
            w.writerow([n, "%.2f" % STILLS[n][0], "%.4f" % c[3], "%.4f" % c[4], "%.4f" % c[5]])
    with open(dst.replace(".csv", "_meta.csv"), "w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["cam", "t_s", "speed", "near_a", "side"])
        for c, tt, x, z, d, sp, a, sd in rows:
            w.writerow([c, "%.2f" % tt, sp, "%.4f" % a, sd])
    with open(dst.replace(".csv", "_stills.csv"), "w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["cam", "t_s", "what"])
        for n, (tt, what) in STILLS.items():
            w.writerow([n, "%.2f" % tt, what])
    with open(camtxt, "w") as f:
        x0, z0 = WIDE["east"]["tx"], WIDE["east"]["tz"]
        f.write("flowday,30,75,%.4f,%.4f,%.4f,%d,%d,%d\n" % (x0, z0, WIDE["east"]["dist"], FOV, W, H))
        for n, c, tk in still_cams():
            f.write("%s,%g,%g,%.2f,%.2f,%.1f,%d,%d,%d\n" % (n, c[1], c[2], c[3], c[4], c[5], FOV, W, H))
    n5 = sum(1 for r in rows if r[5] == "5倍")
    print("flowday %d コマ（%.1f 秒＝%.1f 分。5倍 %d・30倍 %d）〜%.2f／走っている区間 %d" % (len(rows), len(rows) / 25, len(rows) / 25 / 60, n5, len(rows) - n5, rows[-1][1], len(wins)))
    for n, (tt, what) in STILLS.items(): print(n, tt, what)


if __name__ == "__main__":
    main()
```

#### cams154.py（静止画のカメラを決める）

```python
#!/usr/bin/env python3
"""依頼154：カメラ（-h139Cam の値）。依頼149 の6つ（cams149.py。真上の全体・引き 西・東・寄り エレベーターホール・中待合・総合受付と会計の待合）に、
寄り2つを足す（左30°・75°見下ろし。距離と注視点は依頼139 の camfit139.py と同じ決め方）：
  救急の待合（x98〜121・z4〜31.5：救急の待合・救急の処置の場所・その前の大廊下）、
  正面玄関から救急へ行く道（x42〜121・z−0.5〜31.5：正面玄関・職員の中廊下 x44〜52・玄関の通り・大廊下の東・救急の待合）。
静止画は 10:00 ごろと 14:00 ごろの2つの時刻に8つとも撮る（名前の後ろに _10・_14）"""
import os, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from camfit139 import fit
from cams149 import cams as cams149

NEAR = [("n_er", "寄り・救急の待合", (98.0, 121.0, 4.0, 31.5)),
        ("n_erway", "寄り・正面玄関から救急へ行く道", (42.0, 121.0, -0.5, 31.5))]
BASE = ["top_all", "w_west", "w_east", "n_elev", "n_midwait", "n_wait", "n_er", "n_erway"]
TIMES = ["10", "14"]


def cams():
    out = list(cams149())
    for key, ja, (x0, x1, z0, z1) in NEAR:
        tx, tz, d = fit(30.0, 75.0, x0, x1, z0, z1)
        out.append((key, 30.0, 75.0, tx, tz, d, ja, (x0, x1, z0, z1)))
    return out


def still_cams():
    C = {c[0]: c for c in cams()}
    return [(b + "_" + t, C[b], t) for t in TIMES for b in BASE]


if __name__ == "__main__":
    for n, yaw, p, tx, tz, d, ja, rng in cams():
        if len(sys.argv) > 1 and sys.argv[1] == "table":
            print("%s\t%s\t方位%g 見下ろし%g\t範囲 x%g〜%g z%g〜%g\t注視点(%.2f, %.2f)\t距離 %.1f" % (n, ja, yaw, p, rng[0], rng[1], rng[2], rng[3], tx, tz, d))
        else:
            print("%s,%g,%g,%.2f,%.2f,%.1f,40,2720,2160" % (n, yaw, p, tx, tz, d))
```

#### pan154.py（静止画と動画に時刻と問題の数のパネルを付ける）

```python
#!/usr/bin/env python3
"""依頼154（pan149.py の写し：カメラは cams154（寄り 救急の待合・正面玄関から救急へ行く道を足す）、静止画は8つ×2つの時刻）。もとの説明：依頼149（pan147.py の写しを直した：カメラは cams149、静止画は6つ×2つの時刻、エレベーター・トイレ）。もとの説明：依頼147（pan143.py の写しを直した：カメラは cams147、静止画7枚、題に人の動き）。もとの説明：依頼143：h1 の静止画（8枚）と1日の早回しの動画に、輪・パネル・凡例・上の帯を付ける（1920×1080）。依頼141 の pan141.py と同じ作り
（輪・凡例・パネルの形は依頼140 の pan140.py）。違うのは：病院 h1、ロボットの状態（台の前で待っている・積む所へ移る）、
行き先の名前を短く、道（緑・青（表）・青（裏口）・赤）、引きは西半分と東半分、問題を 廊下／部屋の中 に分けて帯に出す。

  python3 pan143.py stills <回のフォルダ> <出力フォルダ>
  python3 pan143.py video <回のフォルダ> flowday <出力.mp4> [並べる数]
  python3 pan143.py frame <回のフォルダ> flowday <コマ番号> <出力.png>
"""
import csv, json, math, os, subprocess, sys, tempfile
from multiprocessing import Pool
from PIL import Image, ImageDraw
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import pan140 as P
from cams154 import cams as cams143, still_cams

W, H, SW = P.W, P.H, P.SW
SHORT = {"救急室の処置室": "救急室", "内科外来の処置室": "内科（表）", "内科外来の処置室（裏口）": "内科（裏口）", "薬剤部の払い出し口": "薬剤部",
         "検査部の検体受付": "検体受付", "外来化学療法室の受付": "化学療法室"}
ROUTE_JA = {"green": "緑", "blue": "青（表）", "blue_back": "青（裏口）", "red": "赤"}
STATE = [("待ち", "台の前で待っている"), ("行き", "行き"), ("帰り", "帰り"), ("移る", "積む所へ移る")]
CAMJA = {c[0]: c[6] + ("" if c[0] == "top_all" else "・75°見下ろし・左30°") for c in cams143()}
CAMJA.update({n: CAMJA[c[0]] for n, c, tk in still_cams()})
RUN = None
DATA = {}


class PathCam:
    def __init__(self, proj, row, w, h, name):
        self.name = name
        self.w, self.h = w, h
        self.p = proj
        self.v = [[float(row["v%d%d" % (r, k)]) for k in range(4)] for r in range(4)]
        self.sx, self.sy = SW / w, H / h

    px = P.Cam.px
    ppm = P.Cam.ppm


def counts(ev, t0, t):
    c = {k: 0 for k in range(1, 7)}
    c["fast"] = c["wait2"] = c["room"] = 0
    for e in ev:
        te = float(e["t_s"])
        if te < t0 - 1e-6 or te > t + 1e-6: continue
        k = int(e["type"])
        if e["dir"] == "待ち":
            if k == 2: c["wait2"] += 1
            continue
        c[k] += 1
        if e.get("area") == "部屋の中" and k <= 5: c["room"] += 1
        if k == 2 and e.get("note") == "速いまま": c["fast"] += 1
    return c


def panel(im, row, ev, t, t0, title0, camja, summ, wide):
    d = ImageDraw.Draw(im)
    d.rectangle([SW, 0, W, H], fill=P.PANEL_BG)
    x0, xr = SW + 40, W - 40
    f_s, f_m, f_mb, f_big, f_num = P.font(False, 24), P.font(False, 27), P.font(True, 29), P.font(True, 54), P.font(True, 46)
    d.text((x0, 24), "病院 h1・人の動き・%s・%.1fm/秒・種%d" % (summ["plan"], summ["speed_mps"], summ["seed"]), font=f_s, fill=P.GRAY)
    d.text((x0, 56), "色 %s・車いすの場所 %s" % (P.SCHEME_JA["A"], "あり" if summ.get("wheelchair_marks") == "on" else "なし"), font=f_s, fill=P.GRAY)
    d.text((x0, 88), "カメラ %s" % camja, font=P.font(False, 22 if len(camja) > 18 else 24), fill=P.GRAY)
    d.text((x0, 122), P.clock(t), font=f_big, fill=P.INK)
    y = 206
    d.text((x0, y), "ロボット・スタッフがしていること", font=f_mb, fill=P.INK)
    y += 52
    d.text((x0, y), "ロボット 1台", font=f_m, fill=P.INK)
    y += 44
    cur = row["leg_dir"]
    for key, ja in STATE:
        on = key == cur
        d.text((x0 + 8, y), ja, font=f_m, fill=P.INK if on else (150, 150, 148))
        if on:
            d.ellipse([xr - 40, y + 2, xr - 4, y + 38], fill=P.WHITE, outline=(80, 80, 80), width=3)
            if key in ("行き", "帰り", "移る") and row["leg_from"]:
                d.text((xr - 52, y + 20), "%s→%s" % (SHORT.get(row["leg_from"], row["leg_from"]), SHORT.get(row["leg_to"], row["leg_to"])), font=f_s, fill=P.GRAY, anchor="rm")
            elif key == "待ち" and row["leg_from"]:
                d.text((xr - 52, y + 20), SHORT.get(row["leg_from"], row["leg_from"]), font=f_s, fill=P.GRAY, anchor="rm")
        y += 40
    y += 10
    d.text((x0, y), "スタッフ", font=f_m, fill=P.INK)
    y += 44
    rs = int(row["rescue_staff"])
    d.text((x0 + 8, y), "ロボットの助け", font=f_m, fill=P.INK if rs else (150, 150, 148))
    for i in range(rs):
        d.rectangle([xr - 36 - i * 44, y + 3, xr - 4 - i * 44, y + 35], fill=P.BLUE)
    y += 52
    d.line([x0, y, xr, y], fill=P.LINE, width=2)
    y += 14
    d.text((x0, y + 8), "大廊下の人（x0〜70）", font=f_m, fill=P.INK)
    d.text((xr, y + 28), row["hall_people"], font=f_num, fill=P.INK, anchor="rm")
    y += 66
    d.line([x0, y, xr, y], fill=P.LINE, width=2)
    y += 14
    d.text((x0, y), title0, font=f_mb, fill=P.INK)
    y += 46
    c = counts(ev, t0, t)
    for k in range(1, 6):
        d.ellipse([x0 + 6, y + 6, x0 + 30, y + 30], outline=P.COLORS[k], width=4)
        d.text((x0 + 44, y + 18), "%d %s" % (k, P.TYPES[k]), font=f_m, fill=P.INK, anchor="lm")
        d.text((xr, y + 18), str(c[k]), font=P.font(True, 34), fill=P.INK, anchor="rm")
        y += 42
    d.ellipse([x0 + 6, y + 6, x0 + 30, y + 30], outline=P.COLORS[2], width=4)
    d.text((x0 + 44, y + 18), "台の前で待っている間のひやり", font=f_s, fill=P.INK, anchor="lm")
    d.text((xr, y + 18), str(c["wait2"]), font=P.font(True, 34), fill=P.INK, anchor="rm")
    y += 48
    d.rectangle([x0 + 9, y + 5, x0 + 27, y + 23], outline=P.FAST_COL, width=3)
    d.rectangle([x0 + 4, y, x0 + 32, y + 28], outline=P.FAST_COL, width=2)
    d.text((x0 + 44, y + 14), "うち速いままのひやり（走っている間） %d" % c["fast"], font=f_s, fill=P.INK, anchor="lm")
    y += 36
    d.rectangle([x0 + 5, y + 1, x0 + 31, y + 27], outline=(255, 210, 0), width=4)
    d.line([x0 + 9, y + 5, x0 + 27, y + 23], fill=P.OVER_COL, width=5)
    d.line([x0 + 9, y + 23, x0 + 27, y + 5], fill=P.OVER_COL, width=5)
    d.text((x0 + 44, y + 14), "止まりきれなかった %d" % c[6], font=f_s, fill=P.INK, anchor="lm")
    y += 38
    note = "輪＝問題の起きた所（10秒）" + ("・白い輪＝ロボット" if wide else "")
    d.text((x0, min(y + 2, H - 34)), note, font=P.font(False, 21), fill=P.GRAY)
    return c


def legend_bottom(im):
    box = im.crop((0, H - 104, SW, H))
    P.legend(box)
    im.paste(box, (0, H - 104))


def load(run, cam):
    rows = [r for r in csv.DictReader(open(os.path.join(run, "look139", "look_frames.csv"), encoding="utf-8")) if r["kind"] == "path" and r["cam"] == cam]
    paths = [r for r in csv.DictReader(open(os.path.join(run, "look139", "look_path.csv"), encoding="utf-8")) if r["cam"] == cam]
    base = os.path.dirname(os.path.dirname(run))
    meta = [r for r in csv.DictReader(open(os.path.join(base, "camspath154_meta.csv"), encoding="utf-8")) if r["cam"] == cam]
    assert len(rows) == len(paths), (len(rows), len(paths))
    if cam == "flowday":
        assert len(rows) == len(meta), (len(rows), len(meta))
        for a, b in zip(rows, meta):
            assert abs(float(a["t_s"]) - float(b["t_s"])) < 0.051, (a["t_s"], b["t_s"])
    j = json.load(open(os.path.join(run, "look139", "cam_%s.json" % cam), encoding="utf-8"))
    ev = list(csv.DictReader(open(os.path.join(run, "events.csv"), encoding="utf-8")))
    summ = json.load(open(os.path.join(run, "summary.json"), encoding="utf-8"))
    trips = {r["trip_no"]: r for r in csv.DictReader(open(os.path.join(run, "trips.csv"), encoding="utf-8"))}
    return dict(rows=rows, paths=paths, meta=meta, proj=j["projectionMatrix"], w=j["width"], h=j["height"], ev=ev, summ=summ, cam=cam, trips=trips)


def compose(i):
    D = DATA
    r, pr = D["rows"][i], D["paths"][i]
    t = float(r["t_s"])
    src = Image.open(os.path.join(RUN, "look139", r["file"])).convert("RGB").resize((SW, H), Image.LANCZOS)
    im = Image.new("RGB", (W, H), P.PANEL_BG)
    im.paste(src, (0, 0))
    m = D["meta"][i]
    a = float(m["near_a"])
    wide = a < 0.5
    cam = PathCam(D["proj"], pr, D["w"], D["h"], "w_" if wide else "n_")
    P.WIDE.add("w_")
    P.overlay(im, cam, D["ev"], t, P.font(True, 22), (float(r["robot_x"]), float(r["robot_z"])))
    legend_bottom(im)
    sp = m["speed"]
    trip = r["trip_no"] if r["trip_no"] not in ("", "0") else ""
    sd = "西半分" if m["side"] == "west" else "東半分"
    camja = ("引き・%s・75°見下ろし・左30°" % sd) if a <= 0 else ("寄り・75°見下ろし・左30°・付いて行く" if a >= 1 else "引き⇄寄りに切り替え中")
    c = panel(im, r, D["ev"], t, 0.0, "7時30分からの1日の問題", camja, D["summ"], wide)
    route = ""
    if trip and trip in D["trips"]:
        route = "　%s便目・%s（%s→%s）" % (trip, ROUTE_JA.get(D["trips"][trip]["route"], ""), SHORT.get(D["trips"][trip]["from"], ""), SHORT.get(D["trips"][trip]["to"], ""))
    elif r["leg_dir"] == "移る":
        route = "　積む所へ移る（%s→%s）" % (SHORT.get(r["leg_from"], r["leg_from"]), SHORT.get(r["leg_to"], r["leg_to"]))
    t1 = "%s　店の時刻の%s%s" % (P.clock(t), sp, route if sp == "5倍" else "")
    t2 = "1日の問題（走っている間）　1 急に止まった %d・2 ひやり %d・3 人がよけさせられた %d・4 立ち往生 %d・5 横切り %d（うち部屋の中 %d）" % (
        c[1], c[2], c[3], c[4], c[5], c["room"])
    P_banner(im, t1, t2, "台の前で待っている間のひやり %d" % c["wait2"])
    return im


def P_banner(im, text1, text2, text3=""):
    lay = Image.new("RGBA", (SW, 140 if text3 else 104), (249, 247, 244, 225))
    d = ImageDraw.Draw(lay)
    d.text((24, 31), text1, font=P.font(True, 34 if len(text1) < 40 else 30), fill=P.INK, anchor="lm")
    d.text((24, 80), text2, font=P.font(False, 22), fill=P.INK, anchor="lm")
    if text3:
        d.text((24, 116), text3, font=P.font(False, 22), fill=P.INK, anchor="lm")
    im.paste(lay, (0, 0), lay)


def still(run, cam, out, what):
    D = load(run, cam)
    assert len(D["rows"]) == 1, (cam, len(D["rows"]))
    r, pr = D["rows"][0], D["paths"][0]
    t = float(r["t_s"])
    src = Image.open(os.path.join(run, "look139", r["file"])).convert("RGB").resize((SW, H), Image.LANCZOS)
    im = Image.new("RGB", (W, H), P.PANEL_BG)
    im.paste(src, (0, 0))
    wide = not cam.startswith("n_")
    pc = PathCam(D["proj"], pr, D["w"], D["h"], "w_" if wide else "n_")
    P.WIDE.add("w_")
    P.overlay(im, pc, D["ev"], t, P.font(True, 22), (float(r["robot_x"]), float(r["robot_z"])))
    legend_bottom(im)
    panel(im, r, D["ev"], t, 0.0, "7時30分からの1日の問題", CAMJA[cam], D["summ"], wide)
    P_banner(im, "%s　%s" % (P.clock(t), CAMJA[cam].split("・")[0] + "・" + CAMJA[cam].split("・")[1] if "・" in CAMJA[cam] else CAMJA[cam]), what)
    f = os.path.join(out, "still154_%s.png" % cam)
    im.save(f)
    return f, t


def init(run, cam):
    global RUN, DATA
    RUN = run
    DATA = load(run, cam)


def seg(args):
    run, cam, lo, hi, dst = args
    init(run, cam)
    p = subprocess.Popen([P.FFMPEG, "-y", "-loglevel", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", "%dx%d" % (W, H), "-framerate", "25",
                          "-i", "-", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-crf", "18", "-g", "50", dst], stdin=subprocess.PIPE)
    for i in range(lo, hi):
        p.stdin.write(compose(i).tobytes())
    p.stdin.close()
    p.wait()
    return dst


def main():
    mode, run = sys.argv[1], sys.argv[2]
    if mode == "stills":
        out = sys.argv[3]
        os.makedirs(out, exist_ok=True)
        base = os.path.dirname(os.path.dirname(run))
        whats = {r["cam"]: r["what"] for r in csv.DictReader(open(os.path.join(base, "camspath154_stills.csv"), encoding="utf-8"))}
        for c, _, _ in still_cams():
            print(*still(run, c, out, whats[c]))
        return
    cam = sys.argv[3]
    if mode == "frame":
        init(run, cam)
        compose(int(sys.argv[4])).save(sys.argv[5])
        print(sys.argv[5], DATA["rows"][int(sys.argv[4])]["t_s"])
        return
    dst = sys.argv[4]
    nproc = int(sys.argv[5]) if len(sys.argv) > 5 else 8
    n = len(load(run, cam)["rows"])
    tmp = tempfile.mkdtemp(prefix="pan154_", dir=os.path.dirname(os.path.abspath(dst)))
    k = max(1, math.ceil(n / (nproc * 4)))
    jobs = [(run, cam, lo, min(n, lo + k), os.path.join(tmp, "s%04d.mp4" % (lo // k))) for lo in range(0, n, k)]
    with Pool(nproc) as pool:
        parts = pool.map(seg, jobs, chunksize=1)
    lst = os.path.join(tmp, "list.txt")
    open(lst, "w").write("".join("file '%s'\n" % q for q in parts))
    subprocess.check_call([P.FFMPEG, "-y", "-loglevel", "error", "-f", "concat", "-safe", "0", "-i", lst, "-c", "copy", "-movflags", "+faststart", dst])
    for q in parts: os.remove(q)
    os.remove(lst); os.rmdir(tmp)
    print(dst, n, "コマ", n / 25.0, "秒")


if __name__ == "__main__":
    main()
```

#### sheet154.py（静止画16枚を並べる）

```python
#!/usr/bin/env python3
"""依頼154：静止画16枚（パネル付き）を1枚に並べる（上の2段＝10:00 ごろ、下の2段＝14:00 ごろ。各段4枚：真上の全体・引き 西半分・引き 東半分・寄り エレベーターホール／
寄り 中待合・総合受付と会計の待合・救急の待合・正面玄関から救急へ行く道）
  python3 sheet154.py <静止画のフォルダ> <camspath154_stills.csv> <出力.png>"""
import csv, os, sys
from PIL import Image, ImageDraw
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import pan140 as P
from cams154 import cams, still_cams

src, stills, dst = sys.argv[1:4]
what = {r["cam"]: (float(r["t_s"]), r["what"]) for r in csv.DictReader(open(stills, encoding="utf-8"))}
ja = {c[0]: c[6] for c in cams()}
sc = still_cams()
tw, th, cap = 960, 540, 64
sheet = Image.new("RGB", (tw * 4 + 50, (th + cap) * 4 + 90), (255, 255, 255))
d = ImageDraw.Draw(sheet)
d.text((10, 34), "依頼154　病院 h1・人の動き（救急の患者・X線室へ行く病棟の患者・ほかの階の職員を通した）・廊下だけ・0.6m/秒・種0・色A　上2段＝10時ごろ・下2段＝14時ごろ",
       font=P.font(True, 30), fill=P.INK, anchor="lm")
mark = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯"
for k, (n, c, tk) in enumerate(sc):
    im = Image.open(os.path.join(src, "still154_%s.png" % n)).convert("RGB").resize((tw, th), Image.LANCZOS)
    x, y = 10 + (k % 4) * (tw + 10), 70 + (k // 4) * (th + cap)
    sheet.paste(im, (x, y))
    t, w = what[n]
    d.text((x + 4, y + th + 22), "%s　%s" % (mark[k], ja[c[0]]), font=P.font(True, 24), fill=P.INK, anchor="lm")
    d.text((x + 4, y + th + 50), "%s（%.2f 秒）　%s" % (P.clock(t), t, w), font=P.font(False, 18 if len(w) > 40 else 20), fill=P.GRAY, anchor="lm")
sheet.save(dst)
print(dst, sheet.size)
```


## 残したことと、次の依頼155 で直すこと

依頼154 の「決めてほしいこと」3つは、次のように決めました。

| 番号 | 依頼154 で止まったこと | 決めた答え |
|---|---|---|
| 1 | 化学療法のトイレへの通り道が幅0.35m で通れない | サイト側でフロアの形を直し、約0.9m 以上にする |
| 2 | 職員が、通り抜けのために足した所へふだんも行き、問題が増えた | ふだんの行き先はもとの持ち場の所だけにする。足した所は付き添いと通り抜けのときだけ |
| 3 | Unity で決めた値（看護師は患者のそば0.7m で待つ・患者の速さに合わせて歩く・X線室では立って待つ） | このままでよい |

1 と 2 はフロアの形の書き方（どの「入る」が通り抜け・付き添いだけなのか）から直すので、サイト側を先に直してから、依頼155 として依頼箱に置きます。問題の数が依頼149 の84件の近くに戻るか、東の端の37件が消えるかで、原因が合っていたかを確かめます。
