> **【注記】** 2026-09-03、TeamWorks 提供の実DDL（`clean_watch_20260902.sql`）と手書き `prisma/schema.prisma` の答え合わせのために作成した記録です（DDL文を直接パースする方法）。
> `prisma db pull` による照合結果 `ddl-diff-dbpull.md` と対になる相互検証の記録です。両者を独立に突き合わせるために両方を残しています。
> **当時の記録であり、現行方針ではありません。** 以降の決定は `docs/decisions.md` と `docs/handoff.md` を参照してください。

# 実DDL 直接パースによる独立比較（担当: DDLテキスト解析）

- 入力1（正）: `C:\Users\daich\Downloads\clean_watch_20260902.sql`（46,390 bytes / mysqldump 10.13, MySQL 8.0.41, DB名 `clean_watch`）
- 入力2（比較対象）: `C:\Users\daich\dev\d-watch\prisma\schema.prisma`（790行 / 28 model / 手書き版）
- 入力3（参考）: 定義書転記 `01-master.md` / `02-clean-error.md` / `03-schedule-summary.md`
- MySQL への接続は一切していない（テキスト解析のみ）。d-watch リポジトリは読み取りのみ、変更なし。
- 相手担当（`prisma db pull`）の成果物（`pulled.prisma` / `diff.json` / `diff.txt` / `30-dbpull-diff.md`）は読んでいない。

---

## 1. パースの方法と、パースできなかった箇所

### 1-1. DDL パーサ（`mine/parse_ddl.py`）

正規表現による分割は使わず、**文字単位の状態機械**で処理した。

1. `CREATE TABLE \`x\` (` を起点に、開き括弧から**括弧深度を数えて** depth が 0 に戻る位置までを1テーブルのブロックとして切り出す。
   走査中は次を読み飛ばすため、`decimal(12,6)` や `enum('a','b')` の内側の括弧・カンマでは壊れない。
   - シングルクォート文字列（`\` エスケープ と `''` 二重化の両方に対応）
   - ダブルクォート文字列 / バッククォート識別子
   - `/* */` ブロックコメント、`--` 行コメント、`#` 行コメント
2. ブロック本体を**深度0のカンマだけ**で分割し、1要素 = 1定義（カラム / PRIMARY KEY / UNIQUE KEY / KEY / CONSTRAINT）に落とす。
3. カラム定義は「識別子 → 型名 → 括弧付きサイズ（これも括弧対応で読む） → 属性列」の順に手で読み進め、
   `unsigned` / `zerofill` / `NOT NULL` / `DEFAULT` / `AUTO_INCREMENT` / `ON UPDATE` / `COMMENT` / 生成列を抽出した。
4. 閉じ括弧の直後から `;` までを table_options として `ENGINE` / `CHARSET` / `AUTO_INCREMENT=n` の取得に使った。

**結果: 39テーブル / 372カラム、パース失敗 0件。**

健全性チェック（別手段での裏取り）: DDL 本文の `^  \`名前\` ` 行を grep すると 368 行。
差の 4 行は列名に数字を含む `addr1` / `addr2`（customers と facilities に各2列）で、grep の文字クラスが数字を含まなかったための取りこぼし。
368 + 4 = 372 で**パース結果と一致**。取りこぼし・二重取りは無い。

### 1-2. prisma パーサ

`model X { ... }` を波括弧深度で切り出し、フィールド行ごとに
`@@map`（物理テーブル名）/ `@db.*`（MySQL型）/ `?`（NULL許容）/ `@default(...)`（括弧対応で抽出）/
`@id` / `@updatedAt` / `@relation(fields:..., references:...)` / `@@index` / `@@unique` を読んだ。
`Type[]` の配列フィールドと `@relation` 属性を持たない非スカラーフィールドは DB カラムではないので除外した。

**結果: 28 model / 311 カラム、パース失敗 0件。**

`@db.*` → MySQL 型の対応は `UnsignedBigInt→bigint unsigned` 等の表引きで正規化し、
`@db.Timestamp(0)` / `@db.DateTime(0)` の `(0)` は MySQL 既定精度なので型名からは落として比較した（誤差分を出さないため）。

### 1-3. パースできなかった / 注意が必要な箇所

DDL・prisma いずれもパース失敗は 0 件。ただし次の 3 点は解析上の制約として明記する。

| # | 箇所 | 内容 |
|---|---|---|
| P-1 | 定義書転記 md の「auto increment」 | 定義書は auto increment を**初期値欄ではなく備考欄**に書いている。素朴に初期値欄だけを比較すると22テーブルの `id` が「定義書に初期値なし / DDLはauto increment」という偽の差分になる。備考欄側を見て除外した（§7-8 の件数はこれを除外済み）。 |
| P-2 | 定義書 `controls` の `id` 行 | 定義書側の通し番号(#)欄が空欄のため、行番号が数字である前提のパーサでは行が落ちる。空欄も許容するよう修正して取り込んだ。 |
| P-3 | `decimal` の unsigned | Prisma には unsigned decimal を表現する native type が無い。よって §4 の decimal 6件は「Prisma で表現できない差」であり、手書き版のミスとは言い切れない（ただし DB の実態としては unsigned なので差分として計上する）。 |

### 1-4. 作業中に起きた事故（申し送り）

出力先 `scratchpad\ddl\` は相手担当（db pull 側）と共有ディレクトリだった。
作業中に相手担当が同名の `compare.py` を同ディレクトリへ書き込み、当方のスクリプトが上書きされた。
その時点の比較結果 JSON は残っていたため結果への影響は無いが、以後は当方の成果物を `scratchpad\ddl\mine\` に隔離した。
なお `prisma/schema.prisma` の md5 は `a1d48b7927c2b93171d3394986abbdf1` で、相手担当が取ったバックアップ `schema.prisma.HANDWRITTEN.bak` と一致しており、**手書き版の本体は書き換えられていない**ことを確認済み（比較には手書き版を使用）。

---

## 2. テーブル単位の差分

Laravel 基盤10テーブル（`users` / `sessions` / `jobs` / `migrations` / `cache` / `cache_locks` / `failed_jobs` / `job_batches` / `password_reset_tokens` / `personal_access_tokens`、DDL側で計60カラム）は比較から除外。

- 実DDL 業務テーブル: **29テーブル / 312カラム**
- 手書き prisma: **28モデル / 311カラム**

| 区分 | 件数 | テーブル |
|---|---|---|
| 実DDL にあり手書きに無い | **1** | `clean_link_schedules` |
| 手書きにあり実DDL に無い | **0** | （なし） |
| 両方にある | 28 | — |

カラム総数の整合（取りこぼしが無いことの確認）:
共通カラム 300 + 実DDLのみ3 + `clean_link_schedules` 9 = **312**（DDL業務側と一致）／
共通カラム 300 + 手書きのみ11 = **311**（prisma側と一致）。

### `clean_link_schedules` について

**実DDL 上で `link_schedules` と構造が完全一致**（列順まで一致、全カラムの型・unsigned・NULL許容・既定値・AUTO_INCREMENT・ON UPDATE がすべて同一、PK も `id`、FK も `section_id → sections(id)` の1本のみ）。詳細は §7-4。

---

## 3. カラム単位の差分（テーブルごと）

### 3-1. 実DDL にあって手書き prisma に無いカラム（3件）

| テーブル | カラム | 実DDL の定義 |
|---|---|---|
| `robot_error_reports` | `responded_at` | `datetime DEFAULT NULL` |
| `control_error_histories` | `responded_at` | `datetime DEFAULT NULL` |
| `control_reports` | `serial_number` | `varchar(100) NOT NULL` |

### 3-2. 手書き prisma にあって実DDL に無いカラム（11件）

| テーブル | カラム | 手書きの定義 | 判定 |
|---|---|---|---|
| `robot_error_reports` | `responsed_at` | `DateTime? @db.DateTime(0)` | **綴り違い**（実物は `responded_at`） |
| `control_error_histories` | `responsed_at` | `DateTime? @db.DateTime(0)` | **綴り違い**（実物は `responded_at`） |
| `control_reports` | `sireal_number` | `String @db.VarChar(100)` | **綴り違い**（実物は `serial_number`） |
| `section_robot_statuses` | `indicatived_at` | `DateTime? @db.Timestamp(0)` | **実DDLに該当列が存在しない**（改名先も無し） |
| `section_robot_statuses` | `deleted_at` | `DateTime? @db.Timestamp(0)` | 実DDLに無い（論理削除なし） |
| `clean_statuses` | `deleted_at` | `DateTime? @db.Timestamp(0)` | 実DDLに無い（論理削除なし） |
| `clean_schedules` | `started_at` | `DateTime? @db.DateTime(0)` | 実DDLに無い |
| `clean_schedules` | `ended_at` | `DateTime? @db.DateTime(0)` | 実DDLに無い |
| `clean_timetable_histories` | `inactive` | `Int @default(0) @db.UnsignedTinyInt` | 実DDLに無い |
| `robots` | `task_id` | `String? @db.VarChar(20)` | 実DDLに無い（`robot_task_versions` 側にのみ存在） |
| `robots` | `version` | `String? @db.VarChar(20)` | 実DDLに無い（`robot_task_versions` 側にのみ存在） |

うち 3 件（`responsed_at`×2 / `sireal_number`）は**単純な改名**で、実質のカラム欠落は 8 件。

### 3-3. カラム集合（名前の集合）が完全一致したテーブル: **20 / 28**

`controls` / `customers` / `customer_contacts` / `customer_error_summaries` / `customer_summaries` /
`devices` / `facilities` / `facility_contacts` / `facility_error_summaries` / `facility_summaries` /
`groups` / `link_schedules` / `robot_instruct_histories` / `robot_task_versions` / `sections` /
`security_statuses` / `security_status_histories` / `clean_status_histories` / `clean_task_reports` / `clean_timetables`

カラム集合が一致しなかった **8テーブル**（§3-1 / §3-2 の内訳）:
`robot_error_reports` / `control_error_histories` / `control_reports` / `section_robot_statuses` /
`clean_statuses` / `clean_schedules` / `clean_timetable_histories` / `robots`

（型・既定値などの差分はカラム集合が一致していても存在する。§4 を参照。）

---

## 4. 型・null許容・既定値の差分

### 4-1. 型の差分（14件）

| テーブル | カラム | 実DDL | 手書き prisma | 影響 |
|---|---|---|---|---|
| `customer_contacts` | `contact_type` | **`bigint unsigned`** | `Int @db.UnsignedTinyInt`（= tinyint unsigned） | **大**（Int↔BigInt。§7-6 参照） |
| `facility_contacts` | `email` | **`varchar(255)`** | `String @db.VarChar(100)` | 中（100超の値が読める／書けない不整合） |
| `robot_error_reports` | `serial_number` | **`varchar(20)`** | `String @db.VarChar(50)` | 中（21〜50文字を書くと切り詰め／エラー） |
| `robot_error_reports` | `product_code` | **`varchar(20)`** | `String? @db.VarChar(50)` | 中（同上） |
| `clean_statuses` | `task_area` | `decimal(12,6) unsigned` | `Decimal @db.Decimal(12,6)` | 小（P-3。Prisma で表現不可） |
| `clean_statuses` | `clean_area` | `decimal(12,6) unsigned` | `Decimal @db.Decimal(12,6)` | 小（P-3） |
| `clean_status_histories` | `task_area` | `decimal(12,6) unsigned` | `Decimal @db.Decimal(12,6)` | 小（P-3） |
| `clean_status_histories` | `clean_area` | `decimal(12,6) unsigned` | `Decimal @db.Decimal(12,6)` | 小（P-3） |
| `clean_task_reports` | `task_area` | `decimal(12,6) unsigned` | `Decimal @db.Decimal(12,6)` | 小（P-3） |
| `clean_task_reports` | `clean_area` | `decimal(12,6) unsigned` | `Decimal @db.Decimal(12,6)` | 小（P-3） |
| `customer_summaries` | `task_area` | `decimal(14,6) unsigned` | `Decimal @db.Decimal(14,6)` | 小（P-3） |
| `customer_summaries` | `clean_area` | `decimal(14,6) unsigned` | `Decimal @db.Decimal(14,6)` | 小（P-3） |
| `facility_summaries` | `task_area` | `decimal(14,6) unsigned` | `Decimal @db.Decimal(14,6)` | 小（P-3） |
| `facility_summaries` | `clean_area` | `decimal(14,6) unsigned` | `Decimal @db.Decimal(14,6)` | 小（P-3） |

**手書き版が実DDLと型・unsigned まで完全一致していた点（差分ゼロだった箇所）で特筆すべきもの:**

- `customers.prefecture_cd` は実DDL も `tinyint`（unsigned **なし**）、`facilities.prefecture_cd` は `tinyint unsigned`。
  手書き版のコメントにある「テーブル間で型が違う」という指摘は**実DDLでもそのまま**で、手書き版の記述が正しい。
- `robots.status` / `section_robot_statuses.robot_status` はいずれも実DDL も `smallint`（符号あり）。一致。
- `clean_statuses.clean_status` / `clean_status_histories.clean_status` はいずれも実DDL も `tinyint`（符号あり）。一致（§7-3）。
- `clean_task_reports.clean_time` は実DDL も `int unsigned`。一致。

### 4-2. NULL 許容の差分（1件）

| テーブル | カラム | 実DDL | 手書き prisma | 影響 |
|---|---|---|---|---|
| `robot_error_reports` | `errored_at` | **`datetime DEFAULT NULL`（NULL可）** | `DateTime @db.DateTime(0)`（必須） | **大**。DB には NULL が入り得るのに型は非 null。読み出し時に実行時エラーになる |

### 4-3. 既定値の差分（9件）

| テーブル | カラム | 実DDL | 手書き prisma | 影響 |
|---|---|---|---|---|
| `robots` | `battery` | **`DEFAULT '0'`** | `@default(100)` | **大**。満充電で作られる想定が実際は 0 |
| `clean_timetables` | `precheck` | **`DEFAULT '0'`** | `@default(9)` | **大**。9 は「本清掃」ではない（§7-7） |
| `robots` | `device_id` | **既定値なし** | `@default(0)` | 中。INSERT 時に明示必須 |
| `clean_timetables` | `parent_id` | **既定値なし** | `@default(0)` | 中。同上 |
| `clean_task_reports` | `created_at` | **既定値なし**（`timestamp NOT NULL`） | `@default(now())` | 中。アプリ側で必ず値を渡す必要がある |
| `clean_task_reports` | `updated_at` | **既定値なし**（`timestamp NOT NULL`、ON UPDATE も無し） | `@default(now()) @updatedAt` | 中。同上 |
| `security_status_histories` | `reported_at` | **既定値なし**（`timestamp NOT NULL`） | `@default(now())` | 中。同上 |
| `clean_status_histories` | `id` | **AUTO_INCREMENT なし** | `@default(autoincrement())` | **大**。採番されない（§4-4） |
| `clean_timetable_histories` | `id` | **AUTO_INCREMENT なし** | `@default(autoincrement())` | **大**。採番されない |

`updated_at` について: 実DDL では `clean_task_reports` と `section_robot_statuses` の `updated_at` だけ
`ON UPDATE CURRENT_TIMESTAMP` が**付いていない**（他の全テーブルは付いている）。
手書き版はどちらにも `@updatedAt` を付けているが、`@updatedAt` は Prisma 側で値を作るので実害は無い。ただし DB 直更新では自動更新されない点に注意。

### 4-4. 主キー / ユニークキーの差分（PK 2件 + UNIQUE 2件）

| テーブル | 実DDL | 手書き prisma |
|---|---|---|
| `clean_status_histories` | **PRIMARY KEY なし**。`UNIQUE KEY clean_status_histories_id_unique (id)` のみ。`id` は `bigint unsigned NOT NULL`（AUTO_INCREMENT なし） | `id BigInt @id @default(autoincrement())` |
| `robot_error_reports` | **PRIMARY KEY なし**。`UNIQUE KEY robot_error_reports_report_id_unique (error_id)` のみ。`id` は `varchar(50) NOT NULL` で制約なし | `id String @id @db.VarChar(50)`（`error_id` 側に `@unique` なし） |

この2件は**実装への影響が大きい**。
- `robot_error_reports` は Prisma が `id` を一意識別子として扱うが、DB 上の一意制約は `error_id` にしかない。
  `id` の重複を DB が止めないため、`findUnique({ where: { id } })` が「一意でないキーでの検索」になる。
  逆に `error_id` に `@unique` が無いので `findUnique({ where: { error_id } })` は書けない。
- 両テーブルの `id` は AUTO_INCREMENT でないため、採番はアプリ責任。

### 4-5. インデックスの差分（6件 / 4テーブル）

| テーブル | 実DDL にのみある KEY | 手書きにのみある `@@index` |
|---|---|---|
| `clean_timetable_histories` | `clean_schedule_id`, `start_at`, `end_at` | — |
| `control_reports` | `control_id` | — |
| `link_schedules` | `section_id` | — |
| `robot_task_versions` | — | `clean_schedule_id` |

`robot_task_versions.clean_schedule_id` は実DDL では**インデックスも外部キーも無い**（`robot_id` にしか KEY が無い）。
この列での絞り込みはフルスキャンになる。

---

## 5. 外部キーの差分

実DDL の業務テーブル FK は計 **48本**（KEY 67本 / UNIQUE 2本）。
**48本すべてが `ON DELETE` / `ON UPDATE` の指定なし**（= RESTRICT 相当）。
手書き版も `onDelete` を一切指定していないため、この点は一致。

| 区分 | 件数 | 内容 |
|---|---|---|
| 実DDL にあって手書きに無い FK | **0** | （なし） |
| 手書きにあって実DDL に無い FK | **3** | 下表 |

| テーブル | 手書きのリレーション | 実DDL の状態 |
|---|---|---|
| `clean_statuses` | `clean_timetable_id → clean_timetables(id)` | **FK なし**（KEY も無い） |
| `clean_status_histories` | `clean_timetable_id → clean_timetables(id)` | **FK なし**（KEY も無い） |
| `robot_task_versions` | `clean_schedule_id → clean_schedules(id)` | **FK なし**（KEY も無い） |

いずれも「参照整合性が DB では担保されていない」方向の差分。
Prisma のリレーションとしては動作するが、DB 側は孤児行を許す。

一方、手書き版が `clean_timetable_id` を**あえてリレーションにしなかった** `robot_error_reports` と
`clean_task_reports` は、実DDL でも FK なし・KEY なしで**判断が正しかった**。
`clean_timetables.parent_id` の自己参照を張らなかったのも実DDL と一致（FK なし、KEY はある）。

`controls.facility_id` は実DDL に FK あり。手書き版（TeamWorks B-2 回答を反映済み）と一致。

---

## 6. 一致していたテーブル

**カラム集合・型・unsigned・NULL許容・既定値・PK・UNIQUE・FK のすべてが一致した 11テーブル:**

`controls` / `customers` / `customer_error_summaries` / `devices` / `facilities` /
`facility_error_summaries` / `groups` / `link_schedules` / `robot_instruct_histories` /
`sections` / `security_statuses`

ただし `link_schedules` は `@@index([section_id])` が手書きに無い（§4-5）。
インデックスまで含めて完全一致なのは残り **10テーブル**。

`customer_summaries` / `facility_summaries` は decimal の unsigned（P-3、Prisma 表現不可）以外は一致。
`facility_contacts` は `email` のサイズ以外は一致。`customer_contacts` は `contact_type` の型以外は一致。

---

## 7. 定義書（転記）と 実DDL の食い違い ★この担当だけが出せる情報★

定義書転記の集計: **29テーブル / 317カラム**（`makers` を含む）。

### 7-1. `responsed_at` の実際のカラム名 → **`responded_at`**（正しい綴り）

| 定義書 | 実DDL | 該当テーブル |
|---|---|---|
| `responsed_at` | **`responded_at`** | `robot_error_reports`（`datetime DEFAULT NULL`） |
| `responsed_at` | **`responded_at`** | `control_error_histories`（`datetime DEFAULT NULL`） |

**定義書の綴りが誤記だった。** TeamWorks 確認項目 C-1 はこれで解決。
手書き版は「実DDL 入手まで定義書表記のまま」という方針で `responsed_at` としているので、**2箇所とも改名が必要**。

### 7-2. その他の綴り違い 3件の決着

| 定義書の綴り | 実DDL | 判定 |
|---|---|---|
| `control_reports.sireal_number` | **`serial_number`** | 定義書が誤記。定義書の索引欄が `fk_control_reports_serial_number` だった方が正しく、定義書内の矛盾は `serial_number` 側で決着 |
| `clean_timetables` の索引対象 `secction_id` | **`section_id`**（索引名も `clean_timetables_section_id_foreign`） | 定義書が誤記。手書き版が `section_id` と解釈したのは正しい |
| `section_robot_statuses.indicatived_at` | **該当列が存在しない** | 綴り違いではなく**列そのものが廃止**（`indicated_at` という列も無い）。§7-5 |

### 7-3. 定義書の「-1」問題（`clean_status`）が実DDL でどう解決されているか

定義書は `clean_status` を **`unsigned tinyint`** としながら、備考に `-1: 清掃完了(セキュリティ無効)` と書いており、
unsigned では格納できない値を要求する自己矛盾があった（`clean_statuses` と `clean_status_histories` の2箇所）。

**実DDL の答え: 両テーブルとも `clean_status tinyint NOT NULL DEFAULT '0'`（unsigned なし = 符号あり）。**
`-1` は格納可能。定義書の型表記が誤りで、備考の値定義が正しかった。

- TeamWorks 回答 A-2（`-1` があるため符号あり tinyint）は**実DDL で裏が取れた**。
- 手書き版は A-2 を反映して `Int @db.TinyInt` にしており、**実DDL と一致**。この箇所は修正不要。
- 併せて確認: 同テーブルの `clean_status_trans` / `clean_error` は実DDL でも `tinyint unsigned`（負値なし）。手書き版と一致。
- `robots.clean_status` / `section_robot_statuses.clean_status` は実DDL でも `tinyint unsigned` のままで、
  A-2 の `-1` 再定義は**この2テーブルには及んでいない**。手書き版も unsigned のままで一致。

### 7-4. 定義書に無い `clean_link_schedules` は何をするテーブルか

**事実（DDL から確定できること）:**

| カラム | 型 | NULL | 既定値 | 備考 |
|---|---|---|---|---|
| `id` | `bigint unsigned` | NOT NULL | — | AUTO_INCREMENT, PRIMARY KEY |
| `section_id` | `bigint unsigned` | NOT NULL | — | FK → `sections(id)`、KEY あり |
| `type` | `tinyint unsigned` | NOT NULL | `1` | |
| `weeks` | `varchar(7)` | NOT NULL | — | |
| `intervals` | `varchar(120)` | NOT NULL | — | |
| `inactive` | `tinyint unsigned` | NOT NULL | `0` | |
| `created_at` | `timestamp` | NOT NULL | `CURRENT_TIMESTAMP` | |
| `updated_at` | `timestamp` | NOT NULL | `CURRENT_TIMESTAMP` | ON UPDATE CURRENT_TIMESTAMP |
| `deleted_at` | `timestamp` | NULL | `NULL` | |

- 既存の `link_schedules`（定義書では「通信スケジュール」）と**構造が完全一致**。
  列名・列順・型・unsigned・NULL許容・既定値・AUTO_INCREMENT・ON UPDATE がすべて同一、PK も `id`、FK も `section_id → sections(id)` の1本のみ。機械的に照合済み。
- table_options に差がある: `link_schedules` は `AUTO_INCREMENT=10`、
  **`clean_link_schedules` は `AUTO_INCREMENT=` の記載自体が無い**。
  mysqldump は次回採番値を出すので、これは `clean_link_schedules` に**一度も行が挿入されていない**ことを示す。

**★以下は推測（DDL からは確定できない）★**

- 「清掃スケジュールに紐づく通信スケジュール」を `link_schedules` から分離する目的で作られた、
  もしくは `link_schedules` を `clean_link_schedules` へ**改名する移行の途中**である可能性が高い。
  構造が完全一致で中身が空という組み合わせは、改名・複製の作りかけに典型的なパターン。
- `clean_` 接頭辞は他の清掃系テーブル（`clean_schedules` / `clean_timetables` / `clean_statuses`）の命名に揃えたものと見える。
  ただし `clean_schedules` への FK は無く、`section_id` しか持たないので、清掃スケジュールとの直接の紐付けは DDL 上に存在しない。
- **どちらが有効なテーブルなのかは DDL からは判定不能。TeamWorks への確認事項とすべき。**
  現時点では `link_schedules` にのみデータがあるため、実装は `link_schedules` を使うのが安全。

### 7-5. 定義書にあって実DDL に無いもの（廃止されたもの）

**テーブル 1件:**

| 定義書 | 実DDL | 補足 |
|---|---|---|
| `makers`（メーカー） | **無い** | TeamWorks 回答 A-1「`makers` テーブルは提供されない」が実DDLで裏付けられた。`devices.maker_id` は実DDL でも `tinyint unsigned` で FK なし。手書き版の扱いと一致 |

**カラム 11件（うち3件は §7-1/7-2 の改名なので、実際の廃止は 8件）:**

| テーブル | 廃止されたカラム | 定義書での定義 |
|---|---|---|
| `robots` | `task_id` | `varchar(20)` — `robot_task_versions` 側にのみ残る |
| `robots` | `version` | `varchar(20)` — `robot_task_versions` 側にのみ残る |
| `clean_schedules` | `started_at` | `datetime`（定義書のキー欄は idx だが索引欄に対応行が無かった列） |
| `clean_schedules` | `ended_at` | `datetime`（同上） |
| `clean_statuses` | `deleted_at` | `timestamp` — 論理削除が廃止 |
| `section_robot_statuses` | `deleted_at` | `timestamp` — 論理削除が廃止 |
| `section_robot_statuses` | `indicatived_at` | `timestamp`（状態変更日時） |
| `clean_timetable_histories` | `inactive` | `unsigned tinyint` — 履歴に無効フラグは不要という判断か |

`clean_schedules.started_at` / `ended_at` の廃止は、定義書側の「キー欄は idx なのに索引欄に対応エントリが無い」という
不整合が**「列自体が無くなった」形で決着**したことを意味する。手書き版が `@@index` を書かずに保留したのは結果的に妥当だった。

同様に `clean_timetable_histories` の索引 `idx_..._started_at` / `idx_..._ended_at` は、
実DDL では `clean_timetable_histories_started_at_index (start_at)` /
`clean_timetable_histories_ended_at_index (end_at)` として**存在する**。
つまり索引名は `started_at` / `ended_at` のまま、対象カラムは `start_at` / `end_at`。
定義書の「索引名と実カラム名が一致しない」という指摘は**実DDL でもそのまま残っている**（索引名だけが古い）。

### 7-6. 定義書が正しく、TeamWorks 回答が実DB に反映されていない箇所 ★要注意★

| テーブル.カラム | 定義書 | TeamWorks 回答 (B-6) | **実DDL** | 手書き版 |
|---|---|---|---|---|
| `customer_contacts.contact_type` | `unsigned bigint` | 「typo。unsigned tinyint に修正」 | **`bigint unsigned`** | `Int @db.UnsignedTinyInt` |
| `facility_contacts.contact_type` | `unsigned tinyint` | 「unsigned tinyint」 | **`tinyint unsigned`** | `Int @db.UnsignedTinyInt` |
| `customer_contacts.email` | `varchar(100)` | — | **`varchar(100)`** | `@db.VarChar(100)` |
| `facility_contacts.email` | `varchar(255)` | 「typo。varchar(100) に修正」 | **`varchar(255)`** | `@db.VarChar(100)` |

**定義書の記載（両テーブルで型・サイズが不揃い）が実DB の実態と一致していた。**
TeamWorks が「typo なので揃える」と回答した2点は、**実DB には適用されていない**。
手書き版は回答を信じて揃えてしまったため、この2箇所が実DDL とずれている（§4-1）。

これは「定義書 vs 実DDL」ではなく「TeamWorks 回答 vs 実DDL」の食い違いであり、
**回答が将来の修正予定なのか、回答が誤りなのかを確認する必要がある。**

### 7-7. 定義書の `precheck` 逆転問題の決着

定義書では `clean_timetables.precheck` の既定値が **9**（備考: `0: 本清掃, 1: 1回目, 2: 2回目 ...`）、
`clean_timetable_histories.precheck` の既定値が **0**（備考: `0: 未設定, ... 9: 本清掃`）で、
初期値とコード値の対応がテーブル間で逆転していた。

**実DDL の答え: 両テーブルとも `precheck tinyint unsigned NOT NULL DEFAULT '0'`。**

TeamWorks 回答 B-1（`0: 本清掃 / 1: 1回目 / 2: 2回目`、`clean_timetables` の定義が正）と整合する。
定義書の `clean_timetables` 側の既定値 9 が誤り。
手書き版は「既定値 9 の意図は未確認」として `@default(9)` を維持しているので、**`@default(0)` へ修正が必要**。

### 7-8. 定義書の初期値と実DDL の DEFAULT の食い違い（6件）

（P-1 の「auto increment が備考欄」による偽陽性 22件を除外した実差分）

| テーブル.カラム | 定義書 | 実DDL |
|---|---|---|
| `robots.battery` | `100` | **`0`** |
| `clean_timetables.precheck` | `9` | **`0`**（§7-7） |
| `robots.device_id` | `0` | **既定値なし** |
| `clean_timetables.parent_id` | `0` | **既定値なし** |
| `clean_task_reports.created_at` | システム日付 | **既定値なし** |
| `clean_task_reports.updated_at` | システム日付 | **既定値なし** |
| `security_status_histories.reported_at` | システム日付 | **既定値なし** |

（表は7行。`clean_timetables.precheck` を §7-7 と重複計上しない場合は 6件。）

`robots.battery` の `100 → 0` は定義書と実DDL の実質的な食い違いで、
手書き版も定義書に従って `@default(100)` としているため要修正。

### 7-9. 定義書の型・サイズ・NULL許容と実DDL の食い違い（9件）

| テーブル.カラム | 定義書 | 実DDL | 備考 |
|---|---|---|---|
| `clean_statuses.clean_status` | `unsigned tinyint` | **`tinyint`** | §7-3。定義書の型が誤り |
| `clean_status_histories.clean_status` | `unsigned tinyint` | **`tinyint`** | §7-3。同上 |
| `clean_status_histories.task_area` | `decimal(12,6)` | **`decimal(12,6) unsigned`** | 定義書内で表記が不統一だった箇所（`clean_statuses` だけ `unsigned decimal`）。実DDL は3テーブルすべて unsigned |
| `clean_status_histories.clean_area` | `decimal(12,6)` | **`decimal(12,6) unsigned`** | 同上 |
| `clean_task_reports.task_area` | `decimal(12,6)` | **`decimal(12,6) unsigned`** | 同上 |
| `clean_task_reports.clean_area` | `decimal(12,6)` | **`decimal(12,6) unsigned`** | 同上 |
| `robot_error_reports.serial_number` | `varchar(50)` | **`varchar(20)`** | 定義書の方が大きい |
| `robot_error_reports.product_code` | `varchar(50)` | **`varchar(20)`** | 同上 |
| `robot_error_reports.errored_at` | 必須（○） | **`DEFAULT NULL`（NULL可）** | §4-2 |

定義書の decimal 表記の不統一（転記者が「矛盾点7」として挙げていた箇所）は、
**実DDL では6カラムすべて unsigned** で決着した。

### 7-10. 定義書の fk 指定と実DDL の外部キーの食い違い（4件）

| テーブル.カラム | 定義書のキー欄 | 実DDL |
|---|---|---|
| `clean_statuses.clean_timetable_id` | fk | **FK なし**（KEY も無し） |
| `clean_status_histories.clean_timetable_id` | fk | **FK なし**（KEY も無し） |
| `robot_task_versions.clean_schedule_id` | fk | **FK なし**（KEY も無し） |
| `controls.facility_id` | （空欄） | **FK あり** → `facilities(id)` |

`controls.facility_id` は定義書内でも矛盾していた（カラム表のキー欄は空欄、索引欄には `fk_controls_facility_id` あり）。
**実DDL は索引欄の側が正しく、FK は存在する。** TeamWorks 回答 B-2 と整合。

逆に `clean_timetable_id` / `clean_schedule_id` の3本は、定義書が fk と書いているのに実DB には無い。
手書き版は定義書に従ってリレーションを張っているため、実DDL とずれている（§5）。

### 7-11. 実DDL 側の自己矛盾（索引名と対象カラムの不一致、3件）

DDL 自身の中に残っている命名のズレ。実害は無いが、索引名で列を推測すると誤る。

| テーブル | 索引 | 名前から推測される列 | 実際の対象列 |
|---|---|---|---|
| `clean_timetable_histories` | `KEY clean_timetable_histories_started_at_index` | `started_at` | **`start_at`** |
| `clean_timetable_histories` | `KEY clean_timetable_histories_ended_at_index` | `ended_at` | **`end_at`** |
| `robot_error_reports` | `UNIQUE KEY robot_error_reports_report_id_unique` | `report_id` | **`error_id`** |

3件目は特に紛らわしい。`robot_error_reports` に `report_id` という列は無く（`report_id` は `clean_task_reports` の PK）、
一意制約が乗っているのは `error_id`。

### 7-12. 定義書 vs 実DDL の食い違い 件数まとめ

| 種類 | 件数 |
|---|---|
| テーブル（定義書のみ / 実DDLのみ） | 2（`makers` / `clean_link_schedules`） |
| カラム（定義書のみ） | 11（うち改名3・実質廃止8） |
| カラム（実DDLのみ） | 3（すべて §7-1/7-2 の改名先） |
| 型・unsigned | 6 |
| サイズ | 2 |
| NULL許容 | 1 |
| 既定値 | 6 |
| 外部キー | 4 |
| **合計** | **35** |

（別枠）TeamWorks 回答が実DB に反映されていない箇所: **2**（§7-6）
（別枠）実DDL 内部の索引名と対象列の不一致: **3**（§7-11）

---

## 付録: 差分の総件数（手書き prisma vs 実DDL）

| 種類 | 件数 |
|---|---|
| テーブル（実DDLのみ） | 1 |
| テーブル（手書きのみ） | 0 |
| カラム（実DDLのみ） | 3 |
| カラム（手書きのみ） | 11 |
| 型・サイズ・unsigned | 14 |
| NULL 許容 | 1 |
| 既定値 | 9 |
| 主キー | 2 |
| ユニークキー | 2 |
| 外部キー（手書きのみ） | 3 |
| 外部キー（実DDLのみ） | 0 |
| インデックス | 6 |
| **合計** | **52** |

### 実装に影響する差分（優先度順）

1. **`robot_error_reports.responsed_at` → `responded_at` に改名**（列が存在しないためクエリが即エラー）
2. **`control_error_histories.responsed_at` → `responded_at` に改名**（同上）
3. **`control_reports.sireal_number` → `serial_number` に改名**（同上）
4. **`robot_error_reports` の PK / UNIQUE**: DB の一意制約は `error_id` のみ。`id` に一意制約なし。`@id` の置き方とアプリ側の採番方針を決める必要がある
5. **`robot_error_reports.errored_at` を nullable に**（DB は NULL 可。非 null 型のままだと読み出しで実行時エラー）
6. **`clean_status_histories` / `clean_timetable_histories` の `id` から `@default(autoincrement())` を外す**（DB は AUTO_INCREMENT でない。採番はアプリ責任）
7. **`clean_status_histories` の PK**: DB は PRIMARY KEY を持たず `UNIQUE(id)` のみ
8. **`customer_contacts.contact_type` は `bigint unsigned`**（`Int` → `BigInt` に変更。TeamWorks B-6 回答と実DB が食い違っており要確認）
9. **`robots.battery` の既定値 100 → 0**
10. **`clean_timetables.precheck` の既定値 9 → 0**
11. **`facility_contacts.email` は `varchar(255)`**（B-6 回答と実DB が食い違っており要確認）
12. **`robot_error_reports.serial_number` / `product_code` は `varchar(20)`**（50 のまま書くと切り詰め・エラー）
13. **廃止列 8件の削除**: `robots.task_id` / `robots.version` / `clean_schedules.started_at` / `clean_schedules.ended_at` / `clean_statuses.deleted_at` / `section_robot_statuses.indicatived_at` / `section_robot_statuses.deleted_at` / `clean_timetable_histories.inactive`
14. **FK 3本は DB に無い**: `clean_statuses.clean_timetable_id` / `clean_status_histories.clean_timetable_id` / `robot_task_versions.clean_schedule_id`。参照整合性が担保されていないので孤児行を前提にしたコードが必要
15. **`clean_task_reports.created_at` / `updated_at`、`security_status_histories.reported_at` に DB 既定値が無い**（INSERT で必ず値を渡す）
16. **`clean_link_schedules` の扱いを TeamWorks に確認**（`link_schedules` と構造完全一致・データ0件。改名途中の可能性）
17. `robot_task_versions.clean_schedule_id` はインデックスすら無い（この列での絞り込みはフルスキャン）

### 使用スクリプト（当方の成果物、`scratchpad\ddl\mine\`）

- `parse_ddl.py` — DDL を括弧対応でパースし `ddl_parsed.json` を生成
- `cmp2.py` — インデックス差分 / DDL内部の索引名不一致 / 定義書 vs DDL のテーブル・カラム・型差分
- `cmp3.py` — 定義書 vs DDL の初期値差分 / fk 指定差分
- `cmp4.py` — `clean_link_schedules` と `link_schedules` の構造一致検証
