# 通知設計書 7-sla_missed_next_response

## 概要

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

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

### 本通知の処理概要

本通知は、SLA（Service Level Agreement）で定義された次回応答時間の目標を超過した場合に、関係者にアラートを送信し、迅速な対応を促すための機能である。初回応答後の継続的なやり取りにおいて、顧客からの問い合わせに対する応答時間を管理する。

**業務上の目的・背景**：エンタープライズ向けのカスタマーサポートでは、初回応答だけでなく、継続的なやり取りにおいても一定の応答時間を維持することが求められる。本通知により、顧客が応答を待っている状態が長時間続いた場合に関係者が即座に把握し、優先的に対応することで、顧客満足度を維持し、SLA違反の累積を防止する。

**通知の送信タイミング**：定期的に実行される`Sla::ProcessAppliedSlaJob`により、`Sla::EvaluateAppliedSlaService`が各会話のSLA状況を評価する。次回応答目標時間（next_response_time_threshold）を超過し、かつ顧客が応答を待っている状態（waiting_since）の場合、`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 next response` |
| 形式 | HTML（Liquidテンプレート） |

### 本文テンプレート

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

<p>
  Conversation #{{conversation.display_id}} in {{ inbox.name }}
  has missed the SLA for next 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.blank?の場合（frt対象） |
| 顧客が応答待ちでない | waiting_since.blank?の場合 |
| 目標時間内（まだ超過していない） | Time.zone.now < threshold |
| 既に同じSLAイベントが存在 | SlaEvent.exists?でチェック（メッセージIDも考慮） |
| コンタクトがブロック済み | contacts.blockedがtrueの場合 |
| メール通知時、既読の場合 | notification.read_atが設定済みの場合はメール送信しない |
| ユーザーがメール未確認 | user.confirmed_atがnilの場合はメール送信しない |

## 処理フロー

### 送信フロー

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

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

### 参照テーブル一覧

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

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

#### sla_policies

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

#### conversations

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | 会話特定 | applied_sla経由 |
| first_reply_created_at | 初回応答時刻 | 次回応答対象判定 |
| waiting_since | 顧客待機開始時刻 | 閾値計算の基準 |
| assignee_id | 担当者ID | 通知対象者 |

### 更新テーブル一覧

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

#### sla_eventsテーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | event_type | 1 (nrt) | 次回応答時間 |
| 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 | { message_id: 最終incomingメッセージID } | 重複チェック用 |

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

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | notification_type | 7 (sla_missed_next_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専用機能
- 次回応答時間は`waiting_since`を基準に計算される
- `waiting_since`は顧客からのメッセージ受信時に設定され、エージェントが応答すると解除される
- 初回応答が完了していない場合は、次回応答チェックは行われない（初回応答チェックのみ）
- SLAイベントのメタデータに最終incomingメッセージIDが含まれ、同じメッセージに対する重複通知を防止
- push_message_bodyでは最後のincoming/outgoingメッセージが表示される（121-122行目）

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notification.rb | `app/models/notification.rb` | NOTIFICATION_TYPESの定義（45行目: sla_missed_next_response: 7） |
| 1-2 | sla_policy.rb | `enterprise/app/models/sla_policy.rb` | next_response_time_threshold定義 |
| 1-3 | sla_event.rb | `enterprise/app/models/sla_event.rb` | event_type enum（31行目: nrt: 1） |

**読解のコツ**: nrt（next response time）は、顧客が応答を待っている状態でのみチェックされる。waiting_sinceカラムとの連携が重要。

#### Step 2: 次回応答時間の計算ロジックを理解する

評価サービスの次回応答チェックロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | evaluate_applied_sla_service.rb | `enterprise/app/services/sla/evaluate_applied_sla_service.rb` | check_next_response_time_threshold（40-49行目） |

**主要処理フロー**:
- **42行目**: first_reply_created_at.blank?で初回応答完了をチェック
- **44行目**: waiting_since.blank?で顧客待機中をチェック
- **46行目**: waiting_since + thresholdで閾値を計算
- **49行目**: handle_missed_slaでSLAイベント作成

#### Step 3: 重複チェックロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | evaluate_applied_sla_service.rb | `enterprise/app/services/sla/evaluate_applied_sla_service.rb` | handle_missed_sla（70-80行目）、get_last_message_id（52-55行目） |

**主要処理フロー**:
- **71行目**: nrtの場合はメッセージIDをメタデータに含める
- **72行目**: already_missed?でメタデータを含めて重複チェック
- **53-54行目**: 最後のincomingメッセージIDを取得

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | sla_event.rb | `enterprise/app/models/sla_event.rb` | create_notifications（64-86行目）、notification_typeマッピング（71-75行目） |

**主要処理フロー**:
- **73行目**: nrtは'sla_missed_next_response'にマッピング

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

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

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

```
Sla::ProcessAppliedSlaJob
    |
    +-- Sla::EvaluateAppliedSlaService#perform
           |
           +-- check_sla_thresholds
                  |
                  +-- check_next_response_time_threshold
                         |
                         +-- first_reply_created_at.blank? (初回応答完了チェック)
                         |      └─ return if blank (frt対象のため)
                         |
                         +-- waiting_since.blank? (顧客待機チェック)
                         |      └─ return if blank (待機中でないため)
                         |
                         +-- threshold = waiting_since + nrt
                         |
                         +-- still_within_threshold? (時間内チェック)
                         |
                         +-- handle_missed_sla('nrt')
                                |
                                +-- get_last_message_id (メッセージID取得)
                                |
                                +-- already_missed?(type, { message_id: ... })
                                |
                                +-- create_sla_event
                                       |
                                       +-- SlaEvent.create!(meta: { message_id: ... })
                                              |
                                              +-- after_create_commit: create_notifications
                                                     |
                                                     +-- notify_users.uniq.each
                                                            |
                                                            +-- NotificationBuilder#perform
```

### データフロー図

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

AppliedSla                 EvaluateAppliedSlaService            SlaEvent
(バッチ対象)      ───────▶  (SLA評価)                ───────▶    (DB保存)
       │                           │                              │
       │                           ▼                              │
       │              check_next_response_time_threshold          │
       │                           │                              │
       │              ┌────────────┴────────────┐                 │
       │              ▼                         ▼                 │
       │     first_reply_created_at?     waiting_since?           │
       │     (初回応答完了チェック)        (顧客待機チェック)       │
       │              │                         │                 │
       │              └────────────┬────────────┘                 │
       │                           ▼                              │
       │              threshold = waiting_since + nrt             │
       │                           │                              ▼
       │                           ▼                     after_create_commit
       │                   still_within_threshold?               │
       │                   (時間内判定)                          │
       │                           │                              ▼
       │                           ▼                     create_notifications
       │                   handle_missed_sla('nrt')              │
       │                           │                              ▼
       │                           ▼                     notify_users収集
       │                   get_last_message_id                    │
       │                   (重複チェック用)                        ▼
       │                           │                     NotificationBuilder
       ▼                           ▼                     (通知作成)
 sla_policy              create_sla_event                        │
 (閾値参照)              (meta: {message_id})                    ▼
                                                         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評価サービス（中核） |
| 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_next_response.liquid | `app/views/mailers/agent_notifications/conversation_notifications_mailer/sla_missed_next_response.liquid` | テンプレート | メールテンプレート |
| conversation.rb | `app/models/conversation.rb` | ソース | 会話モデル（waiting_since） |
