# 機能設計書 87-AnnotatedTextマッパー

## 概要

本ドキュメントは、テキストにアノテーション（注釈）を付与するAnnotated Textフィールドマッパープラグインの設計を記述する。

### 本機能の処理概要

Annotated Textマッパーは、マークダウン風の構文（`[テキスト](アノテーション値)`）で記述されたテキストを解析し、本文テキストとアノテーショントークンを組み合わせたインデックスを作成するフィールドマッパーである。これにより、テキスト内のエンティティ（人名、地名等）を検索時にアノテーションとして利用できる。

**業務上の目的・背景**：自然言語処理（NLP）や知識グラフの結果をインデックスに反映し、エンティティベースの検索を可能にする。例えば、テキスト内の人名をエンティティIDに紐付けて検索することで、表記揺れがあっても正確に検索できる。

**機能の利用シーン**：NLPパイプラインの出力をインデックスに反映する際、エンティティリンキングの結果をアノテーションとして保存する場合、アノテーション付きテキストのハイライト検索を行う場合。

**主要な処理内容**：
1. マークダウン風構文のパース（`[visible text](annotation_value)`形式）
2. アノテーション除去後のプレーンテキストの抽出
3. AnnotationsInjectorによるトークンストリームへのアノテーショントークンの注入
4. AnnotatedTextHighlighterによるハイライト対応

**関連システム・外部連携**：Luceneのアナライザパイプライン、ハイライト機能。

**権限による制御**：標準のインデックス操作権限に従う。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | マッピング定義を通じて間接的に使用される |

## 機能種別

データ変換 / フィールドマッピング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| store | boolean | No | フィールド値を保存するか | デフォルトfalse |
| index_options | String | No | インデックスオプション | - |
| norms | boolean | No | 正規化係数を保存するか | デフォルトtrue |
| term_vectors | String | No | ターム・ベクトルの設定 | - |
| analyzer | String | No | インデックスアナライザ | - |
| search_analyzer | String | No | 検索アナライザ | - |
| similarity | String | No | 類似度アルゴリズム | - |
| position_increment_gap | int | No | 位置増分ギャップ | -1以外で負数不可 |

### 入力データソース

マークダウン風のアノテーション付きテキスト。例: `"New mayor is [John Smith](John%20Smith)"`

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| プレーンテキスト | String | アノテーション除去後のテキスト |
| アノテーショントークン | Token[] | type="annotation"のトークン群 |

### 出力先

Luceneインデックスのtextフィールドとして格納される。アノテーショントークンはプレーンテキストトークンと同じ位置に注入される。

## 処理フロー

### 処理シーケンス

```
1. テキスト入力
   └─ マークダウン風テキスト "[visible](annotation)" を受信
2. AnnotatedText.parse()
   └─ 正規表現でマークアップを解析し、プレーンテキストとAnnotationTokenリストを生成
3. AnnotationAnalyzerWrapper
   └─ 元のアナライザをラップし、AnnotationsInjectorをトークンストリームに挿入
4. AnnotationsInjector.incrementToken()
   └─ プレーンテキストのトークンストリームにアノテーショントークンを適切な位置に注入
5. Luceneインデックスへの格納
   └─ プレーンテキストトークンとアノテーショントークンが混在したインデックスを作成
```

### フローチャート

```mermaid
flowchart TD
    A["入力: [John Smith](person_123)"] --> B[AnnotatedText.parse]
    B --> C[プレーンテキスト: John Smith]
    B --> D["AnnotationToken(0,10,person_123)"]
    C --> E[AnnotationAnalyzerWrapper]
    E --> F[元アナライザでトークン化]
    F --> G[AnnotationsInjector]
    D --> G
    G --> H["token: person_123 (pos=0, type=annotation)"]
    G --> I["token: john (pos=0)"]
    G --> J["token: smith (pos=1)"]
    H --> K[Luceneインデックス格納]
    I --> K
    J --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | マークアップ形式 | `[visible text](value)` 形式。key=value形式はサポートされず例外 | テキスト解析時 |
| BR-02 | URLエンコード | アノテーション値はURLデコードされる | パース時 |
| BR-03 | 位置注入 | アノテーショントークンはテキストトークンと同じ開始オフセット位置に注入される | incrementToken時 |
| BR-04 | type属性 | アノテーショントークンのtype属性は"annotation"に設定される | emitAnnotation時 |
| BR-05 | インデックス必須 | annotated_textフィールドはインデックスが必須（NONE不可） | Builder.build時(173-175行目) |

### 計算ロジック

AnnotationsInjector.incrementToken()では、テキストトークンの開始オフセットがアノテーションのオフセットに達した場合、アノテーショントークンを先にemitし、後続のテキストトークンは位置増分0でバッファに保存される。positionLengthにはアノテーションがカバーするテキストトークン数が設定される。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| インデックス | Lucene転置インデックス | INSERT | アノテーション付きテキストをトークン化して格納 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | OpenSearchParseException | key=value形式のアノテーション | key=value形式を使用しない |
| - | IllegalArgumentException | indexOptionsがNONE | インデックスを有効にする |
| - | MapperParsingException | position_increment_gapが負数 | 0以上の値を指定 |

### リトライ仕様

該当なし。

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

Luceneインデックスのトランザクション管理に従う。

## パフォーマンス要件

AnnotationsInjectorによるトークン注入処理はインデックス時のみ発生し、検索時のオーバーヘッドはない。ハイライト時はAnnotatedHighlighterAnalyzerが使用され、マークアップ形式からのトークン再生成が必要。

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

標準のインデックス操作権限に従う。アノテーション値はURLデコードされるため、不正なエンコーディングに対するバリデーションが行われる。

## 備考

本プラグインはTextFieldMapperのコードを多くコピーして実装されている（AnnotatedTextFieldMapper.javaのコメント参照）。将来的にはTextFieldMapperのサブクラス化が検討されている。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | AnnotatedTextFieldMapper.java (AnnotatedText内部クラス) | `plugins/mapper-annotated-text/src/main/java/org/opensearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java` | AnnotatedText（193-317行目）: textPlusMarkup, textMinusMarkup, annotations。AnnotationToken（252-296行目）: offset, endOffset, value |

**読解のコツ**: markdownPattern（200行目）の正規表現`\\[([^]\\[]*)]\\(([^)(]*)\\)`がマークアップの解析パターン。parse()（202-244行目）が中核のパースロジック。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AnnotatedTextPlugin.java | `plugins/mapper-annotated-text/src/main/java/org/opensearch/plugin/mapper/AnnotatedTextPlugin.java` | getMappers()でAnnotatedTextFieldMapper.PARSERを登録（50行目）、getHighlighters()でAnnotatedTextHighlighterを登録（55行目） |

#### Step 3: トークン注入を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AnnotatedTextFieldMapper.java (AnnotationsInjector内部クラス) | `plugins/mapper-annotated-text/src/main/java/org/opensearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java` | AnnotationsInjector（400-536行目）: incrementToken()（456-492行目）がトークン注入のメインロジック。emitAnnotation()（499-534行目）がアノテーション発行処理 |

**主要処理フロー**:
- **460行目**: テキストトークンのオフセットがアノテーションのオフセット以上かチェック
- **467-468行目**: テキストトークンの位置増分を0にしてバッファに保存
- **470-482行目**: アノテーション範囲内の全テキストトークンをバッファリング
- **483行目**: emitAnnotation()でアノテーショントークンを発行
- **496行目**: typeAtt.setType("annotation")

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

```
AnnotatedTextPlugin
    |
    +-- getMappers() -> AnnotatedTextFieldMapper.PARSER
    +-- getHighlighters() -> AnnotatedTextHighlighter
            |
AnnotatedTextFieldMapper
    |
    +-- Builder.build()
    |       +-- buildFieldType() -> AnnotatedTextFieldType
    |               +-- wrapAnalyzer() -> AnnotationAnalyzerWrapper
    |
    +-- parseCreateField(context)
            +-- AnnotationAnalyzerWrapper
                    +-- wrapComponents()
                            +-- AnnotatedText.parse(text)
                            +-- AnnotationsInjector.setAnnotations()
                            +-- AnnotationsInjector.incrementToken()
                                    +-- emitAnnotation()
```

### データフロー図

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

"[John Smith](person)" ─> AnnotatedText.parse()
                              |
                              +-- textMinusMarkup: "John Smith"
                              +-- annotations: [{0,10,"person"}]
                                    |
                              AnnotationAnalyzerWrapper
                                    |
                              AnnotationsInjector
                                    |
                              +-- "person" (pos=0, annotation)
                              +-- "john" (pos=0)          ──────> Lucene
                              +-- "smith" (pos=1)                 インデックス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AnnotatedTextPlugin.java | `plugins/mapper-annotated-text/src/main/java/org/opensearch/plugin/mapper/AnnotatedTextPlugin.java` | ソース | プラグインエントリーポイント |
| AnnotatedTextFieldMapper.java | `plugins/mapper-annotated-text/src/main/java/org/opensearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java` | ソース | メインマッパー（AnnotatedText, AnnotationsInjector, AnnotationAnalyzerWrapper含む） |
| AnnotatedPassageFormatter.java | `plugins/mapper-annotated-text/src/main/java/org/opensearch/search/fetch/subphase/highlight/AnnotatedPassageFormatter.java` | ソース | ハイライト用パッセージフォーマッタ |
| AnnotatedTextHighlighter.java | `plugins/mapper-annotated-text/src/main/java/org/opensearch/search/fetch/subphase/highlight/AnnotatedTextHighlighter.java` | ソース | アノテーション対応ハイライター |
