# 機能設計書 92-ドキュメント管理

## 概要

本ドキュメントは、Chatwoot の Captain AI 機能における学習用ドキュメント管理機能の設計を記載する。

### 本機能の処理概要

ドキュメント管理機能は、AIアシスタント（Captain）が顧客対応時に参照するナレッジベースとしてのドキュメントを登録・管理する機能である。Webページのクロールまたは PDF ファイルのアップロードにより、AIが学習するコンテンツを蓄積する。

**業務上の目的・背景**：AIアシスタントが正確で適切な回答を行うためには、企業固有の製品情報、FAQ、マニュアルなどの知識が必要である。本機能により、外部ドキュメントやPDFをAIの学習データとして登録し、AIの回答精度を向上させることができる。

**機能の利用シーン**：
- 製品マニュアルのPDFをアップロードしてAIに学習させる
- ヘルプセンターのURLを登録し、Webページの内容をクロールして学習データとする
- FAQページを登録してAIが顧客からの質問に正確に回答できるようにする

**主要な処理内容**：
1. ドキュメント一覧の取得（アシスタント別フィルタ可能）
2. ドキュメントの詳細表示
3. 新規ドキュメントの作成（外部リンクまたはPDFアップロード）
4. ドキュメントの削除
5. 登録後の自動クロール処理（非同期）
6. コンテンツからのFAQ自動生成（非同期）

**関連システム・外部連携**：
- Webページクロール（Firecrawl または SimplePageCrawl）
- OpenAI API（FAQレスポンス自動生成）
- ActiveStorage（PDFファイル管理）

**権限による制御**：Captain::Assistant ポリシーに基づく認可。アシスタント管理権限を持つユーザーが操作可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 106 | ドキュメント一覧画面 | 主画面 | ドキュメントの一覧表示・追加・削除 |

## 機能種別

CRUD操作 / ファイルアップロード / 非同期処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| assistant_id | Integer | Yes | 紐付けるアシスタントのID | 必須 |
| name | String | No | ドキュメント名 | - |
| external_link | String | 条件付き | 外部リンクURL | PDFファイルなしの場合は必須、アシスタント内で一意 |
| pdf_file | File | 条件付き | PDFファイル | external_linkなしの場合は必須、10MB以下、PDF形式 |

### 入力データソース

- 画面入力（API経由）
- ファイルアップロード（ActiveStorage）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | ドキュメントID |
| account_id | Integer | アカウントID |
| assistant_id | Integer | アシスタントID |
| assistant | Object | アシスタント情報（ネスト） |
| name | String | ドキュメント名 |
| external_link | String | 外部リンク |
| display_url | String | 表示用URL |
| content | Text | クロールしたコンテンツ |
| content_type | String | コンテンツタイプ（PDF時） |
| file_size | Integer | ファイルサイズ（PDF時） |
| status | String | 状態（in_progress/available） |
| created_at | Integer | 作成日時（UNIX時間） |
| updated_at | Integer | 更新日時（UNIX時間） |

### 出力先

API レスポンス（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ APIコントローラーがリクエストを受信
2. 認証・認可チェック
   └─ Captain::Assistantポリシーによる権限確認
3. 各アクション実行
   ├─ index: ドキュメント一覧取得（ページネーション対応）
   ├─ show: 指定IDのドキュメント取得
   ├─ create: 新規ドキュメント作成
   │      ├─ プラン制限チェック
   │      ├─ バリデーション
   │      ├─ 保存
   │      └─ 非同期ジョブ登録（クロール・FAQ生成）
   └─ destroy: ドキュメント削除
4. レスポンス返却
   └─ jbuilderによるJSON整形
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{認可チェック}
    B -->|失敗| C[403エラー]
    B -->|成功| D{アクション判定}
    D -->|index| E[ドキュメント一覧取得]
    D -->|create| F{アシスタント存在確認}
    D -->|destroy| G[ドキュメント削除]
    F -->|不在| H[422エラー: Missing Assistant]
    F -->|存在| I{プラン制限チェック}
    I -->|超過| J[422エラー: Limit Exceeded]
    I -->|OK| K[バリデーション]
    K -->|失敗| L[422エラー]
    K -->|成功| M[ドキュメント保存]
    M --> N[クロールジョブ登録]
    N --> O[レスポンス返却]
    E --> O
    G --> P[利用量更新]
    P --> Q[204 No Content]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-92-01 | プラン制限 | ドキュメント数はプランの上限以内 | 新規作成時 |
| BR-92-02 | 外部リンク一意性 | 同一アシスタント内でexternal_linkは一意 | 新規作成時 |
| BR-92-03 | PDFサイズ制限 | PDFファイルは10MB以下 | PDFアップロード時 |
| BR-92-04 | コンテンツ長制限 | contentは200,000文字以下 | クロール完了時 |
| BR-92-05 | 自動クロール | 作成後に自動でクロールジョブを実行 | status=in_progress時 |
| BR-92-06 | 自動FAQ生成 | クロール完了後にFAQ生成ジョブを実行 | status=available変更時 |

### 計算ロジック

PDFアップロード時のexternal_link生成：
```
"PDF: {ファイル名（拡張子なし）}_{タイムスタンプ(YYYYMMDDHHmmss)}"
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| index | captain_documents | SELECT | ドキュメント一覧取得 |
| show | captain_documents | SELECT | 指定IDのドキュメント取得 |
| create | captain_documents | INSERT | 新規ドキュメント作成 |
| destroy | captain_documents | DELETE | ドキュメント削除 |
| create/destroy | accounts | UPDATE | ドキュメント利用量更新 |

### テーブル別操作詳細

#### captain_documents

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT (index) | * | account_id = current_account.id | ページネーション25件/ページ |
| SELECT (index) | * | assistant_id = params[:assistant_id] | アシスタントフィルタ（オプション） |
| SELECT (show) | * | id = params[:id] | - |
| INSERT | name, external_link, assistant_id, account_id, status | 入力値, account_id=assistant.account_id, status=in_progress | - |
| DELETE | - | id = params[:id] | 物理削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | Forbidden | 権限不足 | 適切な権限を付与 |
| 404 | Not Found | 指定IDのドキュメントが存在しない | 正しいIDを指定 |
| 422 | Unprocessable Entity | アシスタントが存在しない | 正しいassistant_idを指定 |
| 422 | Unprocessable Entity | プラン制限超過 | プランをアップグレード |
| 422 | Unprocessable Entity | PDFサイズ超過（10MB超） | 小さいファイルを使用 |
| 422 | Unprocessable Entity | PDF形式エラー | 正しいPDF形式のファイルを使用 |

### リトライ仕様

クロールジョブは非同期で実行され、失敗した場合はSidekiqのリトライ機構により自動リトライされる。

## トランザクション仕様

- create: ドキュメント保存とActiveStorageへのファイルアタッチがトランザクション内で実行
- destroy: ドキュメント削除後に利用量更新がafter_destroyコールバックで実行

## パフォーマンス要件

- 一覧取得は25件/ページでページネーション
- account_id, assistant_id, statusにインデックス設定済み

## セキュリティ考慮事項

- Captain::Assistant ポリシーによるアクセス制御
- PDFファイルはActiveStorageで管理、直接アクセス不可
- 外部リンクのクロール時はSSRF対策が必要

## 備考

Enterprise Edition機能として提供される。

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | document.rb | `enterprise/app/models/captain/document.rb` | ドキュメントのスキーマ定義、バリデーション、コールバック |

**読解のコツ**:
- **23-30行目**: 関連定義（belongs_to :assistant, has_one_attached :pdf_file）
- **32-37行目**: バリデーション（external_link必須条件、uniqueness、content長制限、PDF検証）
- **42-45行目**: statusのenum定義（in_progress, available）
- **47-51行目**: コールバック定義（ensure_within_plan_limit, enqueue_crawl_job, update_document_usage）
- **91-95行目**: クロールジョブ登録ロジック
- **97-110行目**: FAQ生成ジョブ登録条件

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | documents_controller.rb | `enterprise/app/controllers/api/v1/accounts/captain/documents_controller.rb` | APIエンドポイントの実装 |

**主要処理フロー**:
1. **3行目**: before_action で Captain::Assistant ポリシーによる認可チェック
2. **11-17行目**: index アクション - フィルタ・ページネーション対応
3. **21-30行目**: create アクション - アシスタント存在確認、LimitExceededError処理
4. **32-35行目**: destroy アクション

#### Step 3: 非同期処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | crawl_job.rb | `enterprise/app/jobs/captain/documents/crawl_job.rb` | Webページクロール処理 |
| 3-2 | response_builder_job.rb | `enterprise/app/jobs/captain/documents/response_builder_job.rb` | FAQ自動生成処理 |

### プログラム呼び出し階層図

```
Api::V1::Accounts::Captain::DocumentsController
    │
    ├─ before_action -> { check_authorization(Captain::Assistant) }
    │
    ├─ index
    │      └─ Current.account.captain_documents.includes(:assistant).ordered
    │             └─ apply filters (assistant_id)
    │             └─ page(@current_page).per(25)
    │
    ├─ create
    │      ├─ @assistant = Current.account.captain_assistants.find_by(id:)
    │      └─ @assistant.documents.build(document_params)
    │             │
    │             ├─ before_validation :ensure_account_id
    │             ├─ before_create :ensure_within_plan_limit
    │             │      └─ LimitExceededError if limit reached
    │             │
    │             ├─ after_create_commit :enqueue_crawl_job
    │             │      └─ Captain::Documents::CrawlJob.perform_later(self)
    │             │
    │             ├─ after_create_commit :update_document_usage
    │             │      └─ account.update_document_usage
    │             │
    │             └─ after_commit :enqueue_response_builder_job
    │                    └─ Captain::Documents::ResponseBuilderJob.perform_later(self)
    │
    └─ destroy
           └─ @document.destroy
                  └─ after_destroy :update_document_usage
```

### データフロー図

```
[入力]                           [処理]                              [出力]

外部リンクURL ───────────┐
                        ├──▶ DocumentsController#create
PDFファイル ────────────┘               │
                                       ▼
                              Captain::Document.save
                                       │
                      ┌────────────────┼────────────────┐
                      ▼                ▼                ▼
               captain_documents  active_storage   account.usage
                  (INSERT)         (ATTACH)         (UPDATE)
                      │
                      ▼
            status: in_progress
                      │
                      ▼ (非同期)
            CrawlJob.perform_later
                      │
                      ▼
            content取得・保存
                      │
                      ▼
            status: available
                      │
                      ▼ (非同期)
         ResponseBuilderJob.perform_later
                      │
                      ▼
         captain_assistant_responses (INSERT)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| documents_controller.rb | `enterprise/app/controllers/api/v1/accounts/captain/documents_controller.rb` | コントローラー | APIエンドポイント |
| document.rb | `enterprise/app/models/captain/document.rb` | モデル | データ定義・バリデーション・コールバック |
| _document.json.jbuilder | `enterprise/app/views/api/v1/models/captain/_document.json.jbuilder` | ビュー | JSON出力 |
| index.json.jbuilder | `enterprise/app/views/api/v1/accounts/captain/documents/index.json.jbuilder` | ビュー | 一覧JSON出力 |
| crawl_job.rb | `enterprise/app/jobs/captain/documents/crawl_job.rb` | ジョブ | Webクロール非同期処理 |
| response_builder_job.rb | `enterprise/app/jobs/captain/documents/response_builder_job.rb` | ジョブ | FAQ生成非同期処理 |
| firecrawl_service.rb | `enterprise/app/services/captain/tools/firecrawl_service.rb` | サービス | Firecrawlクロール |
| simple_page_crawl_service.rb | `enterprise/app/services/captain/tools/simple_page_crawl_service.rb` | サービス | シンプルクロール |
