# ファイル・フォルダ構成と役割

実装を進めるときの地図。**2026-09-01 時点の実態**を記録し、未着手のものは「予定」として区別している。

構成そのものの規約は `CLAUDE.md` §9.1 が正。この文書は**各ディレクトリが何を担うか**と
**依存の向き**を補足するもの。両者が食い違ったら `CLAUDE.md` に従い、この文書を直す。

---

## 1. 依存の向き（最重要）

```
app/  (画面・APIルート)
  │  呼ぶ
  ▼
lib/db  lib/logger  lib/settings        ← 非純粋層（I/O・副作用あり）
  │  呼ぶ
  ▼
lib/judge  lib/config  lib/format       ← 純粋層（副作用なし・テストで固定）
```

**下の層が上の層を import してはいけない。** 特に:

- `lib/judge/` は `lib/logger/` を import しない（`CLAUDE.md` §9.4 の純粋関数規約）
- 判定結果のログは、判定関数の中ではなく**呼び出し側（APIルート）**で記録する
- `lib/judge/` は `USE_FIXTURES` の切替の**外側**。fixtures でも本番でも同じロジックが走る

---

## 2. ルート直下

| パス | 役割 | 状態 |
|---|---|---|
| `CLAUDE.md` | **全ルールの正。** 仕様・禁止事項・実装規約。Claude Code と Cursor の両方が自動で読む | 運用中 |
| `.cursorrules` | Cursor の legacy 形式。中身は `CLAUDE.md` へ統合済みのポインタ | 縮小済み |
| `d-watch-api.yaml` | **API契約の正。** 変更は実装より先に行う（`CLAUDE.md` §9） | 一部に旧記述が残存※ |
| `d-watch-mock.jsx` | **画面レイアウト・文言・遷移の正。** フロント実装はこれを移植する | 参照専用 |
| `instrumentation.ts` | 起動ログ（`app.start`）と捕捉漏れエラー（`app.uncaught`）の記録（D-047） | 実装済み |
| `proxy.ts` | 相関IDの発行＋認証＋IP制限。**1プロジェクトに1ファイルしか置けない**ので、中身は `lib/proxy/` へ分割し本体は順序制御だけにしてある | **実装済み**（指示書I / D-101） |
| `next.config.ts` | `serverExternalPackages: ["pino"]` を設定 | 実装済み |
| `eslint.config.mjs` | `no-console` を全体に適用。`lib/logger/**` のみ除外 | 実装済み |
| `vitest.config.ts` | テスト設定。`lib/**` と `app/**` の `*.test.ts` を対象 | 運用中 |
| `.claude/launch.json` | 開発サーバーの起動設定。ブラウザ確認込みで `npm run dev` を回すために使う | 運用中 |
| `prisma/schema.prisma` | `db pull` 実施後は生成物を正とし**手で書き換えない**（D-056、`CLAUDE.md` §7）。実施前は定義書ベースで手書き | TeamWorks 領域 |

※ `d-watch-api.yaml` の `info.description` は却下済みの Laravel 構成（D-001）の記述を
現行構成（Next.js の API ルート、D-046）に書き直し済み。`servers.url` も `/api/v1` という
実態と異なる記載になっていたため `/api`（`app/api/clean-logs` 等の実ルートと一致）に修正済み
（D-050）。`securitySchemes` も **cookie 方式（`type: apiKey` / `in: cookie` / `name: dwatch_session`）へ
書き換え済み**（指示書I 段階0 / D-101）。`bearerAuth` と Laravel Sanctum への言及は残っていない。
401（未認証）/ 403（IP拒否）を `components/responses` に定義し、`/auth/login` は `security: []` で
認証不要にしてある。

---

## 3. `app/` — 画面とAPI

### 画面

| パス | 役割 | 状態 |
|---|---|---|
| `app/layout.tsx` | 全体のルートレイアウト | 実装済み |
| `app/(dashboard)/layout.tsx` | サイドバー付きの共通シェル | 実装済み |
| `app/(dashboard)/detail-log/` | 清掃詳細ログ（F-02） | **実装済み。他画面の見本** |
| `app/(dashboard)/dashboard/` | ダッシュボード（F-01） | **実装済み。** `components/features/dashboard/DashboardScreen` を描画する |
| `app/(dashboard)/alerts/` | アラート（F-03） | **実装済み** |
| `app/(dashboard)/reports/` | レポート（F-04） | **実装済み**（指示書F） |
| `app/(dashboard)/inspections/` | 点検記録（F-05） | プレースホルダ。★デモまで一時的にメニューから隠している（D-079 項3）。D-031 のP1スコープは変えていない★ |
| `app/(dashboard)/ds-logs/` `ds-alerts/` | D-Security 系（F-06 / F-07） | **実装済み** |
| `app/(dashboard)/facilities/` `schedules/` `system/` | 施設・スケジュール・運用設定 | **実装済み**（指示書L。`system`は運用設定タブのみ・読み取り専用） |
| `app/login/` | 共有トークンの入力画面 | **実装済み**（指示書I / D-101）。モックには無い画面（認証がモックの対象外だったため） |

**画面は `(dashboard)` ルートグループの中に置く。** ログイン画面だけはシェルを出さないため外に置く。

### APIルート

| パス | 役割 | 状態 |
|---|---|---|
| `app/api/clean-logs/route.ts` | 一覧取得。**「動く見本」。他の一覧APIはこれを真似る** | 実装済み |
| `app/api/clean-logs/export/route.ts` | CSV等の書き出し | 実装済み |
| `app/api/clean-logs/[id]/advice/route.ts` | アドバイス取得 | 実装済み |
| `app/api/clean-logs/unscheduled/[report_id]/advice/route.ts` | 予定なしの行のアドバイス取得 | 実装済み |
| `app/api/clean-logs/[id]/areas/route.ts` | 清掃エリア内訳 | 実装済み |
| `app/api/dashboard/route.ts` | ダッシュボードの集計取得（`failure_breakdown` は D-048 により未返却） | 実装済み |
| `app/api/settings/advice/route.ts` | アドバイス規則の設定 | 実装済み |
| `app/api/auth/login` `logout` | cookie の発行・削除 | **実装済み**（指示書I / D-101）。`login` のみ認証不要 |
| `app/api/prechecks/[id]/status` 他 | 状態更新（PATCH系。M3） | **予定** |

**全APIルートの共通形**:

```ts
export async function GET(request: Request) {
  return withRequestId(request, async () => {
    // ... 本体
    // catch 節では logApiError() を呼ぶ（console.error は禁止）
  });
}
```

`.cursorrules` にあった `app/api/v1/` という記載は**実態と異なる**（`v1` は無い）。
ログの `route` フィールドにも実際のパスを入れること。

---

## 4. `lib/` — ロジック層

### 純粋層（副作用なし。テストで固定する）

| パス | 役割 | 注意 |
|---|---|---|
| `lib/judge/` | **判定ロジック。** ◎○△×（`grade.ts`）、L1〜L3（`escalation.ts`）、アドバイス規則（`advice.ts`）、KPI計算（`kpi.ts`） | **変更時は期待値表のテストを先に書く。** ロガーを import しない。DBアクセスを混ぜない |
| `lib/dashboard/` | **ダッシュボード（F-01）の集計。** `aggregate.ts`。定義の正は D-049 | **`lib/judge/kpi.ts` とは別物。** kpi.ts は週次/月次の KPI 集計（完了率は1回ごとの完了率の単純平均。D-155）、こちらは日次の件数カウント。混同すると分母の意味が変わる。`failure_breakdown` は D-048 により未対応（M3送り） |
| `lib/config/` | **閾値の外出し。** 完了率90/70/10、猶予30分、L2=2日/L3=3日、アドバイス規則の既定値 | **ハードコード禁止。** 閾値は必ずここから読む |
| `lib/format/` | 表示整形（日付など）。丸めは**表示層のみ** | ロジック層には生値を渡す |
| `lib/types/` | `d-watch-api.yaml` から起こした型。`ErrorCode` の union もここ | YAML と乖離させない |
| `lib/i18n.ts` | JP/EN 切替 | 器を壊さない |
| `lib/theme.ts` `theme-status.ts` | ダーク/ライト切替 | 器を壊さない |

### 非純粋層（I/O・副作用あり）

| パス | 役割 | 注意 |
|---|---|---|
| `lib/db/` | **`USE_FIXTURES` による切替の唯一の場所。** `USE_FIXTURES` を export しており、他所で `process.env` を再解釈しない | 画面側は切替を意識しない |
| `lib/fixtures/` | サンプルデータ。境界値・異常系を含む | **DB接続後も削除しない**（テスト・デモ用）。実在の店舗名・住所・電話番号を入れない。`__tests__/schedules.test.ts` が「有効スケジュールの曜日該当件数＝当日の清掃ログ行数」という不変条件を守っている。**この不変条件を壊すとダッシュボードの既定表示が異常系になる** |
| `lib/logger/` | 動作ログ（D-047）。詳細は `docs/logging-design.md` | `lib/judge/` から import してはいけない |
| `lib/settings/` | 設定の永続化（アドバイス規則の上書き） | メインDBには書かない |
| `lib/clean-logs/` | 清掃詳細ログのクエリ解析とフィルタ | 画面固有のロジック |
| `lib/auth/` | 認証の純粋関数（`token.ts` 定数時間比較 / `client-ip.ts` XFF解決とIP許可判定 / `cookie.ts` cookie属性 / `next-path.ts` 戻り先の検証） | **実装済み**（指示書I / D-101）。DBもファイルも触らない |
| `lib/proxy/` | `proxy.ts` から呼ぶ横断処理（`request-id.ts` 相関ID発行 / `authenticate.ts` IP制限と認証の判定） | **実装済み**（指示書I / D-101）。判定だけを返し Response は組み立てない |
| `lib/api/client.ts` | **フロントからの `fetch` 集約点。** 実装済み | `apiFetch()` と `ApiFetchError` を提供し、F-01（`DashboardScreen`）・F-02（`DetailLogScreen`）の両方が利用している |
| `lib/inspections/` | 点検記録の独自ストア（SQLite + オブジェクトストレージ） | **予定。** メインDBと完全分離 |

### `lib/logger/` の内訳

| ファイル | 役割 |
|---|---|
| `index.ts` | pino インスタンス。`mixin()` で `request_id` / `data_source` を自動付与。`logEvent()` |
| `events.ts` | **イベント型（allowlist）。** 許可フィールドのみで構成し、禁止キーは型で弾く |
| `redact.ts` | 実行時の禁止キー検査。個人情報13キー＋認証情報8キー。大文字小文字を無視 |
| `context.ts` | `AsyncLocalStorage`。`withRequestId()` / `normalizeRequestId()` |
| `timestamp.ts` | ISO 8601 JST（ミリ秒付き）。pino 既定の epoch ms を置き換える |
| `api.ts` | `logApiError()`。**クライアントに返す `ApiError` をそのまま受け取る**ことで、レスポンスとログのズレを構造的に防ぐ |

---

## 5. `components/`

| パス | 役割 |
|---|---|
| `components/ui/` | **共通部品。** `Pill` / `GradeBadge` / `FilterButton` / `StatCard` / `ProgressBar`（`ProgressBar` はF-04のKPI4列でも再利用予定） |
| `components/features/{画面名}/` | **画面固有のコンポーネント。** 現在は `detail-log/` と `dashboard/` の2つ |
| `components/layout/` | `AppShell`（サイドバー）、`PlaceholderScreen`（未実装画面の仮表示） |
| `components/theme/` | `ThemeProvider`（ダーク/ライト） |

**判定結果（◎○△×、L1〜L3、ソート順）はAPIから受け取る。フロントで再計算しない。**

---

## 6. `docs/`

| ファイル | 何の正か |
|---|---|
| `spec.md` | **機能一覧と開発フェーズの正。** 何をどの順で作るか |
| `decisions.md` | **決定事項とその理由の正。** 「なぜこの仕様か」を遡る時に読む |
| `fixtures.md` | **サンプルデータの正。** 用意すべき境界値・異常系の一覧 |
| `logging-design.md` | **ログ機構の実装の正。** マスキング規約・イベント定義・実装ステップ |
| `architecture.md` | この文書。構成と役割の地図 |
| `getting-started.md` | 環境構築手順 |

---

## 7. 触ってはいけないもの

| 対象 | 理由 |
|---|---|
| `lib/judge/**` とそのテスト | 純粋関数を維持する。判定を変えるときは期待値表を先に直す |
| `lib/dashboard/aggregate.ts` とそのテスト | `lib/judge/**` と同じ規律（D-041）で期待値表テストを先に直す。`schedule_progress` の定義は D-049 が正 |
| `lib/fixtures/schedules.ts` の曜日設計 | 866fe4f で確定した曜日パターン（補完ペア＋毎日）。`__tests__/schedules.test.ts` がこの不変条件を守っている。崩すと `/dashboard` の既定表示（当日・date省略）が異常系になる |
| `prisma/schema.prisma` | `db pull` 実施後は手で書き換えない（D-056）。TeamWorks の領域 |
| `d-watch-api.yaml` | API契約の正。変更は実装より**先に**、合意の上で |
| `CLAUDE.md` の `<!-- BEGIN:nextjs-agent-rules -->` ブロック | `next dev` が自動生成する |
| メインDBへの書き込み | `robot_error_reports` / `control_error_histories` の4カラムのみ |

---

## 8. 未確定（決まり次第この文書を更新する）

| 項目 | 影響範囲 |
|---|---|
| ★アプリのポートが nginx からのみ到達可能か★ | ネットワーク側の設定。★偽装 XFF を本当に塞ぐのはここ★（アプリでは防げない）。D-101 の申し送り |
| VM のプロセス管理方式 | ログの出力先とローテーション（アプリ側の実装には影響しない） |
| グループ・会社マスタの階層構造 | 施設・会社・グループの関係が未定。`group_id`/`customer_id` は現在API側で受け取るが無視している |
| `PATCH /prechecks/{id}/status` の `memo` と `PrecheckItem.note` | 同一物か別物か契約上判断できない。PATCH実装時に解消する |
