# 機能設計書 114-チャネル別レポート

## 概要

本ドキュメントは、Chatwootのチャネル別レポート機能の設計を記述する。この機能は、チャネルタイプ（Facebook、Email、Web Widget等）ごとの会話統計を集計し、チャネル別のサポート運用状況を可視化するレポーティング機能である。

### 本機能の処理概要

チャネル別レポート機能は、インボックスのチャネルタイプごとに会話をグループ化し、各ステータス（オープン、解決済み、保留中、スヌーズ中）の会話数を集計する機能である。これにより、どのコミュニケーションチャネルが最も利用されているか、どのチャネルの解決率が高いかなどを把握できる。

**業務上の目的・背景**：カスタマーサポートでは、メール、チャット、SNSなど複数のチャネルを運用することが一般的である。チャネル別レポートにより、顧客がどのチャネルを好んで利用しているか、各チャネルの対応状況はどうかを分析できる。この情報は、チャネル戦略の見直しや、特定チャネルへのリソース集中投下の判断材料として活用できる。

**機能の利用シーン**：
- マルチチャネル運用時のチャネル別利用状況分析
- チャネル投資対効果の測定
- 新規チャネル導入後の効果検証
- チャネル別のステータス分布確認

**主要な処理内容**：
1. 指定期間内の会話をチャネルタイプ別・ステータス別にグループ化
2. 各チャネルのオープン、解決済み、保留中、スヌーズ中の会話数を集計
3. 合計数を算出してJSON形式で返却

**関連システム・外部連携**：特になし（内部データの集計機能）

**権限による制御**：管理者（Administrator）ロールを持つユーザーのみがレポートを閲覧可能（ReportPolicy#view?で制御）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | チャネルサマリーAPI | 主機能 | チャネル別統計のJSON取得 |

## 機能種別

データ集計・レポート出力

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| since | String | Yes | 集計開始日時（UNIXタイムスタンプ文字列） | 数値変換可能な文字列 |
| until | String | Yes | 集計終了日時（UNIXタイムスタンプ文字列） | 数値変換可能な文字列 |

### 入力データソース

- HTTPリクエストパラメータ（API呼び出し時）
- Current.account（認証済みユーザーのアカウント情報）

## 出力仕様

### 出力データ

出力はチャネルタイプをキーとするハッシュ形式。

| 項目名 | 型 | 説明 |
|--------|-----|------|
| {channel_type} | Hash | チャネルタイプ（例：Channel::WebWidget）ごとの統計 |
| {channel_type}.open | Integer | オープン状態の会話数 |
| {channel_type}.resolved | Integer | 解決済みの会話数 |
| {channel_type}.pending | Integer | 保留中の会話数 |
| {channel_type}.snoozed | Integer | スヌーズ中の会話数 |
| {channel_type}.total | Integer | 合計会話数 |

### 出力先

- JSON形式：SummaryReports APIエンドポイント（`/api/v2/accounts/:account_id/summary_reports/channel`）

## 処理フロー

### 処理シーケンス

```
1. API呼び出し（GET /api/v2/accounts/:account_id/summary_reports/channel）
   └─ SummaryReportsController#channel アクションが起動

2. 認可チェック
   └─ ReportPolicy#view? で管理者権限を確認

3. レポートデータ生成
   └─ ChannelSummaryBuilder でデータを集計

4. 会話のグループ化
   └─ inboxes.channel_typeとconversations.statusでグループ化

5. チャネル別統計構築
   └─ 各チャネルのステータス別会話数を集計

6. レスポンス返却
   └─ JSON形式でチャネル別統計を返却
```

### フローチャート

```mermaid
flowchart TD
    A[API呼び出し] --> B{認可チェック}
    B -->|権限あり| C[ChannelSummaryBuilder初期化]
    B -->|権限なし| Z[403 Forbidden]
    C --> D[conversations_by_channel_and_status]
    D --> E[inboxesテーブルとJOIN]
    E --> F[channel_type, statusでグループ化]
    F --> G[build_channel_stats]
    G --> H[ステータス別カウントを構築]
    H --> I[JSON形式でレスポンス返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-114-01 | 期間指定必須 | since/untilパラメータは必須 | 常時 |
| BR-114-02 | チャネルタイプでグループ化 | inboxes.channel_typeでグループ化 | 常時 |
| BR-114-03 | ステータス別集計 | open, resolved, pending, snoozedの4ステータスを集計 | 常時 |
| BR-114-04 | 合計算出 | total = open + resolved + pending + snoozed | 常時 |

### 計算ロジック

**会話のグループ化クエリ**：
```ruby
account.conversations
       .joins(:inbox)
       .where(created_at: range)
       .group('inboxes.channel_type', 'conversations.status')
       .count
```

**チャネル別統計の構築**：
```ruby
{
  open: status_counts['open'] || 0,
  resolved: status_counts['resolved'] || 0,
  pending: status_counts['pending'] || 0,
  snoozed: status_counts['snoozed'] || 0,
  total: open + resolved + pending + snoozed
}
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 会話集計 | conversations, inboxes | SELECT | チャネルタイプ・ステータス別にカウント |

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

#### conversations, inboxes (JOIN)

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | COUNT(*) | created_at BETWEEN since AND until, GROUP BY inboxes.channel_type, conversations.status | チャネル・ステータス別会話数 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | Forbidden | 管理者権限がない場合 | 権限のあるユーザーでログイン |
| 422 | Unprocessable Entity | パラメータ不正 | パラメータを確認して再リクエスト |

### リトライ仕様

該当なし（同期処理のため）

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

読み取り専用処理のため、トランザクション制御は不要。

## パフォーマンス要件

- チャネル数が多い場合でも迅速にレスポンスを返すこと
- inboxes.channel_typeとconversations.statusへの適切なインデックスが必要

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

- 管理者ロールのみがアクセス可能（ReportPolicyで制御）
- アカウント間のデータ分離はCurrent.accountスコープで担保

## 備考

- チャネルタイプは`Channel::WebWidget`、`Channel::FacebookPage`、`Channel::Email`等のクラス名形式で出力される
- 会話のステータス値はenumで定義されており、open=0, resolved=1, pending=2, snoozed=3に対応
- InboxSummaryBuilderとは異なり、チャネルタイプ（クラス名）でグループ化するため、同一チャネルタイプの複数インボックスは合算される

---

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

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

### 推奨読解順序

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

まず、関連するデータモデルの関係を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | inbox.rb | `app/models/inbox.rb` | channel_typeカラムの存在確認 |
| 1-2 | conversation.rb | `app/models/conversation.rb` | statusのenum定義 |

**読解のコツ**: InboxモデルはPolymorphic Association（channel_type, channel_id）でチャネルと紐づいている。channel_typeはクラス名が文字列として格納される。

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

APIコントローラーからの処理フローを確認する。SummaryReportsControllerがチャネル別レポートを提供する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | summary_reports_controller.rb | `app/controllers/api/v2/accounts/summary_reports_controller.rb` | channelアクション |

**主要処理フロー**:
- ChannelSummaryBuilderを呼び出してデータを取得
- JSON形式でレスポンスを返却

#### Step 3: ビルダー層を理解する

実際のデータ集計ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | channel_summary_builder.rb | `app/builders/v2/reports/channel_summary_builder.rb` | チャネル集計ロジック |

**主要処理フロー**:
- **1-4行目**: DateRangeHelperをinclude、pattr_initializeで初期化
- **6-8行目**: buildメソッドでconversations_by_channel_and_statusを呼び出し、結果をtransform_values
- **12-22行目**: conversations_by_channel_and_statusでinboxesとJOINし、channel_type, statusでグループ化
- **24-37行目**: build_channel_statsでステータス別カウントを構築し、totalを算出

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

```
SummaryReportsController#channel
    │
    ├─ check_authorization
    │      └─ ReportPolicy#view?
    │
    └─ ChannelSummaryBuilder#new
           │
           └─ ChannelSummaryBuilder#build
                  │
                  ├─ conversations_by_channel_and_status
                  │      │
                  │      ├─ account.conversations.joins(:inbox)
                  │      │
                  │      └─ group('inboxes.channel_type', 'conversations.status')
                  │             .count
                  │
                  └─ transform_values { build_channel_stats }
                         │
                         └─ build_channel_stats(status_counts)
                                │
                                └─ { open, resolved, pending, snoozed, total }
```

### データフロー図

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

since/until パラメータ ───▶ DateRangeHelper#range ───────────┐
                                                              │
                              ┌──────────────────────────────┘
                              ▼
Current.account ────────────▶ ChannelSummaryBuilder
                              │
                              ├─▶ conversations.joins(:inbox)
                              │      .where(created_at: range)
                              │      .group('inboxes.channel_type',
                              │             'conversations.status')
                              │      .count
                              │
                              └─────────────────────────────────────┐
                                                                    │
                              ┌─────────────────────────────────────┘
                              ▼
                         each_with_object({})
                              │
                              ├─▶ grouped[channel_type][status] = count
                              │
                              └─▶ transform_values
                                     │
                                     └─▶ build_channel_stats
                                            │
                                            ▼
                                     { channel_type => { open, resolved,
                                       pending, snoozed, total } }
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| summary_reports_controller.rb | `app/controllers/api/v2/accounts/summary_reports_controller.rb` | コントローラー | APIエンドポイント |
| channel_summary_builder.rb | `app/builders/v2/reports/channel_summary_builder.rb` | ビルダー | チャネル集計ロジック |
| date_range_helper.rb | `app/helpers/date_range_helper.rb` | ヘルパー | 日付範囲処理 |
| inbox.rb | `app/models/inbox.rb` | モデル | インボックスモデル（channel_type） |
| conversation.rb | `app/models/conversation.rb` | モデル | 会話モデル（status enum） |
| report_policy.rb | `app/policies/report_policy.rb` | ポリシー | 認可制御 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（432行目） |
