# 機能設計書 118-ライブレポート

## 概要

本ドキュメントは、Chatwootのライブレポート機能の設計を記述する。この機能は、リアルタイムの会話統計を提供し、現在のサポートキューの状態を即座に把握できるレポーティング機能である。

### 本機能の処理概要

ライブレポート機能は、現在のオープン会話数、未対応会話数、未割り当て会話数、保留中会話数をリアルタイムで取得する機能である。また、チーム別またはエージェント別にグループ化したメトリクスも取得可能で、サポートリソースの即時的な状況把握を支援する。

**業務上の目的・背景**：カスタマーサポート業務では、リアルタイムのキュー状況を把握することが重要である。ライブレポートにより、現在対応が必要な会話数、未対応のままになっている会話数、未割り当ての会話数を即座に確認でき、リソースの迅速な再配分やエスカレーション判断に活用できる。

**機能の利用シーン**：
- リアルタイムダッシュボードでのキュー状況表示
- シフト交代時の引き継ぎ状況確認
- チーム別・エージェント別の負荷確認
- 未対応会話の早期検知

**主要な処理内容**：
1. conversation_metrics：アカウント全体のライブメトリクスを取得
2. grouped_conversation_metrics：チーム別またはエージェント別にグループ化したメトリクスを取得
3. オープン、未対応、未割り当て、保留中の会話数を集計
4. JSON形式でメトリクスを返却

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

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 28 | レポート概要画面 | 主機能 | リアルタイム統計の表示 |

## 機能種別

リアルタイムデータ集計・レポート出力

## 入力仕様

### 入力パラメータ（conversation_metrics）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| team_id | Integer | No | チームIDでフィルタリング | 存在するチームID |

### 入力パラメータ（grouped_conversation_metrics）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| team_id | Integer | No | チームIDでフィルタリング | 存在するチームID |
| group_by | String | Yes | グループ化キー | team_id または assignee_id |

### 入力データソース

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

## 出力仕様

### 出力データ（conversation_metrics）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| open | Integer | オープン状態の会話数 |
| unattended | Integer | 未対応の会話数 |
| unassigned | Integer | 未割り当ての会話数 |
| pending | Integer | 保留中の会話数 |

### 出力データ（grouped_conversation_metrics）

配列形式で各グループの情報を返却。

| 項目名 | 型 | 説明 |
|--------|-----|------|
| open | Integer | オープン状態の会話数 |
| unattended | Integer | 未対応の会話数 |
| unassigned | Integer | 未割り当ての会話数 |
| team_id または assignee_id | Integer | グループ化キーの値 |

### 出力先

- JSON形式：API V2エンドポイント
  - `/api/v2/accounts/:account_id/live_reports/conversation_metrics`
  - `/api/v2/accounts/:account_id/live_reports/grouped_conversation_metrics`

## 処理フロー

### 処理シーケンス（conversation_metrics）

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

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

3. 会話ロード
   └─ load_conversations でスコープを設定（team_idフィルタ含む）

4. メトリクス集計
   └─ open, unattended, unassigned, pending の各カウントを取得

5. レスポンス返却
   └─ JSON形式でメトリクスを返却
```

### 処理シーケンス（grouped_conversation_metrics）

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

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

3. グループスコープ設定
   └─ set_group_scope で group_by パラメータを検証

4. 会話ロード
   └─ load_conversations でスコープを設定

5. グループ別メトリクス集計
   └─ group(group_scope).count で各グループのカウントを取得

6. レスポンス構築
   └─ 各グループの open, unattended, unassigned を配列で構築

7. レスポンス返却
   └─ JSON形式でメトリクスを返却
```

### フローチャート

```mermaid
flowchart TD
    A[API呼び出し] --> B{認可チェック}
    B -->|権限あり| C{エンドポイント}
    B -->|権限なし| Z[403 Forbidden]
    C -->|conversation_metrics| D[load_conversations]
    C -->|grouped_conversation_metrics| E[set_group_scope]
    D --> F[open/unattended/unassigned/pending count]
    E --> G{group_by 有効?}
    G -->|No| Y[422 Unprocessable Entity]
    G -->|Yes| H[load_conversations]
    H --> I[group によるカウント]
    F --> J[JSON レスポンス]
    I --> K[グループ別メトリクス構築]
    K --> J
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-118-01 | グループキー制限 | group_byはteam_idまたはassignee_idのみ有効 | grouped_conversation_metrics |
| BR-118-02 | チームフィルタ | team_idが指定された場合、そのチームの会話のみ対象 | team_id パラメータあり |
| BR-118-03 | リアルタイム集計 | 期間指定なし、現在の状態を即時集計 | 常時 |

### 計算ロジック

**オープン会話の取得**：
```ruby
@conversations.open.count
```

**未対応会話の取得**：
```ruby
@conversations.open.unattended.count
```

**未割り当て会話の取得**：
```ruby
@conversations.open.unassigned.count
```

**保留中会話の取得**：
```ruby
@conversations.pending.count
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 会話カウント | conversations | SELECT | 各種ステータスの会話数をカウント |
| チーム取得 | teams | SELECT | team_idフィルタ時にチームを取得 |

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

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | COUNT(*) | status = 'open', 各種スコープ | ステータス別会話数 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | Forbidden | 管理者権限がない場合 | 権限のあるユーザーでログイン |
| 422 | Unprocessable Entity | 無効なgroup_by値 | team_idまたはassignee_idを指定 |

### リトライ仕様

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

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

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

## パフォーマンス要件

- リアルタイム性が求められるため、高速なレスポンスが必要（1秒以内推奨）
- conversations.statusとteam_id、assignee_idへの適切なインデックスが必要

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

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

## 備考

- ライブレポートは期間指定パラメータ（since/until）を使用しない
- 現在時点のスナップショットを返却するため、キャッシュは使用しない
- フロントエンドでの定期的なポーリングまたはWebSocketでの更新を想定

---

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

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

### 推奨読解順序

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

まず、会話のスコープを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | conversation.rb | `app/models/conversation.rb` | open, unattended, unassigned, pendingスコープの定義 |

**読解のコツ**: Conversationモデルには各種状態を表すスコープが定義されている。`open`はステータスがオープンの会話、`unattended`は応答がない会話、`unassigned`は担当者未割り当ての会話を指す。

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

APIコントローラーの処理フローを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | live_reports_controller.rb | `app/controllers/api/v2/accounts/live_reports_controller.rb` | conversation_metrics（7-14行目）、grouped_conversation_metrics（16-32行目） |

**主要処理フロー**:
1. **7-14行目**: `conversation_metrics`でopen, unattended, unassigned, pendingの各カウントを取得
2. **16-32行目**: `grouped_conversation_metrics`でグループ別にカウントを取得
3. **40-47行目**: `set_group_scope`でgroup_byパラメータを検証
4. **55-59行目**: `load_conversations`でスコープを設定

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

```
LiveReportsController#conversation_metrics
    │
    ├─ check_authorization
    │      └─ ReportPolicy#view?
    │
    ├─ load_conversations
    │      │
    │      └─ Current.account.conversations
    │             └─ where(team_id: team.id) if team
    │
    └─ render json
           │
           ├─ @conversations.open.count
           ├─ @conversations.open.unattended.count
           ├─ @conversations.open.unassigned.count
           └─ @conversations.pending.count

LiveReportsController#grouped_conversation_metrics
    │
    ├─ check_authorization
    │      └─ ReportPolicy#view?
    │
    ├─ load_conversations
    │
    ├─ set_group_scope
    │      └─ validate group_by in ['team_id', 'assignee_id']
    │
    └─ render json
           │
           ├─ @conversations.open.group(@group_scope).count
           ├─ @conversations.open.unattended.group(@group_scope).count
           └─ @conversations.open.unassigned.group(@group_scope).count
```

### データフロー図

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

team_id パラメータ ───────────▶ load_conversations ──────────────┐
                                │                                 │
                                └─▶ scope.where(team_id: ...)     │
                                                                  │
group_by パラメータ ──────────▶ set_group_scope ─────────────────┤
                                │                                 │
                                └─▶ validate ['team_id',          │
                                    'assignee_id']                │
                                                                  │
                              ┌───────────────────────────────────┘
                              ▼
Current.account.conversations
                              │
                              ├─▶ .open.count ──────────────▶ open
                              │
                              ├─▶ .open.unattended.count ──▶ unattended
                              │
                              ├─▶ .open.unassigned.count ──▶ unassigned
                              │
                              └─▶ .pending.count ──────────▶ pending
                                                              │
                                                              ▼
                                               { open, unattended,
                                                 unassigned, pending }
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| live_reports_controller.rb | `app/controllers/api/v2/accounts/live_reports_controller.rb` | コントローラー | APIエンドポイント |
| conversation.rb | `app/models/conversation.rb` | モデル | 会話モデル（スコープ定義） |
| team.rb | `app/models/team.rb` | モデル | チームモデル |
| report_policy.rb | `app/policies/report_policy.rb` | ポリシー | 認可制御 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（450-454行目） |
