# 帳票設計書 3-ラベルレポート（CSV）

## 概要

本ドキュメントは、Chatwootシステムにおけるラベル別の会話統計レポート（CSV形式）の設計仕様を定義する。

### 本帳票の処理概要

ラベルレポートは、指定期間内における各ラベルごとの会話統計を集計し、問い合わせのカテゴリ分析のための統計データをCSV形式で出力する帳票である。

**業務上の目的・背景**：カスタマーサポート業務では、会話にラベル（タグ）を付与して問い合わせの種類や緊急度を分類している。ラベル別の統計を把握することで、どのような種類の問い合わせが多いか、特定のラベルが付いた会話の解決に時間がかかっているかなどを分析できる。本帳票は、サポートチームが問い合わせの傾向を把握し、FAQ改善や製品改善のインサイトを得るための基礎資料として活用される。

**帳票の利用シーン**：問い合わせカテゴリ別のトレンド分析、製品フィードバックの集計、FAQの優先更新項目の特定、サポートナレッジベースの改善計画策定時に利用される。

**主要な出力内容**：
1. ラベル名（問い合わせカテゴリの識別名称）
2. 会話数（各ラベルが付与された会話の総数）
3. 平均初回応答時間（顧客からの問い合わせに対する最初の返信までの平均時間）
4. 平均解決時間（会話開始から解決完了までの平均所要時間）
5. 平均返信時間（各返信までの平均時間）
6. 解決数（解決済みとなった会話の総数）

**帳票の出力タイミング**：管理者がレポート画面から任意のタイミングでダウンロードボタンをクリックした際にオンデマンドで生成・出力される。

**帳票の利用者**：アカウント管理者、プロダクトマネージャー、カスタマーサクセスチーム

## 帳票種別

集計表

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | レポート画面（ラベル） | `/app/accounts/:account_id/reports/labels` | 「Download」ボタンクリック |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | CSV |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | `labels_report.csv` |
| 出力方法 | ダウンロード |
| 文字コード | UTF-8 |

### CSV固有設定

| 項目 | 内容 |
|-----|------|
| 区切り文字 | カンマ（,） |
| 囲み文字 | ダブルクォート（"）- CSVSafeライブラリによる自動エスケープ |
| ヘッダー行 | あり（1行目：期間情報、2行目：カラムヘッダー） |
| BOM | なし |

## 帳票レイアウト

### レイアウト概要

CSVファイルは以下の構成で出力される。1行目にレポート対象期間、2行目にカラムヘッダー、3行目以降にラベル別の集計データが出力される。

```
┌─────────────────────────────────────┐
│          期間情報（1行目）            │
├─────────────────────────────────────┤
│        カラムヘッダー（2行目）         │
├─────────────────────────────────────┤
│           ラベルデータ                │
│           （3行目以降）               │
│          各ラベル1行ずつ              │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | レポート期間 | 集計対象期間 | `params[:since]`, `params[:until]` | "Reporting period YYYY-MM-DD to YYYY-MM-DD" |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | Label | ラベル名 | `labels.title` | 文字列 | - |
| 2 | No. of conversations | 会話数 | `taggings.count` | 整数 | - |
| 3 | Avg first response time | 平均初回応答時間 | `reporting_events.value`（first_response） | "X days Y hours" / "N/A" | - |
| 4 | Avg resolution time | 平均解決時間 | `reporting_events.value`（conversation_resolved） | "X days Y hours" / "N/A" | - |
| 5 | Avg reply time | 平均返信時間 | `reporting_events.value`（reply_time） | "X days Y hours" / "N/A" | - |
| 6 | Resolution Count | 解決した会話数 | `reporting_events.count`（conversation_resolved） | 整数 | - |

### フッター部

フッターなし

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| since | 集計開始日（UNIXタイムスタンプ） | Yes |
| until | 集計終了日（UNIXタイムスタンプ） | Yes |
| business_hours | 営業時間のみで計算するかどうか | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | labels の登録順 | - |

### 改ページ条件

N/A（CSV形式のため改ページなし）

## データベース参照仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| labels | ラベル情報の取得 | `labels.account_id = Current.account.id` |
| taggings | ラベルと会話の関連付け | `taggings.taggable_type = 'Conversation' AND taggings.context = 'labels'` |
| tags | タグ名の取得（ActsAsTaggableOn） | `taggings.tag_id = tags.id` |
| conversations | 会話との結合 | `taggings.taggable_id = conversations.id` |
| reporting_events | 統計値の集計 | `reporting_events.conversation_id = conversations.id` |

### テーブル別参照項目詳細

#### labels

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| title | Label | - | - |

#### taggings / tags

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| COUNT(*) | No. of conversations | `taggable_type = 'Conversation' AND context = 'labels'` | tags.name でグループ化 |

#### reporting_events

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| value / value_in_business_hours | Avg first response time | `name = 'first_response'` | tags.nameでグループ化し平均値を算出 |
| value / value_in_business_hours | Avg resolution time | `name = 'conversation_resolved'` | tags.nameでグループ化し平均値を算出 |
| value / value_in_business_hours | Avg reply time | `name = 'reply_time'` | tags.nameでグループ化し平均値を算出 |
| id | Resolution Count | `name = 'conversation_resolved'` | tags.nameでグループ化しCOUNT |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| Avg first response time | AVG(reporting_events.value WHERE name='first_response') | 整数（秒） | TimeFormatPresenterで人間可読形式に変換 |
| Avg resolution time | AVG(reporting_events.value WHERE name='conversation_resolved') | 整数（秒） | TimeFormatPresenterで人間可読形式に変換 |
| Avg reply time | AVG(reporting_events.value WHERE name='reply_time') | 整数（秒） | TimeFormatPresenterで人間可読形式に変換 |
| Resolution Count | COUNT(reporting_events WHERE name='conversation_resolved') | 整数 | - |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[ダウンロード要求] --> B[check_authorization]
    B --> C[generate_labels_report]
    C --> D[LabelSummaryBuilder.build]
    D --> E[collect_report_data]
    E --> F[fetch_conversation_counts]
    F --> G[fetch_resolved_counts]
    G --> H[fetch_metrics]
    H --> I[build_label_report - ラベル別データ整形]
    I --> J[generate_readable_report_metrics - 時間フォーマット]
    J --> K[generate_csv - CSVレスポンス生成]
    K --> L[ERBテンプレートでCSV出力]
    L --> M[ファイルダウンロード]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 認可エラー | レポート閲覧権限がない場合 | HTTP 403 | 管理者権限を持つユーザーでログイン |
| パラメータ不足 | since/untilパラメータが不正な場合 | パラメータによる | 正しい期間パラメータを指定 |
| データなし | ラベルが存在しない場合 | 空の結果 | ラベルを作成してから実行 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | ラベル数：数十〜数百個 |
| 目標出力時間 | 数秒以内 |
| 同時出力数上限 | 特に制限なし |

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

- レポート閲覧は `report:view?` ポリシーで認可チェックを実施
- ラベル情報はアカウント内の権限を持つユーザーのみアクセス可能

## 備考

- 時間の表示形式は `Reports::TimeFormatPresenter` により「X days Y hours」形式に変換される
- 値が存在しない場合は「N/A」と表示される
- business_hoursパラメータがtrueの場合、`value_in_business_hours`カラムの値が使用される
- ラベルはActsAsTaggableOnジェムを使用してタギング機能を実装している
- 解決数は会話の現在のステータスではなく、reporting_eventsテーブルの解決イベントをカウントする

---

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

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

### 推奨読解順序

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

まず、帳票出力に使用されるデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | en.yml | `config/locales/en.yml` | reports.label_csvセクションでCSVヘッダーの翻訳キーを確認（行161-167） |
| 1-2 | labels テーブル定義 | `db/schema.rb` | labelsテーブルのカラム定義（title等） |
| 1-3 | taggings テーブル定義 | `db/schema.rb` | ActsAsTaggableOnのポリモーフィック関連を理解 |

**読解のコツ**: ラベルはActsAsTaggableOnジェムで実装されており、taggings中間テーブルを介してconversationsと多対多の関係にある。

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

処理の起点となるコントローラーを特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | reports_controller.rb | `app/controllers/api/v2/accounts/reports_controller.rb` | labelsアクション（行31-34）がエントリーポイント |

**主要処理フロー**:
1. **行5**: `check_authorization` で権限チェック
2. **行31-34**: `labels` アクションで `generate_labels_report` を呼び出し
3. **行33**: `generate_csv` でCSVレスポンスを生成

#### Step 3: Helperメソッドを理解する

レポートデータ生成のヘルパーメソッドを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | reports_helper.rb | `app/helpers/api/v2/accounts/reports_helper.rb` | `generate_labels_report` メソッド（行38-47）でデータ生成ロジックを確認 |

**主要処理フロー**:
- **行39-42**: `LabelSummaryBuilder` でレポートデータを構築
- **行44-46**: 各ラベルのデータを整形して配列に変換

#### Step 4: ビルダークラスを理解する

集計処理の本体であるBuilderクラスを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | label_summary_builder.rb | `app/builders/v2/reports/label_summary_builder.rb` | ラベル固有の処理（全113行） |

**主要処理フロー**:
- **行15-21**: buildメソッドでラベル一覧を取得し、各ラベルのレポートを構築
- **行25-35**: collect_report_dataで会話数、解決数、各種メトリクスを一括取得
- **行61-64**: fetch_conversation_countsでtaggingsテーブル経由で会話数をカウント
- **行65-80**: fetch_resolved_countsでreporting_eventsから解決イベントをカウント
- **行96-111**: fetch_metricsでreporting_eventsから平均値を取得

#### Step 5: CSVテンプレートを理解する

最終的なCSV出力のテンプレートを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | labels.csv.erb | `app/views/api/v2/accounts/reports/labels.csv.erb` | CSVの行構造と出力形式（全16行） |

**主要処理フロー**:
- **行1**: 期間情報を1行目に出力
- **行3-10**: ヘッダー行の定義（6カラム）
- **行12**: ヘッダー行をCSV出力
- **行13-15**: @report_dataの各行をCSV出力

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

```
Api::V2::Accounts::ReportsController#labels
    │
    ├─ check_authorization (before_action)
    │      └─ ReportPolicy#view?
    │
    ├─ Api::V2::Accounts::ReportsHelper#generate_labels_report
    │      │
    │      ├─ V2::Reports::LabelSummaryBuilder#build
    │      │      │
    │      │      ├─ #collect_report_data
    │      │      │      ├─ #fetch_conversation_counts
    │      │      │      ├─ #fetch_resolved_counts
    │      │      │      └─ #fetch_metrics (3回: first_response, conversation_resolved, reply_time)
    │      │      │
    │      │      └─ #build_label_report (各ラベル分)
    │      │
    │      └─ Reports::TimeFormatPresenter#format (各メトリクス分)
    │
    └─ #generate_csv
           └─ render 'api/v2/accounts/reports/labels'
                  └─ labels.csv.erb
```

### データフロー図

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

params[:since]    ─────┐
params[:until]    ─────┼──▶  LabelSummaryBuilder           ──▶   @report_data
params[:business_hours]┘           │
                                   ├─▶ taggings JOIN conversations (COUNT)
                                   ├─▶ reporting_events JOIN taggings (解決COUNT)
                                   └─▶ reporting_events JOIN taggings (AVG)
                                              │
                                              ▼
                              TimeFormatPresenter#format
                                              │
                                              ▼
                                     labels.csv.erb
                                              │
                                              ▼
                                     labels_report.csv
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| reports_controller.rb | `app/controllers/api/v2/accounts/reports_controller.rb` | コントローラー | エントリーポイント、CSV生成制御 |
| reports_helper.rb | `app/helpers/api/v2/accounts/reports_helper.rb` | ヘルパー | レポートデータ生成の補助 |
| label_summary_builder.rb | `app/builders/v2/reports/label_summary_builder.rb` | ビルダー | ラベル別集計処理 |
| time_format_presenter.rb | `app/presenters/reports/time_format_presenter.rb` | プレゼンター | 時間表示形式変換 |
| labels.csv.erb | `app/views/api/v2/accounts/reports/labels.csv.erb` | テンプレート | CSV出力フォーマット定義 |
| en.yml | `config/locales/en.yml` | 設定 | CSVヘッダーの翻訳定義 |
| report_policy.rb | `app/policies/report_policy.rb` | ポリシー | レポート閲覧権限制御 |
