---
title: "ツアー ↔ ライブアルバム 紐づきチェック 運用ドキュメント"
---

## 概要

`scripts/find-missing-tour-album-links.ts` は、ツアーデータとライブアルバムリリースデータの紐づき状況を確認するためのスクリプトである。
紐づきが不足している組み合わせを洗い出し、手動でのデータ補完作業（Neon DB 更新）を支援する。

---

## データアーキテクチャ

このスクリプトが参照するデータは **2 つの異なるストア** に分散して保存されている。

| データ種別 | 保存先 | 取得方法 | 主キーフィールド |
| --- | --- | --- | --- |
| ツアー（`tours` テーブル） | **Neon DB**（Prisma） | `DATABASE_URL` 経由で SQL クエリ | `tourId`（例: `2022B`） |
| ライブアルバム（releases） | **Vercel Blob**（`releases.json`） | `RELEASES_BLOB_URL` を fetch | **`id`**（例: `rg-xxxxxxxx-xxxx`） |

> **重要**: releases のフィールドは `id` であり `releaseId` ではない。`lib/blob.ts` の `getReleasesFromBlob()` や `types/release.ts` の `ReleaseSchema` と一致している。

---

## 紐づきの仕組み

```
tours テーブル（Neon）
  tourId: "2022B"
  title:  "モーニング娘。'22 コンサートツアー ..."
  releaseId: "rg-xxxxxxxx-xxxx"  ← Blob releases の id を参照
       │
       ▼
releases.json（Blob）
  id: "rg-xxxxxxxx-xxxx"
  title: "モーニング娘。'22 コンサートツアー ... LIVE"
  format: "live"
```

`tours.releaseId` が `NULL` の場合、そのツアーはライブアルバムと紐づいていない。

---

## チェックロジック

### ① ツアーあり・ライブアルバムなし

```typescript
const toursWithoutAlbum = tours.filter((t) => !t.releaseId);
```

`tours.releaseId` が NULL のツアーが対象。

### ② ライブアルバムあり・対応ツアーなし

```typescript
const tourReleaseIds = new Set(
  tours.map((t) => t.releaseId).filter(Boolean) as string[]
);
const albumsWithoutTour = liveReleases.filter((r) => !tourReleaseIds.has(r.id));
```

Blob の `format: 'live'` なリリースのうち、どのツアーの `releaseId` にも一致しないものが対象。

### 参考: 紐づき済みの組み合わせ確認

```typescript
for (const t of tours.filter((t) => t.releaseId)) {
  const album = liveReleases.find((r) => r.id === t.releaseId);
  const albumTitle = album ? album.title : '(Blob に存在しない)';
  console.log(`  ${t.tourId} → ${t.releaseId}  ${albumTitle}`);
}
```

`liveReleases.find((r) => r.id === t.releaseId)` で一致を検索する。
`r.id` と `t.releaseId` を比較することが正しい。

---

## 既知の問題と修正履歴

### Issue #681: `type Release` のフィールド名ミス（2026-04-20 修正）

**症状**: スクリプト実行時、手動で紐づけ済みのツアーがすべて「Blob に存在しない」と表示され、ほぼ全件が未紐づけと誤判定された。

**根本原因**: スクリプト内のローカル型定義が誤っていた。

```typescript
// ❌ 修正前: releaseId は存在しないフィールド → 常に undefined
type Release = { releaseId: string; title: string; format: string };

// ✅ 修正後
type Release = { id: string; title: string; format: string };
```

**影響箇所**:

| 箇所 | 誤り | 正しい記述 |
| --- | --- | --- |
| `liveReleases.map((r) => r.releaseId)` | `r.releaseId` が常に `undefined` | `r.id` |
| `!tourReleaseIds.has(r.releaseId)` | 全件 `true` になり全アルバムが未紐づけ判定 | `!tourReleaseIds.has(r.id)` |
| `liveReleases.find((r) => r.releaseId === t.releaseId)` | 永遠に一致しない | `r.id === t.releaseId` |
| `console.log(\`  ${r.releaseId}  ${r.title}\`)` | `undefined` が出力される | `r.id` |

---

## 実行手順

```bash
# .env.local に以下を設定
# DATABASE_URL=...
# RELEASES_BLOB_URL=...

bun --env-file=.env.local run scripts/find-missing-tour-album-links.ts
```

---

## 出力の見方

```
============================================================
【① ツアーあり・ライブアルバムなし】 (X 件)
============================================================
  2024B  モーニング娘。'24 MOTTO MORNING MUSUME。
  ...
  → tours.releaseId が NULL。対応するライブアルバムの id を調べて UPDATE する。

============================================================
【② ライブアルバムあり・対応ツアーなし】 (X 件)
============================================================
  rg-xxxxxxxx  モーニング娘。コンサートツアー ○○ LIVE
  ...
  → Blob にはあるがどのツアーにも紐づいていない。ツアーを新規追加するか既存ツアーに紐づける。

--- 参考: 現在紐づき済みの組み合わせ ---
  2022B → rg-xxxxxxxx  モーニング娘。'22 コンサートツアー ... LIVE
  ...
  → "(Blob に存在しない)" と表示された場合、tours.releaseId に設定された id が Blob の releases に存在しない。
```

---

## データ補完手順

### ツアーにライブアルバムを紐づける

1. ②の出力からライブアルバムの `id` と対応するツアーを特定する
2. Neon DB の `tours` テーブルを更新する

```sql
UPDATE tours SET "releaseId" = 'rg-xxxxxxxx-xxxx' WHERE "tourId" = '2022B';
```

### 対応ツアーがないライブアルバムへの対処

- **ツアーデータが存在しない場合**: `tours` テーブルに新規レコードを追加する
- **既存ツアーと実質同一の場合**: 既存ツアーの `releaseId` を更新する
