# 通知設計書 6-sla_missed_first_response

## 概要

本ドキュメントは、Chatwootシステムにおける「sla_missed_first_response（SLA初回応答目標未達通知）」の設計仕様を記載したものである。SLAポリシーで定められた初回応答目標時間が未達となった際に、関係者に通知を送信する機能の詳細を定義する。

**注意**: 本機能はEnterprise Edition専用機能である。

### 本通知の処理概要

本通知は、SLA（Service Level Agreement）で定義された初回応答時間の目標を超過した場合に、関係者にアラートを送信し、迅速な対応を促すための機能である。

**業務上の目的・背景**：エンタープライズ向けのカスタマーサポートでは、SLAに基づいた応答時間の遵守が契約上の義務となることが多い。本通知により、初回応答目標時間を超過した会話を関係者が即座に把握し、優先的に対応することで、SLA違反の影響を最小限に抑えることができる。また、SLA遵守状況の可視化により、サポート品質の継続的な改善にも寄与する。

**通知の送信タイミング**：定期的に実行される`Sla::ProcessAppliedSlaJob`により、`Sla::EvaluateAppliedSlaService`が各会話のSLA状況を評価する。初回応答目標時間（first_response_time_threshold）を超過し、かつ初回応答がまだ行われていない場合、`SlaEvent`が作成され、その`after_create_commit`コールバックで通知が生成される。

**通知の受信者**：会話参加者、アカウント管理者、および会話担当者が受信者となる。重複を排除した上で全員に通知される。

**通知内容の概要**：SLA違反が発生した会話のID、受信トレイ名、適用されたSLAポリシー名、および会話へのリンクが含まれる。

**期待されるアクション**：通知を受け取った関係者は、即座に該当会話にアクセスし、顧客への初回応答を行うことが期待される。

## 通知種別

- メール通知
- プッシュ通知（ブラウザプッシュ、FCMモバイルプッシュ）
- アプリ内通知（リアルタイム）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（ActiveJob） |
| 優先度 | 中（defaultキュー） |
| リトライ | Sidekiqデフォルト設定に準拠 |

### 送信先決定ロジック

1. 会話参加者（conversation_participants）を取得
2. アカウント管理者（account.administrators）を追加
3. 会話担当者（conversation.assignee）を追加（存在する場合）
4. リストをuniqで重複排除
5. 各対象ユーザーに対して`NotificationBuilder`で通知を作成
6. コンタクトがブロックされている場合、通知は作成されない

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | 環境変数`MAILER_SENDER_EMAIL`（デフォルト: accounts@chatwoot.com） |
| 送信元名称 | Chatwoot（環境変数で設定可能） |
| 件名 | `Conversation [ID - {display_id}] missed SLA for first response` |
| 形式 | HTML（Liquidテンプレート） |

### 本文テンプレート

```html
<p>Hi {{user.available_name}},</p>

<p>
  Conversation #{{conversation.display_id}} in {{ inbox.name }}
  has missed the SLA for first response under policy {{ sla_policy.name }}.
</p>

<p>
<a href="{{action_url}}">Please address immediately.</a>
</p>
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | - |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| user.available_name | エージェントの表示名 | users.available_name | Yes |
| conversation.display_id | 会話の表示ID | conversations.display_id | Yes |
| inbox.name | 受信トレイ名 | inboxes.name | Yes |
| sla_policy.name | SLAポリシー名 | sla_policies.name | Yes |
| action_url | 会話詳細ページURL | 動的生成 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| バッチ | Sla::ProcessAppliedSlaJob | 初回応答目標時間を超過 かつ 初回応答未完了 | SLA評価ジョブ実行時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 初回応答が目標時間内に完了 | first_reply_created_at <= threshold |
| 目標時間内（まだ超過していない） | Time.zone.now < threshold |
| 既に同じSLAイベントが存在 | SlaEvent.exists?でチェック |
| コンタクトがブロック済み | contacts.blockedがtrueの場合 |
| メール通知時、既読の場合 | notification.read_atが設定済みの場合はメール送信しない |
| ユーザーがメール未確認 | user.confirmed_atがnilの場合はメール送信しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Sla::TriggerSlasForAccountsJob] --> B[Sla::ProcessAccountAppliedSlasJob]
    B --> C[Sla::ProcessAppliedSlaJob]
    C --> D[Sla::EvaluateAppliedSlaService]
    D --> E{first_response_time_threshold設定あり?}
    E -->|No| F[スキップ]
    E -->|Yes| G{初回応答が目標時間内?}
    G -->|Yes| F
    G -->|No| H{まだ目標時間内?}
    H -->|Yes| F
    H -->|No| I{既にSlaEvent存在?}
    I -->|Yes| F
    I -->|No| J[SlaEvent作成]
    J --> K[after_create_commit: create_notifications]
    K --> L[通知対象者取得]
    L --> M[notify_users.uniq.each]
    M --> N[NotificationBuilder実行]
    N --> O{次の対象者あり?}
    O -->|Yes| M
    O -->|No| P[完了]
    F --> P
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| applied_slas | 適用済みSLA情報 | バッチ対象 |
| sla_policies | SLAポリシー設定 | 閾値取得 |
| conversations | 会話情報取得 | primary_actor、first_reply_created_at |
| conversation_participants | 参加者情報取得 | 通知対象者 |
| account_users | アカウント管理者情報 | 通知対象者 |
| users | エージェント情報 | 通知先 |
| sla_events | SLAイベント履歴 | 重複チェック |
| notification_settings | 通知設定 | 購読状況確認 |
| contacts | コンタクト情報 | ブロック状態確認 |

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

#### sla_policies

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | SLAポリシー特定 | applied_sla経由 |
| name | ポリシー名 | テンプレート用 |
| first_response_time_threshold | 初回応答目標時間（秒） | 閾値計算用 |

#### conversations

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | 会話特定 | applied_sla経由 |
| created_at | 会話作成時刻 | 閾値計算の基準 |
| first_reply_created_at | 初回応答時刻 | 目標達成判定 |
| assignee_id | 担当者ID | 通知対象者 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| sla_events | INSERT | SLAイベントレコード作成 |
| notifications | INSERT | 通知レコード作成 |
| applied_slas | UPDATE | sla_status更新 |

#### sla_eventsテーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | event_type | 0 (frt) | 初回応答時間 |
| INSERT | account_id | 会話のaccount_id | FK |
| INSERT | conversation_id | 会話ID | FK |
| INSERT | inbox_id | 受信トレイID | FK |
| INSERT | sla_policy_id | SLAポリシーID | FK |
| INSERT | applied_sla_id | 適用済みSLA ID | FK |
| INSERT | meta | {} | メタデータ |

#### 送信ログテーブル（notifications）

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | notification_type | 6 (sla_missed_first_response) | ENUM値 |
| INSERT | account_id | 会話のaccount_id | FK |
| INSERT | user_id | 通知対象者ID | FK |
| INSERT | primary_actor_type | 'Conversation' | ポリモーフィック |
| INSERT | primary_actor_id | 会話ID | FK |
| INSERT | secondary_actor_type | 'SlaPolicy' | SLAポリシー |
| INSERT | secondary_actor_id | SLAポリシーID | FK |
| INSERT | last_activity_at | created_at | 作成時刻 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗（SMTP） | SMTPサーバー接続エラー | ログ出力、例外キャッチ |
| WebPush期限切れ | WebPush::ExpiredSubscription | サブスクリプション削除 |
| WebPush無効 | WebPush::InvalidSubscription | サブスクリプション削除 |
| WebPush認証エラー | WebPush::Unauthorized | サブスクリプション削除 |
| WebPushレート制限 | WebPush::TooManyRequests | ログ警告出力 |
| FCMエラー | レスポンスにerror含む | サブスクリプション削除 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | 一時的なネットワークエラー等 |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（システムレベル） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし。24時間送信可能。SLAバッチは定期的に実行される。

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

- メール送信はSMTP over TLS/SSLを推奨
- プッシュ通知はVAPID認証を使用
- FCM通知はFirebase認証を使用
- 通知内容に個人情報（会話ID、ポリシー名）が含まれるため、HTTPS必須
- ユーザー確認済み（confirmed_at）のチェックにより、未確認アカウントへの送信を防止
- Enterprise Edition機能のため、ライセンス確認が必要

## 備考

- Enterprise Edition専用機能
- SLAイベントは一度作成されると、同じタイプのイベントは重複作成されない
- SLA違反が発生すると、applied_slaのsla_statusが`active_with_misses`に更新される
- 会話が解決すると、SLA達成/未達成の最終判定が行われる
- secondary_actorにSLAポリシーが設定される
- push_message_bodyでは会話の最初のメッセージが表示される（117-118行目）

---

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

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

### 推奨読解順序

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

SLA関連のデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notification.rb | `app/models/notification.rb` | NOTIFICATION_TYPESの定義（44行目: sla_missed_first_response: 6） |
| 1-2 | sla_policy.rb | `enterprise/app/models/sla_policy.rb` | first_response_time_threshold定義 |
| 1-3 | sla_event.rb | `enterprise/app/models/sla_event.rb` | event_type enum（31行目: frt: 0） |
| 1-4 | applied_sla.rb | `enterprise/app/models/applied_sla.rb` | SLA適用状態管理 |

**読解のコツ**: SLAは3つの閾値（frt: first response time, nrt: next response time, rt: resolution time）を持つ。sla_eventsテーブルでSLA違反履歴を管理する。

#### Step 2: バッチ処理の流れを理解する

SLA評価のトリガーとなるバッチジョブを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | trigger_slas_for_accounts_job.rb | `enterprise/app/jobs/sla/trigger_slas_for_accounts_job.rb` | アカウント単位でジョブ発火 |
| 2-2 | process_account_applied_slas_job.rb | `enterprise/app/jobs/sla/process_account_applied_slas_job.rb` | 適用済みSLAを処理 |
| 2-3 | process_applied_sla_job.rb | `enterprise/app/jobs/sla/process_applied_sla_job.rb` | 個別SLA評価 |

#### Step 3: SLA評価サービスを理解する

実際の評価ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | evaluate_applied_sla_service.rb | `enterprise/app/services/sla/evaluate_applied_sla_service.rb` | 全体（1-107行目） |

**主要処理フロー**:
- **17行目**: check_first_response_time_thresholdを呼び出し
- **28-33行目**: 初回応答が目標時間内か判定
- **30行目**: first_reply_was_within_threshold?で初回応答時刻をチェック
- **31行目**: still_within_threshold?で目標時間内かチェック
- **33行目**: handle_missed_slaでSLAイベント作成

#### Step 4: SLAイベント作成と通知を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | evaluate_applied_sla_service.rb | `enterprise/app/services/sla/evaluate_applied_sla_service.rb` | handle_missed_sla（70-80行目）、create_sla_event（96-106行目） |
| 4-2 | sla_event.rb | `enterprise/app/models/sla_event.rb` | create_notifications（64-86行目） |

**主要処理フロー**:
- **72行目**: already_missed?で重複チェック
- **74行目**: create_sla_eventでSlaEvent作成
- **64-69行目**: 通知対象者（参加者+管理者+担当者）を収集
- **71-75行目**: notification_typeをevent_typeからマッピング
- **77-84行目**: NotificationBuilderで通知作成

#### Step 5: メール通知を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | conversation_notifications_mailer.rb | `enterprise/app/mailers/enterprise/agent_notifications/conversation_notifications_mailer.rb` | sla_missed_first_response（2-11行目） |
| 5-2 | sla_missed_first_response.liquid | `app/views/mailers/agent_notifications/conversation_notifications_mailer/sla_missed_first_response.liquid` | メールテンプレート全体 |

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

```
Sla::TriggerSlasForAccountsJob (定期実行)
    |
    +-- Account.find_each
           |
           +-- Sla::ProcessAccountAppliedSlasJob.perform_later
                  |
                  +-- AppliedSla.where(sla_status: [:active, :active_with_misses]).each
                         |
                         +-- Sla::ProcessAppliedSlaJob.perform_later
                                |
                                +-- Sla::EvaluateAppliedSlaService#perform
                                       |
                                       +-- check_sla_thresholds
                                              |
                                              +-- check_first_response_time_threshold
                                                     |
                                                     +-- first_reply_was_within_threshold? (判定)
                                                     |
                                                     +-- still_within_threshold? (判定)
                                                     |
                                                     +-- handle_missed_sla
                                                            |
                                                            +-- already_missed? (重複チェック)
                                                            |
                                                            +-- create_sla_event
                                                                   |
                                                                   +-- SlaEvent.create!
                                                                          |
                                                                          +-- after_create_commit: create_notifications
                                                                                 |
                                                                                 +-- notify_users収集
                                                                                 |
                                                                                 +-- notify_users.uniq.each
                                                                                        |
                                                                                        +-- NotificationBuilder#perform
```

### データフロー図

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

AppliedSla                 EvaluateAppliedSlaService            SlaEvent
(バッチ対象)      ───────▶  (SLA評価)                ───────▶    (DB保存)
       │                           │                              │
       │                           ▼                              │
       │              check_first_response_time_threshold         │
       │                           │                              │
       │              ┌────────────┴────────────┐                 │
       │              ▼                         ▼                 ▼
       │     first_reply_was_within_threshold?  still_within_threshold?  after_create_commit
       │     (初回応答判定)                     (時間内判定)             │
       │              │                         │                       │
       │              └────────────┬────────────┘                       ▼
       │                           ▼                           create_notifications
       │                    handle_missed_sla                          │
       │                    (SLA違反処理)                               ▼
       │                           │                           notify_users収集
       │                           ▼                           (参加者+管理者+担当者)
       │                    already_missed?                            │
       │                    (重複チェック)                             ▼
       │                           │                           NotificationBuilder
       ▼                           ▼                           (通知作成)
 sla_policy              create_sla_event                              │
 (閾値参照)              (イベント作成)                               ▼
                                                               Notification
                                                               (DB保存)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification.rb | `app/models/notification.rb` | ソース | 通知モデル・配信トリガー |
| sla_event.rb | `enterprise/app/models/sla_event.rb` | ソース | SLAイベントモデル（通知生成） |
| sla_policy.rb | `enterprise/app/models/sla_policy.rb` | ソース | SLAポリシーモデル |
| applied_sla.rb | `enterprise/app/models/applied_sla.rb` | ソース | 適用済みSLAモデル |
| evaluate_applied_sla_service.rb | `enterprise/app/services/sla/evaluate_applied_sla_service.rb` | ソース | SLA評価サービス（中核） |
| trigger_slas_for_accounts_job.rb | `enterprise/app/jobs/sla/trigger_slas_for_accounts_job.rb` | ソース | SLAトリガージョブ |
| process_account_applied_slas_job.rb | `enterprise/app/jobs/sla/process_account_applied_slas_job.rb` | ソース | アカウント単位処理ジョブ |
| process_applied_sla_job.rb | `enterprise/app/jobs/sla/process_applied_sla_job.rb` | ソース | 個別SLA処理ジョブ |
| notification_builder.rb | `app/builders/notification_builder.rb` | ソース | 通知ビルダー |
| conversation_notifications_mailer.rb | `enterprise/app/mailers/enterprise/agent_notifications/conversation_notifications_mailer.rb` | ソース | メーラー（Enterprise拡張） |
| sla_missed_first_response.liquid | `app/views/mailers/agent_notifications/conversation_notifications_mailer/sla_missed_first_response.liquid` | テンプレート | メールテンプレート |
