# 機能設計書 21-自動割り当て設定

## 概要

本ドキュメントは、Chatwootにおけるインボックスの自動割り当て設定機能について記述する。この機能は、新規会話や再オープンされた会話をエージェントに自動的に割り当てるための設定を管理する。

### 本機能の処理概要

**業務上の目的・背景**：カスタマーサポート業務において、顧客からの問い合わせを迅速かつ公平にエージェントへ割り当てることは、対応品質とエージェントの負荷バランスに直結する。手動での割り当ては管理者の負担が大きく、対応遅延や不公平な負荷分散を招く恐れがある。自動割り当て機能により、新規会話を即座に適切なエージェントへ振り分け、業務効率とサービス品質の向上を実現する。

**機能の利用シーン**：インボックス設定画面において、管理者が自動割り当ての有効/無効を切り替える場面、または割り当てポリシー（ラウンドロビン、優先度等）を設定する場面で利用される。会話が作成された際や、ステータスがオープンに変更された際に、バックグラウンドで自動割り当て処理が実行される。

**主要な処理内容**：
1. インボックスの`enable_auto_assignment`フラグによる自動割り当ての有効/無効切り替え
2. `auto_assignment_config`JSONフィールドによる詳細設定の保持（優先度順など）
3. レガシーシステム（オンラインエージェントへのラウンドロビン）と新システム（assignment_v2）の両方をサポート
4. ラウンドロビン方式によるエージェント選択（Redisキューを使用）
5. レート制限機能による公平な分配（時間枠内の割り当て上限）

**関連システム・外部連携**：Redisを使用したラウンドロビンキュー管理、Sidekiqによる非同期ジョブ実行、割り当てポリシー（AssignmentPolicy）との連携。

**権限による制御**：インボックスの設定変更は管理者（Administrator）権限を持つユーザーのみ実行可能。エージェント権限ではインボックス設定の閲覧のみ可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 50 | インボックス設定画面 | 主画面 | 自動割り当ての有効/無効設定 |

## 機能種別

CRUD操作 / 設定管理 / バックグラウンド処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| enable_auto_assignment | Boolean | No | 自動割り当ての有効/無効 | true/falseのみ |
| auto_assignment_config | JSON | No | 自動割り当ての詳細設定 | 有効なJSON形式 |
| auto_assignment_config.conversation_priority | String | No | 会話の優先度順 | "earliest_created" または "longest_waiting" |

### 入力データソース

- 画面入力: インボックス設定画面から管理者が設定
- DBテーブル: inboxesテーブルのenable_auto_assignment、auto_assignment_configカラム

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| enable_auto_assignment | Boolean | 自動割り当ての有効/無効状態 |
| auto_assignment_config | JSON | 自動割り当ての詳細設定 |

### 出力先

- 画面表示: インボックス設定画面での現在の設定表示
- DBテーブル: inboxesテーブルへの設定保存
- Redisキュー: ラウンドロビン用エージェントキュー

## 処理フロー

### 処理シーケンス

```
1. インボックス設定の更新（API: PATCH /api/v1/accounts/:account_id/inboxes/:id）
   └─ enable_auto_assignment、auto_assignment_configパラメータを受け取り保存

2. 会話作成/ステータス変更時の自動割り当てトリガー
   └─ AutoAssignmentHandler concernが発火

3. 割り当て処理の実行
   └─ assignment_v2有効時: AutoAssignment::AssignmentJob を非同期実行
   └─ レガシー: AutoAssignment::AgentAssignmentService を同期実行

4. エージェント選択
   └─ オンライン状態のエージェントを取得
   └─ ラウンドロビン方式で次のエージェントを選択
   └─ レート制限をチェック（fair_distribution_limit）

5. 会話への割り当て
   └─ conversation.update!(assignee: agent)
   └─ ASSIGNEE_CHANGEDイベントをディスパッチ
```

### フローチャート

```mermaid
flowchart TD
    A[会話作成/再オープン] --> B{enable_auto_assignment?}
    B -->|No| Z[終了]
    B -->|Yes| C{担当者未割り当て?}
    C -->|No| Z
    C -->|Yes| D{assignment_v2有効?}
    D -->|Yes| E[AssignmentJob実行]
    D -->|No| F[AgentAssignmentService実行]
    E --> G[利用可能エージェント取得]
    F --> G
    G --> H{エージェント存在?}
    H -->|No| Z
    H -->|Yes| I[レート制限チェック]
    I --> J{制限内?}
    J -->|No| K[次のエージェントを試行]
    K --> G
    J -->|Yes| L[ラウンドロビン選択]
    L --> M[会話に割り当て]
    M --> N[イベント発火]
    N --> Z
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-21-01 | 自動割り当て条件 | 会話ステータスがオープンで、担当者が未割り当ての場合のみ実行 | 会話作成時、ステータス変更時 |
| BR-21-02 | オンラインエージェント優先 | レガシーモードではオンライン状態のエージェントのみを割り当て対象とする | assignment_v2無効時 |
| BR-21-03 | ラウンドロビン順序 | エージェントは順番に割り当てられ、最後に割り当てられたエージェントはキューの最後尾に移動 | 常時 |
| BR-21-04 | レート制限 | 公平分配のため、時間枠内の割り当て上限を超えたエージェントはスキップ | assignment_v2有効時 |

### 計算ロジック

**ラウンドロビンキュー管理**:
- Redisリストを使用（キー: `round_robin_agents:inbox:{inbox_id}`）
- エージェント追加: LPUSH
- エージェント選択: キューと許可リストの交差から取得後、LREM + LPUSH で末尾に移動

**レート制限計算**:
- 現在の割り当て数 = Redisキーパターンマッチング件数
- 制限内判定: current_count < fair_distribution_limit

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 自動割り当て設定更新 | inboxes | UPDATE | enable_auto_assignment、auto_assignment_configを更新 |
| 会話割り当て | conversations | UPDATE | assignee_idを更新 |
| イベント記録 | conversation_activities | INSERT | 割り当て変更のアクティビティを記録 |

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

#### inboxes

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | enable_auto_assignment | true/false | 自動割り当ての有効/無効 |
| UPDATE | auto_assignment_config | JSON値 | 詳細設定 |

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | assignee_id | 選択されたエージェントのuser_id | 自動割り当て実行時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | RecordNotFound | 指定されたインボックスが存在しない | 404エラーを返却 |
| - | StandardError | 割り当て処理中のエラー | ログ出力後、例外を再スロー（テスト環境のみ） |

### リトライ仕様

AssignmentJobはSidekiqのデフォルトリトライ機構を使用。エラー発生時は自動的にリトライされる。

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

会話への割り当て更新はActiveRecordの暗黙的トランザクション内で実行。インボックス設定更新時のトランザクションはコントローラー層で管理。

## パフォーマンス要件

- バルク割り当て: 1回のジョブ実行で最大100件（`AUTO_ASSIGNMENT_BULK_LIMIT`環境変数で調整可能）
- 定期実行: schedule.ymlで設定されたインターバルで実行
- Redisキュー操作: O(1)〜O(n)の計算量

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

- インボックス設定変更はPunditポリシーによる認可チェック
- 管理者権限を持つユーザーのみが設定を変更可能
- APIトークンによるアクセス時も同様の権限チェックを実施

## 備考

- Enterprise版ではassignment_v2フィーチャーフラグで新しい割り当てシステムが有効化される
- Enterprise版では追加の割り当て順序（容量ベースなど）が利用可能

---

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

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

### 推奨読解順序

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

インボックスモデルの自動割り当て関連カラムと関連モデルを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | inbox.rb | `app/models/inbox.rb` | enable_auto_assignment、auto_assignment_configカラム、関連アソシエーション |
| 1-2 | assignment_policy.rb | `app/models/assignment_policy.rb` | 割り当てポリシーのスキーマ、enum定義 |
| 1-3 | inbox_assignment_policy.rb | `app/models/inbox_assignment_policy.rb` | インボックスとポリシーの中間テーブル |

**読解のコツ**: `inbox.rb`の7-40行目にスキーマ情報が記載されている。`auto_assignment_config`はJSONB型で柔軟な設定を保持できる。

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

自動割り当てがトリガーされる起点を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | auto_assignment_handler.rb | `app/models/concerns/auto_assignment_handler.rb` | after_saveコールバック、分岐ロジック |
| 2-2 | inboxes_controller.rb | `app/controllers/api/v1/accounts/inboxes_controller.rb` | 設定更新のAPIエンドポイント |

**主要処理フロー**:
1. **6行目**: `after_save :run_auto_assignment`でコールバック登録
2. **11-24行目**: `run_auto_assignment`メソッドで条件判定と処理分岐
3. **17-23行目**: assignment_v2有効時は`AssignmentJob`を非同期実行、無効時は`AgentAssignmentService`を同期実行

#### Step 3: レガシー割り当てサービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | agent_assignment_service.rb | `app/services/auto_assignment/agent_assignment_service.rb` | オンラインエージェント取得、ラウンドロビン呼び出し |

**主要処理フロー**:
- **7-9行目**: `find_assignee`でラウンドロビンサービスを呼び出し
- **11-13行目**: `perform`で会話を更新
- **18-21行目**: `online_agent_ids`でRedis経由でオンラインエージェントを取得

#### Step 4: 新しい割り当てサービス（assignment_v2）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | assignment_job.rb | `app/jobs/auto_assignment/assignment_job.rb` | ジョブのエントリーポイント |
| 4-2 | assignment_service.rb | `app/services/auto_assignment/assignment_service.rb` | バルク割り当てロジック |

**主要処理フロー**:
- **4-15行目** (assignment_job.rb): ジョブ実行、サービス呼び出し
- **4-14行目** (assignment_service.rb): `perform_bulk_assignment`で複数会話を処理
- **32-42行目**: `unassigned_conversations`で未割り当て会話を優先度順に取得

#### Step 5: ラウンドロビン・レート制限を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | inbox_round_robin_service.rb | `app/services/auto_assignment/inbox_round_robin_service.rb` | Redisキュー管理 |
| 5-2 | round_robin_selector.rb | `app/services/auto_assignment/round_robin_selector.rb` | エージェント選択ラッパー |
| 5-3 | rate_limiter.rb | `app/services/auto_assignment/rate_limiter.rb` | 公平分配制限 |

**主要処理フロー**:
- **28-32行目** (inbox_round_robin_service.rb): `available_agent`でキューから次のエージェントを取得
- **4-8行目** (rate_limiter.rb): `within_limit?`で制限内かチェック

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

```
Conversation (after_save)
    │
    └─ AutoAssignmentHandler#run_auto_assignment
           │
           ├─ [assignment_v2有効]
           │      │
           │      └─ AutoAssignment::AssignmentJob
           │             │
           │             └─ AutoAssignment::AssignmentService#perform_bulk_assignment
           │                    │
           │                    ├─ #unassigned_conversations (優先度順取得)
           │                    │
           │                    ├─ #find_available_agent
           │                    │      │
           │                    │      ├─ #filter_agents_by_rate_limit
           │                    │      │      └─ AutoAssignment::RateLimiter#within_limit?
           │                    │      │
           │                    │      └─ AutoAssignment::RoundRobinSelector#select_agent
           │                    │             └─ InboxRoundRobinService#available_agent
           │                    │
           │                    └─ #assign_conversation
           │
           └─ [レガシー]
                  │
                  └─ AutoAssignment::AgentAssignmentService#perform
                         │
                         └─ InboxRoundRobinService#available_agent
```

### データフロー図

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

会話作成/更新 ───▶ AutoAssignmentHandler ───▶ 会話更新(assignee_id)
       │                    │
       ▼                    ▼
 inbox設定参照 ───▶ assignment_v2判定
                           │
                    ┌──────┴──────┐
                    ▼              ▼
              AssignmentJob   AgentAssignmentService
                    │              │
                    ▼              ▼
             AssignmentService    InboxRoundRobinService
                    │                    │
                    ▼                    ▼
           RateLimiter ◀──── Redis ────▶ ラウンドロビンキュー
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| inbox.rb | `app/models/inbox.rb` | モデル | インボックス設定保持 |
| assignment_policy.rb | `app/models/assignment_policy.rb` | モデル | 割り当てポリシー定義 |
| inbox_assignment_policy.rb | `app/models/inbox_assignment_policy.rb` | モデル | インボックス-ポリシー関連 |
| auto_assignment_handler.rb | `app/models/concerns/auto_assignment_handler.rb` | Concern | 自動割り当てトリガー |
| inboxes_controller.rb | `app/controllers/api/v1/accounts/inboxes_controller.rb` | コントローラー | API設定エンドポイント |
| assignment_job.rb | `app/jobs/auto_assignment/assignment_job.rb` | ジョブ | 非同期割り当て実行 |
| periodic_assignment_job.rb | `app/jobs/auto_assignment/periodic_assignment_job.rb` | ジョブ | 定期バルク割り当て |
| assignment_service.rb | `app/services/auto_assignment/assignment_service.rb` | サービス | v2割り当てロジック |
| agent_assignment_service.rb | `app/services/auto_assignment/agent_assignment_service.rb` | サービス | レガシー割り当てロジック |
| inbox_round_robin_service.rb | `app/services/auto_assignment/inbox_round_robin_service.rb` | サービス | Redisキュー管理 |
| round_robin_selector.rb | `app/services/auto_assignment/round_robin_selector.rb` | サービス | エージェント選択 |
| rate_limiter.rb | `app/services/auto_assignment/rate_limiter.rb` | サービス | 公平分配制限 |
