# 機能設計書 195-高度な検索

## 概要

本ドキュメントは、ChatwootにおけるSearchkickを使用した高度な検索機能の設計を記載したものである。この機能により、Elasticsearchを活用した高速な全文検索と高度な検索オプションが提供される。

### 本機能の処理概要

**業務上の目的・背景**：大規模なメッセージデータベースにおいて、PostgreSQLのLIKE検索やGINインデックスでは十分なパフォーマンスが得られない場合がある。Searchkick（Elasticsearch）を使用することで、より高速で柔軟な全文検索を実現し、大量のメッセージから必要な情報を瞬時に見つけ出すことができる。

**機能の利用シーン**：エージェントが過去のメッセージを検索する際、特に大量のデータを持つアカウントでは高度な検索機能が有効。フレーズ検索、部分一致検索、時間範囲検索など、より詳細な検索条件を指定できる。

**主要な処理内容**：
1. Messageモデルへのsearchkick統合
2. 非同期インデックス更新（callbacks: false）
3. 検索データの正規化とインデックス作成
4. Elasticsearchクエリの実行
5. SQL検索へのフォールバック（接続失敗時）

**関連システム・外部連携**：Elasticsearch/Searchkickとの連携。

**権限による制御**：advanced_searchフィーチャーフラグが有効なアカウントのみ利用可能。Chatwoot CloudではadvancedSearchIndexingフィーチャーフラグも必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 27 | 検索画面 | 主画面 | 高度な検索機能の利用 |

## 機能種別

検索・インデックス管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| q | String | Yes | 検索クエリ文字列 | - |
| page | Integer | No | ページ番号 | デフォルト: 1 |
| since | Integer | No | 検索開始日時（Unix timestamp） | 90日以内に制限 |
| until | Integer | No | 検索終了日時（Unix timestamp） | 90日以内に制限 |
| from | String | No | 送信者フィルタ | 形式: "contact:ID" or "agent:ID" |
| inbox_id | Integer | No | インボックスIDフィルタ | アクセス権限チェック |

### 入力データソース

- 検索API（SearchController）経由

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| messages | Array | 検索にマッチしたメッセージ一覧 |

### 出力先

- 検索APIのレスポンスとして返却

## 処理フロー

### 処理シーケンス

```
1. メッセージ作成/更新
   └─ after_commit コールバック発火
2. インデックス更新判定
   ├─ ChatwootApp.advanced_search_allowed? チェック
   ├─ メッセージタイプチェック（incoming/outgoing）
   └─ Chatwoot Cloud時はadvanced_search_indexingフラグチェック
3. 非同期インデックス更新
   └─ reindex(mode: :async)
4. 検索実行時
   ├─ should_run_advanced_search? 判定
   ├─ Elasticsearchクエリ実行
   └─ 失敗時はSQL検索にフォールバック
```

### フローチャート

```mermaid
flowchart TD
    A[メッセージ作成/更新] --> B{should_index?}
    B -->|No| C[インデックス更新なし]
    B -->|Yes| D[reindex mode: async]
    D --> E[Elasticsearch更新]

    F[検索リクエスト] --> G{advanced_search有効?}
    G -->|No| H[GIN/ILIKE検索]
    G -->|Yes| I[Elasticsearch検索]
    I --> J{検索成功?}
    J -->|Yes| K[結果返却]
    J -->|No| L[SQL検索フォールバック]
    L --> K
    H --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-195-01 | インデックス対象制限 | incoming/outgoingメッセージのみインデックス対象 | 常時 |
| BR-195-02 | Cloud版追加制限 | Chatwoot Cloudではadvanced_search_indexingフラグも必要 | Chatwoot Cloud環境 |
| BR-195-03 | フォールバック動作 | Elasticsearch接続失敗時はSQL検索にフォールバック | Elasticsearch障害時 |
| BR-195-04 | 非同期インデックス | インデックス更新は非同期で実行 | 常時 |

### 計算ロジック

なし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| インデックス更新 | Elasticsearch | INDEX | メッセージデータのインデックス作成/更新 |
| 検索 | Elasticsearch | SEARCH | 全文検索クエリ実行 |

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

#### Elasticsearchインデックス

| 操作 | 項目（フィールド） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INDEX | search_data | Messages::SearchDataPresenter出力 | 検索用データ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Faraday::ConnectionFailed | Elasticsearch接続失敗 | SQL検索にフォールバック |
| - | Searchkick::Error | Searchkickエラー | SQL検索にフォールバック |
| - | Elasticsearch::Transport::Transport::Error | Elasticsearchトランスポートエラー | SQL検索にフォールバック |

### リトライ仕様

- Elasticsearch接続失敗時は即座にSQL検索にフォールバック（リトライなし）

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

- インデックス更新はafter_commitで非同期実行
- 検索は読み取り専用

## パフォーマンス要件

- 非同期インデックス更新によりアプリケーションレスポンスへの影響を最小化
- Elasticsearchによる高速全文検索

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

- advanced_searchフィーチャーフラグによるアクセス制御
- インボックスアクセス権限に基づくフィルタリング

## 備考

- `ChatwootApp.advanced_search_allowed?`でグローバルな有効/無効を制御
- アカウント単位で`advanced_search`フィーチャーフラグを設定可能
- Chatwoot Cloudでは追加で`advanced_search_indexing`フラグが必要

---

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

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

### 推奨読解順序

#### Step 1: Messageモデルのsearchkick設定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | message.rb | `app/models/message.rb` | searchkick統合設定 |

**主要処理フロー**:
1. **42行目**: `searchkick callbacks: false if ChatwootApp.advanced_search_allowed?` - 条件付きsearchkick有効化
2. **138行目**: `after_commit :reindex_for_search, if: :should_index?, on: [:create, :update]` - インデックス更新トリガー
3. **237-251行目**: `should_index?`メソッド - インデックス更新判定ロジック
4. **253-255行目**: `search_data`メソッド - 検索データの生成
5. **422-424行目**: `reindex_for_search`メソッド - 非同期インデックス更新

**読解のコツ**: `searchkick callbacks: false`は、デフォルトのコールバックを無効化し、カスタムのインデックス更新ロジックを使用することを示す。これにより、不要なインデックス更新を避け、パフォーマンスを最適化している。

#### Step 2: インデックス更新判定ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | message.rb | `app/models/message.rb` | should_index?メソッド（237-251行目） |

**should_index?の判定フロー**:
1. `ChatwootApp.advanced_search_allowed?`がfalseならfalse
2. `incoming?`または`outgoing?`でなければfalse
3. Chatwoot Cloudかつ`advanced_search_indexing`フラグがなければfalse
4. それ以外はtrue

#### Step 3: SearchServiceの高度な検索処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | search_service.rb | `app/services/search_service.rb` | advanced_search関連処理 |

**主要処理フロー**:
1. **59-64行目**: `advanced_search_with_fallback` - フォールバック付き検索
2. **66-68行目**: `should_run_advanced_search?` - advanced_search有効判定
3. **70行目**: `advanced_search` - Elasticsearch検索（空メソッド、拡張ポイント）

**読解のコツ**: `advanced_search`メソッドは空のまま定義されており、`prepend_mod_with('SearchService')`によりEnterprise版で実装が追加される設計パターン。

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

```
Message (作成/更新)
    │
    └─ after_commit :reindex_for_search
           │
           ├─ should_index?
           │      ├─ ChatwootApp.advanced_search_allowed?
           │      ├─ incoming? || outgoing?
           │      └─ ChatwootApp.chatwoot_cloud? チェック
           │
           └─ reindex(mode: :async)
                  └─ Searchkick async job
                         └─ Elasticsearch index update

SearchService#filter_messages
    │
    └─ should_run_advanced_search?
           │
           └─ advanced_search_with_fallback
                  │
                  ├─ advanced_search (Elasticsearch)
                  │      └─ Message.search(...)
                  │
                  └─ rescue → filter_messages_with_gin/like
```

### データフロー図

```
[インデックス更新フロー]

Message作成/更新
        │
        ▼
  should_index?
        │
        ▼
  search_data生成
        │
        ▼
  Searchkick async
        │
        ▼
  Elasticsearch

[検索フロー]

検索クエリ
        │
        ▼
SearchService
        │
        ▼
advanced_search?
        │
   ┌────┴────┐
   ▼         ▼
  Yes        No
   │         │
   ▼         ▼
Elasticsearch  GIN/ILIKE
   │         │
   │ (失敗)   │
   └─────┬───┘
         │
         ▼
     検索結果
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| message.rb | `app/models/message.rb` | ソース | searchkick統合、インデックス判定 |
| search_service.rb | `app/services/search_service.rb` | ソース | advanced_search処理 |
| search_data_presenter.rb | `app/services/messages/search_data_presenter.rb` | ソース | 検索データ生成（存在する場合） |
| chatwoot_app.rb | `lib/chatwoot_app.rb` | ソース | advanced_search_allowed?定義 |
