# 機能設計書 121-サマリーレポートV2

## 概要

本ドキュメントは、Chatwootの「サマリーレポートV2」機能の設計仕様を記載する。この機能は、エージェント、チーム、インボックス、ラベル、チャネルごとのパフォーマンス指標を集計し、サマリーレポートとして提供するAPIである。

### 本機能の処理概要

サマリーレポートV2は、カスタマーサポートチームの業務効率を測定・分析するための包括的なレポーティング機能である。会話数、解決数、平均応答時間などの重要KPIを複数の切り口で集計する。

**業務上の目的・背景**：カスタマーサポート業務において、エージェントやチームのパフォーマンスを定量的に評価・改善することは重要な経営課題である。本機能は、会話の処理状況、応答速度、解決率などを可視化することで、リソース配分の最適化、ボトルネックの特定、サービス品質の向上を支援する。

**機能の利用シーン**：
- 管理者がエージェントごとの業績を週次・月次で評価する際
- チームリーダーがチーム全体のパフォーマンスを把握する際
- インボックスごとの負荷分散を分析する際
- ラベル別に特定カテゴリの会話傾向を分析する際
- チャネル別（メール、チャット、SNS等）の利用状況を比較する際

**主要な処理内容**：
1. 認可チェック（レポート閲覧権限の確認）
2. 日付範囲パラメータの解析
3. 指定された集計軸（エージェント/チーム/インボックス/ラベル/チャネル）でのデータ取得
4. 会話数、解決数、平均解決時間、平均初回応答時間、平均返信時間の計算
5. JSON形式でのレポートデータ返却

**関連システム・外部連携**：特になし（内部データベースのみ参照）

**権限による制御**：`report:view`権限を持つユーザーのみがレポートを閲覧可能。ReportPolicyによる認可チェックが行われる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | レポート画面 | 主画面 | サマリーレポートの表示・フィルタリング |

## 機能種別

計算処理 / データ集計

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| since | Integer | No | 集計開始日時（Unix timestamp） | 数値形式 |
| until | Integer | No | 集計終了日時（Unix timestamp） | 数値形式 |
| business_hours | Boolean | No | 営業時間ベースで計算するか | true/false |

### 入力データソース

- URLパラメータから取得
- Current.accountからアカウント情報を取得

## 出力仕様

### 出力データ

#### エージェントサマリー（/agent）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | ユーザーID |
| conversations_count | Integer | 会話数 |
| resolved_conversations_count | Integer | 解決済み会話数 |
| avg_resolution_time | Float | 平均解決時間（秒） |
| avg_first_response_time | Float | 平均初回応答時間（秒） |
| avg_reply_time | Float | 平均返信時間（秒） |

#### チームサマリー（/team）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | チームID |
| conversations_count | Integer | 会話数 |
| resolved_conversations_count | Integer | 解決済み会話数 |
| avg_resolution_time | Float | 平均解決時間（秒） |
| avg_first_response_time | Float | 平均初回応答時間（秒） |
| avg_reply_time | Float | 平均返信時間（秒） |

#### インボックスサマリー（/inbox）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | インボックスID |
| conversations_count | Integer | 会話数 |
| resolved_conversations_count | Integer | 解決済み会話数 |
| avg_resolution_time | Float | 平均解決時間（秒） |
| avg_first_response_time | Float | 平均初回応答時間（秒） |
| avg_reply_time | Float | 平均返信時間（秒） |

#### ラベルサマリー（/label）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | ラベルID |
| name | String | ラベル名 |
| conversations_count | Integer | 会話数 |
| resolved_conversations_count | Integer | 解決済み会話数 |
| avg_resolution_time | Float | 平均解決時間（秒） |
| avg_first_response_time | Float | 平均初回応答時間（秒） |
| avg_reply_time | Float | 平均返信時間（秒） |

#### チャネルサマリー（/channel）

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

### 出力先

- JSON形式でHTTPレスポンスとして返却

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ APIエンドポイント（/api/v2/accounts/:account_id/summary_reports/:type）
2. 認可チェック
   └─ ReportPolicy#view?でレポート閲覧権限を確認
3. パラメータ準備
   └─ since, until, business_hoursパラメータを抽出
4. チャネルサマリーの場合、日付範囲チェック
   └─ 6ヶ月を超える場合はエラー返却
5. Builderクラス実行
   └─ 対応するSummaryBuilderでデータ集計
6. レスポンス返却
   └─ JSON形式でレポートデータを返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{認可チェック}
    B -->|失敗| C[403 Forbidden]
    B -->|成功| D[パラメータ準備]
    D --> E{チャネルサマリー?}
    E -->|Yes| F{日付範囲 > 6ヶ月?}
    F -->|Yes| G[エラー返却]
    F -->|No| H[ChannelSummaryBuilder実行]
    E -->|No| I{集計タイプ判定}
    I -->|agent| J[AgentSummaryBuilder実行]
    I -->|team| K[TeamSummaryBuilder実行]
    I -->|inbox| L[InboxSummaryBuilder実行]
    I -->|label| M[LabelSummaryBuilder実行]
    J --> N[JSON レスポンス返却]
    K --> N
    L --> N
    M --> N
    H --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 日付範囲制限 | チャネルサマリーは6ヶ月以内の日付範囲のみ許可 | チャネルサマリー取得時 |
| BR-002 | 営業時間計算 | business_hours=trueの場合、営業時間ベースで計算 | パラメータ指定時 |
| BR-003 | 集計対象 | 指定期間内のcreated_atを持つ会話・イベントが対象 | 全レポートタイプ |

### 計算ロジック

- **会話数**: 指定期間内に作成された会話をグループ化してカウント
- **解決数**: reporting_eventsテーブルのconversation_resolvedイベントをカウント
- **平均解決時間**: reporting_eventsのconversation_resolvedイベントのvalue（またはvalue_in_business_hours）の平均
- **平均初回応答時間**: reporting_eventsのfirst_responseイベントのvalueの平均
- **平均返信時間**: reporting_eventsのreply_timeイベントのvalueの平均

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 会話数取得 | conversations | SELECT | 期間内の会話をグループ化してカウント |
| イベント取得 | reporting_events | SELECT | 解決・応答関連イベントを集計 |
| エージェント取得 | account_users | SELECT | アカウントに属するユーザー一覧取得 |
| チーム取得 | teams | SELECT | アカウントのチーム一覧取得 |
| インボックス取得 | inboxes | SELECT | アカウントのインボックス一覧取得 |
| ラベル取得 | labels | SELECT | アカウントのラベル一覧取得 |

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

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, assignee_id, team_id, inbox_id, status | created_at BETWEEN :since AND :until | グループ化集計 |

#### reporting_events

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | name, value, value_in_business_hours, user_id, inbox_id | created_at BETWEEN :since AND :until | AVG/COUNT集計 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | Forbidden | レポート閲覧権限なし | 管理者に権限付与を依頼 |
| 422 | Unprocessable Entity | チャネルサマリーで日付範囲が6ヶ月超過 | 日付範囲を6ヶ月以内に調整 |

### リトライ仕様

特になし（読み取り専用API）

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

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

## パフォーマンス要件

- 大量データの集計処理のため、適切なインデックスの活用が重要
- チャネルサマリーは6ヶ月制限によりデータ量を抑制

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

- ReportPolicyによる認可チェック必須
- アカウントスコープでのデータアクセス制限

## 備考

- V2 APIとして従来のレポートAPIを拡張
- DateRangeHelperを使用して日付範囲を処理

---

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

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

### 推奨読解順序

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

まず、レポートの基盤となるデータモデルを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | reporting_event.rb | `app/models/reporting_event.rb` | イベントデータの構造（name, value, value_in_business_hours） |
| 1-2 | conversation.rb | `app/models/conversation.rb` | 会話の状態（status）とリレーション |

**読解のコツ**: reporting_eventsテーブルは`conversation_resolved`、`first_response`、`reply_time`の3種類のイベントを記録している。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | summary_reports_controller.rb | `app/controllers/api/v2/accounts/summary_reports_controller.rb` | 各エンドポイント（agent, team, inbox, label, channel）の定義 |

**主要処理フロー**:
1. **2-3行目**: before_actionで認可チェックとパラメータ準備
2. **5-25行目**: 各サマリータイプのアクションメソッド
3. **41-43行目**: render_report_withでBuilderを実行しJSON返却
4. **50-55行目**: date_range_too_long?でチャネルサマリーの日付範囲チェック

#### Step 3: Builderクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | base_summary_builder.rb | `app/builders/v2/reports/base_summary_builder.rb` | 共通の集計ロジック（load_data, load_reporting_events_data） |
| 3-2 | agent_summary_builder.rb | `app/builders/v2/reports/agent_summary_builder.rb` | エージェント別集計の実装 |
| 3-3 | team_summary_builder.rb | `app/builders/v2/reports/team_summary_builder.rb` | チーム別集計の実装 |
| 3-4 | inbox_summary_builder.rb | `app/builders/v2/reports/inbox_summary_builder.rb` | インボックス別集計の実装 |
| 3-5 | label_summary_builder.rb | `app/builders/v2/reports/label_summary_builder.rb` | ラベル別集計の実装（独自の集計ロジック） |
| 3-6 | channel_summary_builder.rb | `app/builders/v2/reports/channel_summary_builder.rb` | チャネル別集計の実装 |

**主要処理フロー（BaseSummaryBuilder）**:
- **4-7行目**: buildメソッドでload_data→prepare_reportの順序で実行
- **11-14行目**: load_dataで会話数取得とイベントデータ読み込み
- **16-34行目**: load_reporting_events_dataで解決数、平均時間を一括集計

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

```
SummaryReportsController#agent/team/inbox/label/channel
    │
    ├─ check_authorization
    │      └─ ReportPolicy#view?
    │
    ├─ prepare_builder_params
    │      └─ パラメータ抽出
    │
    └─ render_report_with(BuilderClass)
           │
           ├─ V2::Reports::AgentSummaryBuilder#build
           │      ├─ load_data
           │      │      ├─ fetch_conversations_count
           │      │      └─ load_reporting_events_data
           │      └─ prepare_report
           │
           ├─ V2::Reports::TeamSummaryBuilder#build
           │      └─ (同様の構造)
           │
           ├─ V2::Reports::InboxSummaryBuilder#build
           │      └─ (同様の構造)
           │
           ├─ V2::Reports::LabelSummaryBuilder#build
           │      └─ collect_report_data (独自実装)
           │
           └─ V2::Reports::ChannelSummaryBuilder#build
                  └─ conversations_by_channel_and_status
```

### データフロー図

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

パラメータ
(since, until,      ───▶ SummaryReportsController
business_hours)           │
                          ▼
                    SummaryBuilder
                          │
                          ├─▶ conversations (SELECT)
                          │      ▼
                          │   会話数集計
                          │
                          └─▶ reporting_events (SELECT)
                                 ▼
                           イベント集計                  ───▶ JSON Response
                           (解決数, 平均時間)                 (サマリーデータ)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| summary_reports_controller.rb | `app/controllers/api/v2/accounts/summary_reports_controller.rb` | コントローラー | APIエントリーポイント |
| base_summary_builder.rb | `app/builders/v2/reports/base_summary_builder.rb` | ビルダー | 共通集計ロジック |
| agent_summary_builder.rb | `app/builders/v2/reports/agent_summary_builder.rb` | ビルダー | エージェント別集計 |
| team_summary_builder.rb | `app/builders/v2/reports/team_summary_builder.rb` | ビルダー | チーム別集計 |
| inbox_summary_builder.rb | `app/builders/v2/reports/inbox_summary_builder.rb` | ビルダー | インボックス別集計 |
| label_summary_builder.rb | `app/builders/v2/reports/label_summary_builder.rb` | ビルダー | ラベル別集計 |
| channel_summary_builder.rb | `app/builders/v2/reports/channel_summary_builder.rb` | ビルダー | チャネル別集計 |
| date_range_helper.rb | `app/helpers/date_range_helper.rb` | ヘルパー | 日付範囲処理 |
| report_policy.rb | `app/policies/report_policy.rb` | ポリシー | 認可チェック |
