./knowledge-base/docs/改修要件定義書_時代項目シンプル化_v1.0.md
# 時代考証データベース 改修要件定義書 — 時代項目シンプル化 v1.0
---
## 📋 目次
1. [改修概要](#1-改修概要)
2. [データモデル変更](#2-データモデル変更)
3. [バックエンド改修](#3-バックエンド改修)
4. [フロントエンド改修](#4-フロントエンド改修)
5. [データ移行](#5-データ移行)
6. [影響範囲サマリ](#6-影響範囲サマリ)
---
## 1. 改修概要
### 1.1 背景・目的
現行システムでは、ドキュメントに付与される「時代」メタ項目が**西暦年の範囲**として格納される複雑な仕組みになっている。
具体的には、固定ラベル(`user_era_labels`)と展開年リスト(`user_era_years`)の2フィールドに分散しており、
フロントエンド〜バックエンド〜DBの各層で相互変換処理が必要だった。
本改修ではこれを廃止し、**9種類の固定ラベル文字列の配列** 1フィールドに統一することで、
実装の複雑性を大幅に削減し、メンテナンス性を向上させる。
### 1.2 新しい時代ラベル定義(全9種)
| ID | ラベル | 備考 |
| ------------------ | ------------------ | ---- |
| `muromachi_before` | 室町以前 | |
| `edo_early` | 江戸時代前期 | |
| `edo_late` | 江戸時代後期 | |
| `bakumatsu` | 幕末 | |
| `meiji` | 明治 | |
| `taisho` | 大正 | |
| `showa_prewar` | 昭和(戦前・戦中) | |
| `showa_postwar` | 昭和(戦後) | |
| `heisei_reiwa` | 平成~ | |
### 1.3 基本方針
- アップロード時に上記9ラベルから**複数選択**できる
- AIによる自動判定も引き続き利用可能。AIも上記9ラベルから**複数**返却できる
- 既存の西暦年ベース仕様(`user_era_years` 等)は完全廃止。移行スクリプトで変換後、旧フィールドを削除する
- 旧仕様・新仕様の資料が混在することは想定しない。すべて新仕様で統一する
---
## 2. データモデル変更
### 2.1 DynamoDB フィールド変更
**対象テーブル**: `historical-research-processing-status`
#### 廃止フィールド
| フィールド名 | 型 | 内容 |
| ----------------- | ---------------- | ------------------------------------------- |
| `user_era_labels` | `string[]` | 固定3ラベル(室町以前・江戸前期・江戸後期) |
| `user_era_years` | `number[]` | 1853年以降を年単位で展開したリスト |
| `user_era_start` | `number \| null` | 展開年の最小値 |
| `user_era_end` | `number \| null` | 展開年の最大値 |
#### 新設フィールド
| フィールド名 | 型 | 内容 |
| ------------ | ---------- | --------------------------------------------------------- |
| `era_labels` | `string[]` | 9ラベルから選択した文字列の配列(例: `["明治", "大正"]`) |
### 2.2 OpenSearch インデックス フィールド変更
**対象インデックス**: `historical-research-pages`
#### 廃止フィールド
| フィールド名 | 型 |
| ----------------- | --------- |
| `user_era_labels` | `keyword` |
| `user_era_years` | `integer` |
| `user_era_start` | `integer` |
| `user_era_end` | `integer` |
#### 新設フィールド
| フィールド名 | 型 | 備考 |
| ------------ | --------- | ---------------------------------------------- |
| `era_labels` | `keyword` | 複数値フィールド(配列をそのままインデックス) |
### 2.3 era-list.json 変更
#### 現行仕様
```json
[
{
"id": "muromachi_before",
"label": "室町以前",
"year_range": { "start": 0, "end": 1599 },
"is_fixed_label": true
},
...
]
```
#### 改修後仕様(シンプル化)
```json
[
{ "id": "muromachi_before", "label": "室町以前" },
{ "id": "edo_early", "label": "江戸時代前期" },
{ "id": "edo_late", "label": "江戸時代後期" },
{ "id": "bakumatsu", "label": "幕末" },
{ "id": "meiji", "label": "明治" },
{ "id": "taisho", "label": "大正" },
{ "id": "showa_prewar", "label": "昭和(戦前・戦中)" },
{ "id": "showa_postwar", "label": "昭和(戦後)" },
{ "id": "heisei_reiwa", "label": "平成~" }
]
```
---
## 3. バックエンド改修
### 3.1 共通削除処理
以下の関数は `upload-handler`・`pdf-splitter`・`image-analyzer`・`maintenance-handler` の4 Lambdaに重複して存在するため、すべて削除する。
| 関数名 | 内容 |
| ------------------------------------------ | ------------------------------------------------------------------ |
| `extract_era_fields(era_entries)` | `era_entries` → `user_era_labels` / `user_era_years` 変換 |
| `ai_era_to_entries(ai_era_text, era_list)` | AI自由記述テキスト → `era_entries` 変換(`image-analyzer`のみ) |
| `_years_to_era_labels(years)` | 年リスト → ラベル配列変換(`search-api`のみ) |
| `_build_era_entries_from_stored(item)` | 保存済みデータ → `era_entries` 逆変換(`maintenance-handler`のみ) |
### 3.2 upload-handler Lambda
**ファイル**: `lambda/upload-handler/handler.py`
#### 変更点
| 項目 | 現行 | 改修後 |
| -------------------------------------- | -------------------------------------------------- | --------------------------------------------- |
| リクエストボディのera項目 | `era_entries: list[dict]`(ラベル+年範囲の構造体) | `era_labels: list[str]`(ラベル文字列の配列) |
| `ai_era_mode=false` 時のバリデーション | `era_entries` が空なら400エラー | `era_labels` が空なら400エラー |
| DynamoDB保存 | `extract_era_fields()` を呼び出して変換後に保存 | `era_labels` をそのまま保存 |
#### 削除関数
- `extract_era_fields()`
### 3.3 pdf-splitter Lambda
**ファイル**: `lambda/pdf-splitter/handler.py`
#### 変更点
| 項目 | 現行 | 改修後 |
| ----------------------------- | --------------------------------------------------------------------- | ---------------------------- |
| メタデータから取得するera項目 | `era_entries`(構造体リスト) | `era_labels`(文字列リスト) |
| DynamoDB保存フィールド | `user_era_labels`, `user_era_years`, `user_era_start`, `user_era_end` | `era_labels` のみ |
| OpenSearch保存フィールド | 同上 | `era_labels` のみ |
#### 削除関数
- `extract_era_fields()`
### 3.4 image-analyzer Lambda
**ファイル**: `lambda/image-analyzer/handler.py`
#### 変更点①:AI判定結果の受け取り
| 項目 | 現行 | 改修後 |
| ---------------- | -------------------------------------------------------- | ------------------------------------------------------ |
| AIへの出力指示 | `ai_era: string`(自由記述例: "昭和40年代(1965年〜)") | `ai_era_labels: string[]`(9ラベルの列挙から複数選択) |
| AI出力の変換処理 | `ai_era_to_entries()` → `extract_era_fields()` の2段変換 | 変換不要(`ai_era_labels` をそのまま利用) |
#### 変更点②:ドキュメント保存フィールド
| 現行 | 改修後 |
| --------------------------------------------------------------------- | ----------------- |
| `user_era_labels`, `user_era_years`, `user_era_start`, `user_era_end` | `era_labels` のみ |
#### 変更点③:プロンプト内のera説明文
`ai_era_mode=true` の場合のプロンプト内変数 `{era}` が現行では `"不明(AIが判断します)"` となっているが、
改修後は `"AIが以下の9ラベルから判断します:室町以前/江戸時代前期/江戸時代後期/幕末/明治/大正/昭和(戦前・戦中)/昭和(戦後)/平成~"` に変更する。
#### 削除関数
- `extract_era_fields()`
- `ai_era_to_entries()`
### 3.5 maintenance-handler Lambda
**ファイル**: `lambda/maintenance-handler/handler.py`
#### 変更点
| 項目 | 現行 | 改修後 |
| ---------------------------------- | --------------------------------------------------------------------- | --------------------------- |
| PUT エンドポイントの受付フィールド | `era_entries: list[dict]` | `era_labels: list[str]` |
| DynamoDB更新フィールド | `user_era_labels`, `user_era_years`, `user_era_start`, `user_era_end` | `era_labels` のみ |
| OpenSearch更新フィールド | 同上 | `era_labels` のみ |
| 既存データ読み込み時の変換 | `_build_era_entries_from_stored()` で `era_entries` に変換 | `era_labels` をそのまま返す |
#### 削除関数
- `_extract_era_fields()`
- `_build_era_entries_from_stored()`
### 3.6 search-api Lambda
**ファイル**: `lambda/search-api/handler.py`
#### 変更点①:eraフィルタークエリ
| 項目 | 現行 | 改修後 |
| ---------------- | ------------------------------------------------------------- | ------------------------------------------ |
| ラベル一致 | `user_era_labels.keyword` への `term` クエリ | `era_labels` への `term` クエリ |
| 年範囲フィルター | `user_era_years` への `terms` + `user_era_start/end` のレンジ | 廃止(`era_labels` の `terms` クエリのみ) |
| 複数選択時の条件 | 単一値のみ対応 | `terms` クエリ(OR条件、複数値対応) |
| 未設定フィルター | `user_era_labels` も `user_era_years` も存在しない条件 | `era_labels` が存在しない条件 |
#### 変更点②:検索結果の era フィールド組み立て
| 現行 | 改修後 |
| ----------------------------------------------------------------- | --------------------------- |
| `user_era_labels` + `_years_to_era_labels(user_era_years)` を合成 | `era_labels` をそのまま返す |
#### 削除関数
- `_years_to_era_labels()`
### 3.7 AI JSON スキーマ変更
**ファイル**: `prompts/image-analysis/json-schema.json`
#### 変更点
```json
// 現行
"ai_era": {
"type": "string",
"description": "AIが判定した時代(例: 昭和40年代(1965年~1974年))"
}
// 改修後
"ai_era_labels": {
"type": "array",
"items": {
"type": "string",
"enum": [
"室町以前", "江戸時代前期", "江戸時代後期",
"幕末", "明治", "大正",
"昭和(戦前・戦中)", "昭和(戦後)", "平成~"
]
},
"description": "AIが判定した時代ラベル。上記のenum値から該当するものをすべて選択してください。"
}
```
---
## 4. フロントエンド改修
### 4.1 型定義 (`types/index.ts`)
#### 廃止する型・フィールド
| 型 | フィールド/内容 |
| ----------------------------- | ----------------------------------------------------------------------------- |
| `EraEntry` インターフェース | `{ label: string; start_year: number; end_year: number }` — 完全廃止 |
| `EraMaster` インターフェース | `year_range`, `is_fixed_label` フィールドを削除(`id`, `label` のみに簡略化) |
| `PageMetadata.era_entries` | `era_labels: string[]` に変更 |
| `SearchResultItem.era_labels` | `string[]` に維持(フィールド名変更なし) |
| `SearchResultItem.era_years` | 廃止 |
| `SearchResultItem.era_start` | 廃止 |
| `SearchResultItem.era_end` | 廃止 |
#### 追加・変更する型
```typescript
// 改修後の EraMaster
export interface EraMaster {
id: string;
label: string;
}
// 改修後の PageMetadata(era関連)
export interface PageMetadata {
era_labels: string[]; // 変更: era_entries → era_labels
ai_era_mode?: boolean; // 変更なし
// ... その他フィールド
}
// 改修後の SearchResultItem(era関連)
export interface SearchResultItem {
era_labels: string[]; // 変更なし(中身がシンプル化)
// era_years, era_start, era_end を削除
// ... その他フィールド
}
```
### 4.2 eraUtils.ts 変更
**ファイル**: `frontend/src/utils/eraUtils.ts`
#### 廃止するもの
- `GENGOU_LIST` 定数(元号→西暦オフセットリスト)
- `ERA_YEAR_BASED_LIST` 定数(年範囲付き時代リスト)
- 年変換関連ユーティリティ関数すべて
#### 残すもの
```typescript
// 9ラベルの定数配列(これが唯一のラベル定義)
export const ERA_LABELS = [
"室町以前",
"江戸時代前期",
"江戸時代後期",
"幕末",
"明治",
"大正",
"昭和(戦前・戦中)",
"昭和(戦後)",
"平成~",
] as const;
export type EraLabel = (typeof ERA_LABELS)[number];
```
### 4.3 EraSelector.tsx 完全書き換え
**ファイル**: `frontend/src/components/EraSelector.tsx`
#### 現行コンポーネントの問題点
- 固定ラベル3択 + 「それ以降」展開 + 年入力フィールドという複雑な2段UI
- `EraEntry[]`(ラベル+年範囲構造体)を状態として管理
- initEraStateFromStored / initEraStateFromEntries の2種類の初期化関数
#### 改修後のコンポーネント仕様
```typescript
interface Props {
value: string[]; // 選択済みのラベル配列
onChange: (labels: string[]) => void;
disabled?: boolean;
}
```
- **UI**: `ERA_LABELS` の9項目をチェックボックスリストで表示
- 複数選択可(制限なし)
- 年入力フィールドは完全廃止
- `initEraFromLabels(storedLabels: string[]): string[]` のシンプルな初期化関数のみ
#### 廃止するエクスポート
- `ERA_SELECTION_OPTIONS` 定数
- `initEraStateFromStored()` 関数
- `initEraStateFromEntries()` 関数
### 4.4 UploadPage.tsx 変更
**ファイル**: `frontend/src/pages/UploadPage.tsx`
| 項目 | 現行 | 改修後 |
| ------------------------ | --------------------------------------------- | ---------------------------------- |
| era状態 | `eraEntries: EraEntry[]` | `eraLabels: string[]` |
| EraSelector props | `initialEntries` / `onChange` が `EraEntry[]` | `value` / `onChange` が `string[]` |
| buildMetadata の era項目 | `era_entries: eraEntries` | `era_labels: eraLabels` |
| バリデーション | `eraEntries.length > 0` | `eraLabels.length > 0` |
### 4.5 MaintenancePage.tsx 変更
**ファイル**: `frontend/src/pages/MaintenancePage.tsx`
| 項目 | 現行 | 改修後 |
| ------------------ | ------------------------------------------------------------------ | ---------------------------------- |
| 編集状態のera項目 | `era_entries: EraEntry[]` | `era_labels: string[]` |
| EraSelector props | `initialEntries` / `onChange` が `EraEntry[]` | `value` / `onChange` が `string[]` |
| APIペイロード | `era_entries: edit.era_entries` | `era_labels: edit.era_labels` |
| 既存データ読み込み | `_build_era_entries_from_stored()` の結果を `era_entries` にセット | `era_labels` をそのままセット |
### 4.6 BrowseLists.tsx 変更
**ファイル**: `frontend/src/components/BrowseLists.tsx`
#### eraフィルターUIの変更
| 項目 | 現行 | 改修後 |
| -------------- | -------------------------- | ------------------------------------ |
| フィルター形式 | ドロップダウン(単一選択) | チェックボックス or Chip(複数選択) |
| 選択条件 | 単一ラベル絞り込み | 複数ラベル OR 条件絞り込み |
| 状態型 | `selectedEra: string` | `selectedEras: string[]` |