# 機能設計書 150-Twilio音声

## 概要

本ドキュメントは、ChatwootとTwilio音声サービスを連携する統合機能の設計書である。この機能はEnterprise版限定であり、Twilio経由での音声通話（インバウンド/アウトバウンド）をChatwoot上で管理し、カンファレンスコールとして処理する仕組みを提供する。

### 本機能の処理概要

**業務上の目的・背景**：テキストベースのサポートでは対応が難しいケースや、電話サポートを希望する顧客に対して、Chatwoot上で音声通話を管理する需要がある。Twilio音声統合により、通話の受発信、会議への参加、通話状態の追跡をChatwoot上で一元管理できる。これにより、オムニチャネル対応が実現し、顧客体験の向上とサポート効率化が期待できる。

**機能の利用シーン**：
- 顧客からのインバウンド通話の受信
- エージェントからのアウトバウンド通話の発信
- 通話状態（呼び出し中、通話中、完了等）の追跡
- 会議機能による複数参加者の通話
- 通話履歴の記録

**主要な処理内容**：
1. Twilioからのコールバック受信（status, call_twiml, conference_status）
2. インバウンド通話時の会話・連絡先作成
3. 会議（カンファレンス）の管理
4. 通話状態の更新
5. 通話メッセージの作成・更新

**関連システム・外部連携**：
- Twilio Voice API（通話管理、TwiML生成）
- Twilio Conference API（会議管理）

**権限による制御**：
- Enterprise版限定機能
- Channel::Voice設定が必要

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 通話画面（Enterprise） | 主画面 | 音声通話の管理 |

## 機能種別

データ連携 / 音声通話 / 会議管理

## 入力仕様

### 入力パラメータ（Twilioコールバック）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| phone | String | Yes | 電話番号（E.164形式） | Channel::Voiceに存在 |
| CallSid | String | Yes | Twilio通話ID | - |
| CallStatus | String | Yes | 通話ステータス | - |
| Direction | String | Yes | 通話方向 | inbound/outbound-api/outbound-dial |
| From | String | Yes | 発信元番号 | - |
| To | String | Yes | 着信先番号 | - |

### 入力パラメータ（会議ステータス）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| StatusCallbackEvent | String | Yes | 会議イベント | conference-start/participant-join/participant-leave/conference-end |
| FriendlyName | String | Yes | 会議名 | - |
| ParticipantLabel | String | No | 参加者ラベル | agent/contact |

### 入力データソース

- Twilio Webhookコールバック

## 出力仕様

### 出力データ（TwiML）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| TwiML | XML | Twilio Voice Response XML |

### 出力データ（会話更新）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| conversation.identifier | String | Twilio CallSid |
| conversation.additional_attributes | JSONB | 通話情報（call_direction, call_status, conference_sid等） |

### 出力先

- Twilio（TwiMLレスポンス）
- conversationsテーブル
- messagesテーブル（voice_call）
- contactsテーブル

## 処理フロー

### 処理シーケンス（インバウンド通話）

```
1. Twilioからcall_twimlコールバック受信
   └─ VoiceController#call_twiml

2. インボックス特定
   └─ set_inbox!
   └─ Channel::Voice.find_by!(phone_number: e164)

3. 会話解決
   └─ resolve_conversation
   └─ InboundCallBuilder.perform!

4. 連絡先作成/取得
   └─ ensure_contact!

5. 会話作成
   └─ create_conversation!
   └─ identifier: CallSid

6. 通話メッセージ作成
   └─ Voice::CallMessageBuilder.perform!
   └─ content_type: 'voice_call'

7. カンファレンスSID生成
   └─ ensure_conference_sid!

8. TwiML生成・返却
   └─ conference_twiml
   └─ Twilio::TwiML::VoiceResponse
```

### 処理シーケンス（ステータス更新）

```
1. TwilioからstatusコールバックCallback受信
   └─ VoiceController#status

2. ステータス正規化
   └─ Voice::StatusUpdateService
   └─ TWILIO_STATUS_MAP

3. 会話検索
   └─ conversations.find_by(identifier: call_sid)

4. ステータス更新
   └─ Voice::CallStatus::Manager
   └─ apply_status

5. メッセージ更新
   └─ update_message
   └─ content_attributes更新
```

### フローチャート

```mermaid
flowchart TD
    A[Twilioコールバック受信] --> B{エンドポイント}
    B -->|status| C[Voice::StatusUpdateService]
    B -->|call_twiml| D{Direction判定}
    B -->|conference_status| E[Voice::Conference::Manager]

    D -->|inbound| F[InboundCallBuilder]
    D -->|outbound| G[sync_outbound_leg]
    D -->|agent leg| H[find_conversation_for_agent]

    F --> I[ensure_contact!]
    I --> J[create_conversation!]
    J --> K[build_voice_message!]
    K --> L[ensure_conference_sid!]
    L --> M[conference_twiml生成]

    C --> N[normalize_status]
    N --> O[CallStatus::Manager]
    O --> P[apply_status]
    P --> Q[update_message]

    E --> R{event判定}
    R -->|start| S[ensure_conference_sid!]
    R -->|join| T[mark_in_progress!]
    R -->|leave| U[handle_leave!]
    R -->|end| V[finalize_conference!]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-150-01 | Enterprise限定 | この機能はEnterprise版でのみ利用可能 | 全操作 |
| BR-150-02 | ステータスマッピング | Twilioステータスを内部ステータスにマッピング | ステータス更新時 |
| BR-150-03 | エージェント参加判定 | client:で始まる番号はエージェントと判定 | 通話処理時 |
| BR-150-04 | 会議開始条件 | エージェント参加で会議開始（start_conference_on_enter: true） | TwiML生成時 |
| BR-150-05 | 参加者ラベル | エージェントはagent、連絡先はcontactラベル | TwiML生成時 |
| BR-150-06 | 通話終了ステータス | completed, no-answer, failedは終了ステータス | ステータス更新時 |

### ステータスマッピング

| Twilioステータス | 内部ステータス |
|-----------------|---------------|
| queued, initiated, ringing | ringing |
| in-progress, inprogress, answered | in-progress |
| completed | completed |
| busy, no-answer | no-answer |
| failed, canceled | failed |

### 計算ロジック

通話時間の計算:
```ruby
duration = call_ended_at - call_started_at
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| インバウンド | contacts | INSERT/SELECT | 連絡先作成/取得 |
| インバウンド | contact_inboxes | INSERT | コンタクトインボックス作成 |
| インバウンド | conversations | INSERT | 会話作成 |
| インバウンド | messages | INSERT | 通話メッセージ作成 |
| ステータス更新 | conversations | UPDATE | additional_attributes更新 |
| ステータス更新 | messages | UPDATE | content_attributes更新 |

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

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | identifier | Twilio CallSid | 通話識別子 |
| INSERT | status | 'open' | 初期ステータス |
| UPDATE | additional_attributes | call_direction, call_status, conference_sid等 | 通話情報 |
| UPDATE | last_activity_at | 現在時刻 | アクティビティ更新 |

#### messages

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | content_type | 'voice_call' | 通話メッセージ |
| INSERT | message_type | incoming/outgoing | 方向による |
| INSERT | content_attributes | { data: { call_sid, status, ... } } | 通話情報 |
| UPDATE | content_attributes | statusの更新 | ステータス変更時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | Channel::Voiceが見つからない | インボックス設定を確認 |
| 404 | Not Found | 会話が見つからない | 正しいCallSidを使用 |
| - | ArgumentError | 未対応のDirection | ログ出力 |

### リトライ仕様

Twilioコールバック自体にはリトライ仕様は存在しない（Twilio側でリトライ制御）

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

- InboundCallBuilder内の処理はActiveRecord::Base.transactionで囲まれている
- 連絡先作成、会話作成、メッセージ作成は同一トランザクション

## パフォーマンス要件

- Twilioコールバック応答: 5秒以内（Twilio要件）

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

- Enterprise版限定でアクセス制御
- Twilioコールバックは公開エンドポイント
- phone_numberによるインボックス特定でスコープ制御

## 備考

- この機能はEnterprise版（enterprise/app/controllers/twilio/voice_controller.rb）で提供
- Twilio Conference機能を使用
- TwiML（Twilio Markup Language）でレスポンス生成

---

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

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

### 推奨読解順序

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

通話ステータスとマッピングを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | status_update_service.rb | `enterprise/app/services/voice/status_update_service.rb` | ステータスマッピング |
| 1-2 | manager.rb | `enterprise/app/services/voice/call_status/manager.rb` | ステータス管理 |

**読解のコツ**:
- **status_update_service.rb 4-16行目**: TWILIO_STATUS_MAPでTwilioステータスを内部ステータスにマッピング
- **manager.rb 4-5行目**: ALLOWED_STATUSESとTERMINAL_STATUSESの定義

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

VoiceControllerの各エンドポイントを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | voice_controller.rb | `enterprise/app/controllers/twilio/voice_controller.rb` | 各種Webhookエンドポイント |

**主要処理フロー**:
1. **11-19行目**: `status`でステータス更新
2. **21-31行目**: `call_twiml`でTwiML生成
3. **33-50行目**: `conference_status`で会議イベント処理
4. **83-103行目**: `resolve_conversation`でDirection判定
5. **137-151行目**: `conference_twiml`でTwiML生成

#### Step 3: インバウンド通話処理を理解する

インバウンド通話の処理フローを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | inbound_call_builder.rb | `enterprise/app/services/voice/inbound_call_builder.rb` | インバウンド処理 |
| 3-2 | call_message_builder.rb | `enterprise/app/services/voice/call_message_builder.rb` | メッセージ作成 |

**主要処理フロー**:
- **inbound_call_builder.rb 15-26行目**: `perform!`でトランザクション内処理
- **inbound_call_builder.rb 31-35行目**: `ensure_contact!`で連絡先作成
- **call_message_builder.rb 42-49行目**: `create_message!`でvoice_callメッセージ作成

#### Step 4: 会議管理を理解する

カンファレンス管理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | manager.rb | `enterprise/app/services/voice/conference/manager.rb` | 会議イベント処理 |

**主要処理フロー**:
- **4-15行目**: `process`でイベント種別に応じた処理
- **39-42行目**: `mark_in_progress!`でエージェント参加時処理
- **44-51行目**: `handle_leave!`で離脱時処理

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

```
Twilio::VoiceController
    │
    ├─ #status
    │      └─ Voice::StatusUpdateService.perform
    │             └─ normalize_status
    │             └─ Voice::CallStatus::Manager.process_status_update
    │                    └─ apply_status
    │                    └─ update_message
    │
    ├─ #call_twiml
    │      ├─ set_inbox!
    │      │      └─ Channel::Voice.find_by!(phone_number: e164)
    │      ├─ resolve_conversation
    │      │      ├─ [inbound] Voice::InboundCallBuilder.perform!
    │      │      │      ├─ ensure_contact!
    │      │      │      ├─ ensure_contact_inbox!
    │      │      │      ├─ create_conversation!
    │      │      │      └─ Voice::CallMessageBuilder.perform!
    │      │      ├─ [outbound] sync_outbound_leg
    │      │      └─ [agent] find_conversation_for_agent
    │      ├─ ensure_conference_sid!
    │      └─ conference_twiml
    │             └─ Twilio::TwiML::VoiceResponse
    │
    └─ #conference_status
           └─ Voice::Conference::Manager.process
                  ├─ [start] ensure_conference_sid!
                  ├─ [join] mark_in_progress!
                  ├─ [leave] handle_leave!
                  └─ [end] finalize_conference!
```

### データフロー図

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

Twilioコールバック     ───▶ VoiceController             ───▶ TwiML Response
(CallSid, Status,              │
 Direction, etc.)              ▼
                       resolve_conversation            ───▶ conversations
                               │                              (identifier, attrs)
                               ▼
                       InboundCallBuilder              ───▶ contacts
                               │                              (phone_number)
                               ▼
                       CallMessageBuilder              ───▶ messages
                               │                              (voice_call)
                               ▼
                       CallStatus::Manager             ───▶ conversations
                                                             (status更新)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| voice_controller.rb | `enterprise/app/controllers/twilio/voice_controller.rb` | コントローラー | Twilioコールバック受信 |
| status_update_service.rb | `enterprise/app/services/voice/status_update_service.rb` | サービス | ステータス更新 |
| inbound_call_builder.rb | `enterprise/app/services/voice/inbound_call_builder.rb` | サービス | インバウンド処理 |
| outbound_call_builder.rb | `enterprise/app/services/voice/outbound_call_builder.rb` | サービス | アウトバウンド処理 |
| call_message_builder.rb | `enterprise/app/services/voice/call_message_builder.rb` | サービス | 通話メッセージ作成 |
| call_session_sync_service.rb | `enterprise/app/services/voice/call_session_sync_service.rb` | サービス | 通話セッション同期 |
| manager.rb | `enterprise/app/services/voice/call_status/manager.rb` | サービス | ステータス管理 |
| manager.rb | `enterprise/app/services/voice/conference/manager.rb` | サービス | 会議イベント処理 |
| name.rb | `enterprise/app/services/voice/conference/name.rb` | サービス | 会議名生成 |
| adapter.rb | `enterprise/app/services/voice/provider/twilio/adapter.rb` | サービス | Twilioアダプター |
| conference_service.rb | `enterprise/app/services/voice/provider/twilio/conference_service.rb` | サービス | 会議サービス |
| token_service.rb | `enterprise/app/services/voice/provider/twilio/token_service.rb` | サービス | トークン生成 |
