---
title: "メンバー関係性マップ データ設計書"
---

最終更新: 2026-07-06
関連 Issue:
- #1197（メンバ関係性マップ）/ #1208（データ管理）/ #1210（レイアウト操作・永続化）/ #1236（共演エッジのラベル表示）/ #1263（集計とラベルの関連付け見直し）/ #1266（加入時リーダーによるチルドレン分類）
- #1198（関係性スコアリングマップ）/ #1268（関係性スコアリング設計書作成）/ #1269（関係性スコアリングロジックの実装）

---

## 1. 概要

メンバー関係性マップ機能は、モーニング娘。現役メンバー間の関係性（同期・同郷・師弟など）を React Flow でビジュアライズする画面。データは Neon（PostgreSQL）上の2テーブルで管理する。

---

## 2. テーブル設計

### 2-1. `member_relationships`（MemberRelationship）

メンバー間の関係性を1件1レコードとして保持する。

| カラム | 型 | 制約 | 説明 |
|---|---|---|---|
| `id` | INT | PK, AUTO INCREMENT | 関係性 ID |
| `fromMemberId` | VARCHAR(50) | NOT NULL | 関係の起点メンバー ID |
| `toMemberId` | VARCHAR(50) | NOT NULL | 関係の終点メンバー ID |
| `type` | VARCHAR(50) | NOT NULL | 関係種別（下記参照） |
| `weight` | INT | NOT NULL, DEFAULT 1 | 関係の強さ（エッジの太さに反映） |
| `label` | VARCHAR(255) | NULL | 補足ラベル（`same-unit` ではユニット名、`live-coappearance`/`radio-coappearance`/`event-coappearance` では最多共演ライブ名・番組名・イベント名） |

- **ユニーク制約:** `(fromMemberId, toMemberId, type)`
- **インデックス:** `fromMemberId`・`toMemberId`（各単列インデックス）
- Prisma モデル名: `MemberRelationship`（`@@map("member_relationships")`）

#### 関係種別（type）一覧

| type 値 | 表示ラベル | 有向エッジ | フェーズ1現役マップ表示 |
|---|---|---|---|
| `same-gen` | 同期 | なし | ✅ 表示 |
| `same-hometown` | 同郷 | なし | ✅ 表示 |
| `same-unit` | `label`（ユニット名）| なし | ❌ 非表示 |
| `leader-succession` | リーダー継承 | あり（→後任） | ✅ 表示 |
| `mentor-student` | 師弟 | あり（→弟子） | ✅ 表示 |
| `live-coappearance` | ライブ共演 | なし | ❌ 非表示 |
| `radio-coappearance` | ラジオ共演 | なし | ❌ 非表示 |
| `event-coappearance` | イベント共演 | なし | ❌ 非表示 |
| `overlapping` | 同在籍 | なし | ❌ 非表示（ラベルも非表示） |

#### エッジ生成元（静的エッジ / 動的エッジ）

`member_relationships` は生成元スクリプトが異なる2種類のエッジで構成される。

| 区分 | 対象 type | 生成スクリプト | 実行契機 |
|---|---|---|---|
| 静的エッジ（`STATIC_EDGE_TYPES`） | `same-gen`・`same-hometown`・`same-unit`・`overlapping`・`leader-succession`・`mentor-student` | `scripts/console/seed-member-relationships.ts` | メンバー名簿・ユニット構成の変更時に手動実行（コンソールスクリプト） |
| 動的エッジ（`DYNAMIC_EDGE_TYPES`） | `live-coappearance`・`radio-coappearance`・`event-coappearance` | `scripts/workflow/sync-relationships.ts` | Sync Events ワークフローから定期実行 |

「静的」「動的」は生成元スクリプトの実行契機による分類であり、値そのものの不変性を意味しない（例: `overlapping` は静的エッジだが、現役メンバーの在籍月数は時間経過で変わる。ただし `weight`/`label` への反映は手動実行時点のスナップショットであり、自動で追従はしない）。

**現役マップ表示対象 (`ACTIVE_MEMBER_ALLOWED_RELATIONSHIP_TYPES`):**

```typescript
// lib/member-map-utils.ts
export const ACTIVE_MEMBER_ALLOWED_RELATIONSHIP_TYPES = new Set([
  'same-gen',
  'same-hometown',
  'leader-succession',
  'mentor-student',
]);
```

`GET /api/member-relationships` は全種別を返す。クライアント側の `filterDisplayRelationships()` で絞り込む（「OGを含む」OFF 時はこの4種別かつ両端が表示対象メンバーの関係のみ、ON 時は全種別）。

**ラベル表示ルール (`getEdgeLabel`):**
- `overlapping`: 常に `null`（ラベルを表示しない）
- `same-unit`: `r.label`（ユニット名）を使用。`null` の場合は `'ユニット'`
- `live-coappearance`/`radio-coappearance`/`event-coappearance`: `r.label`（最多共演ライブ名・番組名・イベント名）を使用。`null`（同期前の旧データ等）の場合は固定ラベル（「ライブ共演」「ラジオ共演」「イベント共演」）にフォールバック
- その他: `RELATIONSHIP_LABEL_MAP[type]` の固定ラベルを使用

**`live-coappearance`/`radio-coappearance`/`event-coappearance` の `label` 算出（`scripts/workflow/sync-relationships.ts`）:**

`sync-relationships.ts` の同期処理で、メンバーペアごとに以下を集計する。

1. ライブ共演: `member_lives` → `lives` → `tours`（LEFT JOIN）を辿り、`Tour.title` → （null の場合）`Live.title` → （両方 null の場合）固定文言「ライブ共演」の順でフォールバックしたラベルをペアごとに取得
2. ラジオ共演: `radio_members` → `radio_episodes` → `radio_shows` を JOIN し、ペア×番組名ごとの共演回数を取得
3. イベント共演: `events.title` を JOIN 不要でそのまま参照し、ペア×イベント名ごとの共演回数を取得。`type='live'`（ライブ種別イベント）はライブ共演で別途集計するため除外する
4. ペアごとに、`weight`（全ラベル共通の合計共演回数）とは別に、ラベルごとの出現回数を集計
5. 出現回数が最多のラベルを `label` に設定する。同数の場合は文字列順（昇順）で決定的に選ぶ

### 2-2. `member_map_layouts`（MemberMapLayout）

メンバーのノード座標を永続化する。

| カラム | 型 | 制約 | 説明 |
|---|---|---|---|
| `memberId` | VARCHAR(50) | PK | メンバー ID（`member.id` と対応） |
| `x` | FLOAT | NOT NULL | React Flow キャンバス上の X 座標 |
| `y` | FLOAT | NOT NULL | React Flow キャンバス上の Y 座標 |

- Prisma モデル名: `MemberMapLayout`（`@@map("member_map_layouts")`）
- 座標未登録のメンバーは円形配置アルゴリズムで仮配置される（DB には保存されない）
- 「配置を保存」ボタン押下時に `PUT /api/member-map-layout` で UPSERT される

---

## 3. TypeScript 型定義

`lib/member-map-utils.ts` に定義。

```typescript
export interface MemberRelationship {
  id: number;
  fromMemberId: string;
  toMemberId: string;
  type: string;
  weight: number;
  label: string | null;
}

export interface MemberMapLayoutPoint {
  memberId: string;
  x: number;
  y: number;
}

export type ControlMode = 'filter' | 'group';
export type ControlAxis = 'generation' | 'hometown' | 'relationship' | 'leader' | 'joiningRoute';
```

---

## 4. API エンドポイント

### 4-1. GET /api/member-map-layout

保存済みの全ノード座標を返す。

- **実装:** `app/api/member-map-layout/route.ts`
- **レスポンス:** `MemberMapLayoutPoint[]`

```json
[
  { "memberId": "oda-sakura", "x": 120.5, "y": -80.0 },
  { "memberId": "ishida-ayumi", "x": -200.0, "y": 150.0 }
]
```

- **エラー:** 500 → `{ "error": "メンバーマップレイアウトデータの取得に失敗しました。" }`

### 4-2. PUT /api/member-map-layout

ノード座標を保存（UPSERT）する。

- **実装:** `app/api/member-map-layout/route.ts`
- **リクエストボディ:**

```json
{
  "layouts": [
    { "memberId": "oda-sakura", "x": 120.5, "y": -80.0 }
  ]
}
```

- **処理:** Prisma `$transaction` 内で各レコードを `upsert`（`where: { memberId }` で一致時 update、不一致時 create）
- **レスポンス:** `{ "updated": <更新件数> }`
- **エラー:** 400 → `{ "error": "layouts は空でない配列である必要があります。" }`（`layouts` が空配列または非配列の場合）
- **エラー:** 500 → `{ "error": "レイアウトデータの保存に失敗しました。" }`

### 4-3. GET /api/member-relationships

全関係性データを返す（全 type・全ステータスのメンバー間を含む）。

- **実装:** `app/api/member-relationships/route.ts`
- **ソート:** `type` 昇順、`id` 昇順
- **レスポンス:** `MemberRelationship[]`

```json
[
  { "id": 1, "fromMemberId": "oda-sakura", "toMemberId": "nonaka-miki", "type": "same-gen", "weight": 1, "label": null }
]
```

- **エラー:** 500 → `{ "error": "メンバー関係性データの取得に失敗しました。" }`

> **注意:** API はフィルタリングを行わない。表示対象メンバー間・許可 type への絞り込みはクライアント側（`filterDisplayRelationships`）で実施する。

---

## 5. クライアント側ユーティリティ（lib/member-map-utils.ts）

### 5-1. データ変換

| 関数 | 用途 |
|---|---|
| `buildActiveMemberSet(members)` | Active メンバーの ID セットを生成 |
| `buildDisplayMemberSet(members, includeOg)` | 表示対象メンバーの ID セットを生成（`includeOg=false` は Active のみ、`true` は全メンバー） |
| `filterDisplayRelationships(relationships, displayMemberIds, includeOg)` | 表示対象メンバー間の関係を抽出（`includeOg=false` 時は許可 type のみ、`true` 時は全種別） |
| `buildMemberColorMap(members)` | memberId → color のマップを生成 |
| `buildLayoutMap(layouts)` | memberId → `{x, y}` のマップを生成 |

### 5-2. フィルタリング（mode = 'filter'）

**`getFilteredMemberIds(axis, selectedValue, members, activeRelationships, activeMemberIds)`**

| axis | 返却内容 |
|---|---|
| `generation` | 選択期（`parseInt(selectedValue)`）の表示対象メンバー ID セット |
| `hometown` | `member.profile.birthplace === selectedValue` の表示対象メンバー ID セット |
| `relationship` | `r.type === selectedValue` の関係に含まれる両端メンバー ID セット |
| `leader` | 加入時のリーダーが選択したリーダーだった表示対象メンバー ID セット（`deriveLeaderPeriods`、#1266） |
| `joiningRoute` | `member.joiningRoute === selectedValue` の表示対象メンバー ID セット（#1284） |

**`getFilteredEdgeIds(axis, selectedValue, activeRelationships, highlightedMemberIds)`**

| axis | 返却内容 |
|---|---|
| `relationship` | `r.type === selectedValue` のエッジ ID セット（`edge-${r.id}`） |
| その他 | 両端が `highlightedMemberIds` に含まれるエッジ ID セット |

### 5-3. グルーピング（mode = 'group'）

**`buildGroupInfos(axis, members, activeRelationships, activeMemberIds, selectedRelType)`**

| axis | グループキー | ラベル |
|---|---|---|
| `generation` | `String(member.generation)` | `${key}期` |
| `hometown` | `member.profile.birthplace` | 出身地名（`null` メンバーはグループ外） |
| `relationship` | `r.label ?? r.type` | 関係種別ラベル（`selectedRelType` 一致の関係のみ） |
| `leader` | 加入時のリーダーの `memberId`（`getMemberLeaderId`、#1266） | `getLeaderChildrenLabel` による「（愛称&#124;氏名）チルドレン」形式（`member.nickname` 配列の先頭（1件目）が登録されていれば愛称、未登録なら氏名） |
| `joiningRoute` | `member.joiningRoute` | 加入ルート名（`undefined` メンバーはグループ外、#1284） |

**`buildGroupedLayout(activeMemberIds, groups, layoutMap)`**

1. グループ内メンバーの座標（`layoutMap`）から min/max を求める
2. `GROUP_PADDING = 36` を四辺に加えて包含矩形を算出
3. グループ同士の重なりを `resolveGroupOverlaps` で解消（重なりがなくなった時点で終了、最大 100 回）
4. 子ノードに `parentId`・相対座標（`relX = absX - groupX`・`relY = absY - groupY`）を付与
5. いずれのグループにも属さないメンバーは `ungroupedIds` として管理（絶対座標）

### 5-4. 並列エッジ計算

**`computeParallelEdgeGroups(edges)`**

同一ノードペア（`source`・`target` をソートしたキー）に複数エッジが存在する場合、各エッジに `parallelIndex`（0-based）・`parallelTotal` を付与する。

GradientEdge 側でこれらを使って垂直方向 `EDGE_SEPARATION = 18` px ずつオフセットする。

---

## 6. 円形配置アルゴリズム

`getCirclePosition(index, total)` — DB 未登録メンバーの仮配置に使用。

```
radius = max(200, total × 60)
angle = 2π × index / total
x = round(radius × cos(angle))
y = round(radius × sin(angle))
```

- `total`: 座標未登録メンバー（`unsaved`、現役＋OG 全メンバーが対象）の総数
- `index`: `unsaved` 配列中での順序（`unsaved.forEach` のインデックス）
- 仮配置の座標は DB に保存しない。「配置を保存」ボタン押下時に初めて DB へ書き込まれる

---

## 7. エッジ線幅計算

`getEdgeStrokeWidth(weight)` — `weight` を 1〜5px の線幅に変換。

```
strokeWidth = max(1, min(1 + weight / 20, 5))
```

| weight | strokeWidth |
|---|---|
| 1 | 1.05 |
| 20 | 2.0 |
| 80 | 5.0 |
| 100 | 5.0（上限） |

## 8. 関係性スコアリング（実装: #1269）

メンバー間の関係性を4軸で数値化し、総合スコア（0〜100pt）として算出する機能。既存の相関図（React Flow）とは独立した機能であり、`member_relationships` テーブルを置き換えるものではなく拡張する（§8-6）。

### 8-1. スコア軸と配点

| 軸 | 配点 | 算出元 |
|---|---|---|
| 在籍期間重なり | 0〜40pt | `member.profile.joinDate` / `gradDate` |
| 師弟関係 | 0〜30pt | `member_relationships`（静的エッジ、`type='mentor-student'`） |
| メンバーカラー距離 | 0〜20pt | `member.color` |
| 出身地距離 | 0〜10pt | `member.profile.birthplace` |

総合スコア = 4軸の合計（0〜100pt）。

**実装:** `lib/member-map-utils.ts` の `getOverlapScore`・`getMentorStudentScore`・`getColorDistanceScore`・`getBirthplaceDistanceScore`・`getRelationshipScore`（4軸合算）。既存の相関図と同じくクライアントから import 可能な純粋関数として実装し、DB・API 呼び出しは行わない（§8-6）。

### 8-2. 在籍期間重なり（0〜40pt）

```
overlapMonths = 2メンバーの在籍期間（joinDate〜gradDate：現役は現在まで）が重なる月数
score = min(40, round(40 × min(overlapMonths, 120) / 120))
```

10年（120ヶ月）で満点（40pt）とし、10年以上は40ptで頭打ちとする。

`overlapMonths` はカレンダー年月の差分（年×12+月）で算出し、日にちは見ない（例: 1/25〜2/10の重なりも実日数16日ではなく1ヶ月として扱う）。スコアリング用途の粗い指標であり、日単位の精度は求めない。

### 8-3. 師弟関係（0〜30pt）

既存の `mentor-student` エッジ（静的エッジ、`profile.instructor` 由来、方向: 師匠→弟子）を direct/indirect の2段階に分けてスコア化する。

| 区分 | 判定 | score |
|---|---|---|
| direct | `profile.instructor` 由来の直接エッジ（depth=1） | 30pt |
| indirect | `profile.instructor` の連鎖を depth 2 以上たどって到達できるメンバー（師匠の師匠…） | 15pt |
| 上記以外 | 無関係 | 0pt |

- **区別方法**: 新規カラムは追加せず、`member_relationships.label` に `'direct'` / `'indirect'` を格納して表現する。`same-unit`（ユニット名）・`radio-coappearance`（番組名）等と同様、`type` ごとに `label` の意味を再定義する既存パターンを踏襲する
- **indirect 算出**: `profile.instructor` の連鎖を depth 2 以上たどって機械的に算出する。新規データ収集は不要（既存の direct エッジをグラフ探索するのみ）
- **循環参照防止**: 連鎖探索時は訪問済みメンバー ID を `Set` で管理し、既訪問メンバーには再度たどらない
- **将来拡張**: 連鎖では拾えない関係（本人の発言による影響表明等）を追加できるよう、`data/inputs/manual-releases.json` と同様のパターンで `data/inputs/manual-mentor-relationships.json`（手動追記ファイル、初期状態は空）を用意し、静的エッジ生成スクリプト実行時にマージする方針とする
- **実装**: direct/indirect の生成・手動登録マージは静的エッジ生成スクリプト `scripts/console/seed-member-relationships.ts` の `generateEdges`（+ `loadManualMentorRelationships`）で行う。動的エッジ生成スクリプト `scripts/workflow/sync-relationships.ts`（live/radio/event共演のみを扱う）は対象外

### 8-4. メンバーカラー距離（0〜20pt）

```
hueDiff = |hueA - hueB| を 0〜180 度に正規化（円環距離: min(|a-b|, 360-|a-b|)）
score = round(20 × (1 − hueDiff / 180))
```

`member.color`（`#RRGGBB`）を RGB → HSV 変換して色相（hue、0〜360°）を算出する。hue は既存データに保存されておらず、スコア計算時に都度変換する（新規カラム追加なし）。

### 8-5. 出身地距離（0〜10pt）

| 条件 | score |
|---|---|
| 同一都道府県（`profile.birthplace` 完全一致） | 10pt |
| 同一地方（都道府県は異なる） | 5pt |
| 上記以外・`birthplace` 未設定 | 0pt |

**地方判定は既存インフラを再利用する（新規マッピング不要）:**

1. `prefecture_codes` テーブル（`aliases` に `birthplace` の表記ゆれを含む）で `birthplace` → `prefectureCode` を解決する
2. `types/live.ts` の `REGION_PREFECTURE_MAP`（`prefectureCode` → `Region`）で地方ブロックを解決する
3. 両メンバーの `Region` が一致すれば同地方と判定する

`birthplace` が未設定、または `prefecture_codes` に一致するエイリアスがない場合は出身地距離を 0pt とする。

### 8-6. `member_relationships` テーブルとの関係

スコアリングは既存の `member_relationships` テーブルを**置き換えるのではなく拡張する**。

- 在籍期間重なり・メンバーカラー距離・出身地距離は、スコア計算時に `members` データから都度算出する（DB に新規テーブル・カラムを追加しない）
- 師弟関係のみ、既存 `mentor-student` エッジ（静的エッジ）の `label` を書き換えて direct/indirect を表現する（§8-3）。動的エッジ（live/radio/event共演）はスコアリングの対象外であり変更しない
- 4軸の計算結果（総合スコア・軸別内訳）はリクエスト時にサーバー側で算出し、永続化しない（メンバー数規模ではキャッシュテーブルを追加するほどの計算コストではないため）

## 9. メンバー愛称（Member.nickname、実装: #1266・#1286）

グルーピング軸 `leader`（加入時リーダーによるチルドレン分類、§5-3）で、グループラベルに使うリーダーの愛称。当初はリーダー限定の `leaderNickname` フィールドだったが、#1286 で汎用化し、メンバー詳細画面（`MemberDetailView`）での表示にも用いる。

### 9-1. データ形状

- **フィールド:** `member.nickname: string[]`（`types/member.ts` の `MemberSchema` に定義。任意フィールド）。愛称は複数バリエーションを持つメンバーがいるため配列とする
- **値:** メンバーの愛称（例: `iida-kaori` → `['かおりん']`）。登録対象は愛称が判明しているメンバーのみで、`data/inputs/member-nicknames.json` に列挙された分だけ存在する
- **利用箇所:**
  - `lib/member-map-utils.ts` の `getLeaderChildrenLabel`。`member.nickname` が登録されていれば配列の先頭（1件目）を、未登録なら `member.name`（氏名）を使って「（愛称&#124;氏名）チルドレン」ラベルを組み立てる（§5-3）
  - `components/MemberDetailView.tsx`。設定されていれば「愛称」欄に配列を連結して表示する

### 9-2. データ投入

デプロイなしにBlobデータへ反映できるよう、以下の3点セットで構成する。

| ファイル | 役割 |
|---|---|
| `types/member.ts` の `MemberSchema.nickname` | Member のスキーマフィールド（任意の `string[]`） |
| `data/inputs/member-nicknames.json` | 入力データ。`{ "nicknames": [{ "memberId": string, "nicknames": string[] }] }` |
| `scripts/patch/patch-member-nicknames.ts` | 入力 JSON を読み込み、Blob の `members.json` の該当メンバーへ `nickname` をセットして書き戻す。入力に含まれないメンバーは既存の `nickname` を削除する。書き戻し先は Blob 本体（`put()`）と `data/work/members.json`（作業用の一時コピー。確認後に削除する）の両方 |

### 9-3. 実装の経緯

当初は `lib/member-map-utils.ts` から `data/leader-nicknames.json` を静的import（`resolveJsonModule`）する実装だった。これは Next.js のビルド時にバンドルされるため、JSONを編集しただけでは本番に反映されずコミット＋デプロイが必要になり、「コンテンツはBlob、コードはデプロイ」という設計原則に反していた。上記の3点セット方式に変更し、`lib/member-map-utils.ts` 側は `member.leaderNickname` を直接参照するだけにした（#1266）。

その後、愛称はリーダーに限らず多くのメンバーが持つものであり、メンバー詳細画面にも表示価値があることから、`leaderNickname`（単一・リーダー限定）を汎用的な `nickname`（配列・全メンバー対象）に置き換えた（#1286）。

## 10. 加入ルート（Member.joiningRoute、実装: #1283）

メンバーの加入経緯（オーディション合格・キッズ/研修生からの昇格 等）を表す属性。

### 10-1. データ形状

- **フィールド:** `member.joiningRoute: string`（`types/member.ts` の `MemberSchema` に追加。任意フィールド）
- **値:** 生データをそのまま保持する（正規化・3値への集約は行わない）。収集済みの値は以下の6種類:
  - `初期メンバー`（オーディションを経ない最初期の指名メンバー）
  - `オーディション`
  - `キッズ昇格`（ハロプロキッズからの昇格）
  - `研修生昇格`（ハロプロ研修生からの昇格）
  - `留学生`（海外からの留学生としての加入）
  - `その他`
- **設計判断:** #1267 のコメントでは表示用に「オーディション合格／キッズ・研修生からの昇格／その他」の3値への集約が提案されていたが、生データの粒度（キッズ昇格と研修生昇格の区別、初期メンバー・留学生の区別）を失わないよう、Member 側は生データを保持する方針とした
- **対象:** 全メンバー（現役＋OG）。未収集のメンバーは `joiningRoute` を持たない（`undefined`）

### 10-2. データ投入

§9-2 の `nickname` と同型の3点セットを踏襲する。

| ファイル | 役割 |
|---|---|
| `types/member.ts` の `MemberSchema.joiningRoute` | Member のスキーマフィールド（任意の `string`） |
| `data/inputs/member-joining-routes.json` | 入力データ。`{ "joiningRoutes": [{ "memberId": string, "route": string }] }` |
| `scripts/patch/patch-member-joining-routes.ts` | 入力 JSON を読み込み、Blob の `members.json` の該当メンバーへ `joiningRoute` をセットして書き戻す。入力に含まれないメンバーは既存の `joiningRoute` を削除する。書き戻し先は Blob 本体と `data/work/members.json`（作業用の一時コピー。確認後に削除する）の両方 |

---

## 改訂履歴（バージョン降順に記載）

| バージョン | 日付 | 変更内容 |
|---|---|---|
| 1.10 | 2026-07-07 | §9 リーダー限定の `Member.leaderNickname`（単一）を汎用的な `Member.nickname`（配列）に置き換え。`data/leader-nicknames.json` → `data/member-nicknames.json`、`scripts/patch/patch-leader-nicknames.ts` → `scripts/patch/patch-member-nicknames.ts` にリネーム。`MemberDetailView` での愛称表示への利用を追記（#1286） |
| 1.9 | 2026-07-06 | §3 `ControlAxis` に加入ルート軸（`joiningRoute`）を追加。§5-2/§5-3 に加入ルート軸のフィルタ・グルーピング仕様（`member.joiningRoute` をそのままキーに使う）を追記（#1284） |
| 1.8 | 2026-07-06 | §9 リーダー愛称（`Member.leaderNickname`）のデータ設計を新規セクションとして追加（実装済みの#1266の内容を明文化）。§10 加入ルート（`Member.joiningRoute`）のデータ設計を新規追加。生データ（6種類の値）をそのまま保持する方針、`data/member-joining-routes.json` → `scripts/patch/patch-member-joining-routes.ts` によるBlobマージ方式を記載（#1283） |
| 1.7 | 2026-07-06 | 愛称データの管理方式を修正。`data/leader-nicknames.json` の静的importをやめ、`leaderGenerationNumber` と同様に Member の `leaderNickname` フィールド（Blob の members.json）で管理する方式に変更。`scripts/patch/patch-leader-nicknames.ts` を新設し、デプロイ不要で愛称を反映できるようにした（#1266） |
| 1.6 | 2026-07-06 | §3 `ControlAxis` にリーダー軸（`leader`）を追加。§5-2/§5-3 にリーダー軸のフィルタ・グルーピング仕様（`getMemberLeaderId`・`getLeaderChildrenLabel`）を追記。旧関数名 `filterActiveRelationships` を現行の `filterDisplayRelationships` に修正し、「Active メンバー」表記を実装（`includeOg` 対応）に合わせて「表示対象メンバー」に修正。§6 円形配置アルゴリズムの対象範囲・変数名の記述を実装（`unsaved`、現役＋OG 全メンバー）に合わせて修正（#1266） |
| 1.5 | 2026-07-05 | §2-1 静的エッジ／動的エッジの生成元スクリプトの区分を新規追加し、§8 の実装参照先（`scripts/console/seed-member-relationships.ts`・`lib/member-map-utils.ts`）を明記（#1269） |
| 1.4 | 2026-07-04 | §8 関係性スコアリングの計算式・direct/indirect ラベル運用・出身地距離の既存インフラ再利用方針を新規追加（#1268） |
| 1.3 | 2026-07-04 | §2-1 live-coappearance の label 算出（Tour.title→Live.title→固定文言フォールバック）を追記し、event-coappearance からライブ種別イベントを除外する仕様を反映（#1263） |
| 1.2 | 2026-07-02 | §2-1 label カラムの説明・ラベル表示ルールを更新し、radio-coappearance/event-coappearance の最多共演番組名・イベント名算出方法を追記（#1236） |
| 1.1 | 2026-06-29 | §4-3 エラーレスポンス追記・§5-3 resolveGroupOverlaps の早期収束に関する補足追加 |
| 1.0 | 2026-06-29 | 初版作成（#1245） |
