ひよっこエンジニアが生成AIを組み込んだ名寄せツールを開発した話


## はじめに

不動産データの名寄せは、表記揺れや欠損情報の補完が必要な場面が多く、手作業で行うには非常に手間がかかります。今回は、**機械的な名寄せ処理**と**AIおよび翻訳APIを組み合わせた補完処理**を組み込んだツールを作成しました。

このツールは以下の課題を解決します：
1. 表記揺れの正規化とデータの重複排除
2. 備考欄の英語を翻訳して日本語データに統一
3. 欠損情報をAIで補完し、データの品質を向上

---

## 使用した技術スタック

以下の技術を使用しました：

- **言語**: TypeScript
- **フレームワーク**: Next.js
- **翻訳サービス**: DeepL API
- **AIモデル**: Google Generative AI (Gemini Pro)
- **データ操作**:
  - Node.js の `fs` モジュールでファイル操作
  - `csv-parser` ライブラリでCSVデータ解析
  - `csv-writer` ライブラリでCSV出力

---

## 機能要件

### **主な機能**
1. **表記揺れの正規化**
   - 物件名、住所、電話番号の表記揺れを統一し、データの重複を検出しやすくする。

2. **備考欄の翻訳**
   - 備考欄の英語をDeepL APIで高品質な日本語に翻訳。
   - 日本語の備考は翻訳せずそのまま保持。

3. **フェーズ1：機械的な名寄せ**
   - 正規化されたフィールドをもとに、同一データを識別。
   - 備考欄の情報を重複なく統合。

4. **フェーズ2：AIによる補完と仕上げ**
   - Google Generative AIを使用し、欠損データの補完や備考欄の整理を実施。

---

## 苦労した不具合と解決方法

### **1. 表記揺れの統一**
**問題**: 名寄せキー作成時、住所や電話番号の表記揺れが原因でデータが正しく統合されない。  
**解決**: 
- `normalizeField`関数を作成し、全角・半角やハイフン、スペースなどを正規化。

### **2. 英語翻訳の品質問題**
**問題**: 備考欄をAIで翻訳した場合、翻訳品質が低く、誤訳や不自然な表現が多発。  
**解決**: 
- DeepL APIを使用し、英語の備考のみを高品質な日本語に翻訳。
- 日本語の備考はそのまま保持するように`containsJapanese`関数を実装。

### **3. AIによる名寄せの品質不足**
**問題**: AIを単独で名寄せに使用すると、備考欄の情報が欠損したり、順序が乱れるケースが発生。  
**解決**:
- 機械的な名寄せ処理（フェーズ1）で同一データを事前に統合。
- AIには欠損補完や備考欄の調整のみを依頼。

---

## 工夫したロジック

### **1. 機械的な名寄せ処理の採用**
AI任せにすると品質が低下するため、以下の手順を追加：
- 正規化されたフィールドを用いて名寄せキーを生成。
- 機械的にデータを統合し、備考欄を一意にまとめる。

### **2. DeepLとAIの役割分担**
- 英語の備考翻訳をDeepL APIに任せることで、翻訳品質を確保。
- AIには欠損補完や最終的な統合処理を依頼。

### **3. プロンプト設計**
Google Generative AIに渡すプロンプトを以下のように改善：
- 欠損や順序乱れを防ぐため、具体的な指示を記載。
- 出力フォーマットをJSONで固定し、解析エラーを防止。

---

以下は、**AIによる名寄せの部分**を含めたコードのポイントです。

## コードのポイント

### **1. 備考欄をDeepL APIで翻訳するロジック**

英語の備考を高品質な日本語に翻訳し、日本語の備考はそのまま保持します。

```typescript
const translateNotes = async (records: Array<{ [key: string]: string }>) => {
  const translatedRecords = [];
  for (const record of records) {
    if (record.備考) {
      try {
        if (containsJapanese(record.備考)) {
          // 日本語の場合はそのまま
          translatedRecords.push(record);
        } else {
          // DeepLで英語を日本語に翻訳
          const result = await translator.translateText(record.備考, null, 'ja');
          translatedRecords.push({ ...record, 備考: result.text });
        }
      } catch (err) {
        console.error('Failed to translate note:', record.備考, err);
        translatedRecords.push(record); // 翻訳失敗時は元データを保持
      }
    } else {
      translatedRecords.push(record);
    }
  }
  return translatedRecords;
};
```

---

### **2. AIによる名寄せのロジック**

Google Generative AIを使用して、欠損データの補完と備考欄の整理を行います。

```typescript
const finalizeDeduplicationWithAI = async (records: Array<{ [key: string]: string }>) => {
  const prompt = `
    以下の不動産データについて、重複を名寄せして整理してください。一意のレコードのみを保持し、すべての情報を欠損なく統合してください。
    - 備考欄の情報は重要であるため、欠損や省略がないように統合してください。
    - 既存の備考を削除せず、すべての内容を含めてください。
    - 備考欄の情報が重複する場合は、順序を保持して統一してください。

    入力データ:
    ${JSON.stringify(records)}

    出力フォーマット:
    [
      {
        "物件名": "正規化済みの物件名",
        "住所": "正規化済みの住所",
        "電話番号": "正規化済みの電話番号",
        "備考": "統合された備考情報（, 区切り）"
      },
      ...
    ]
  `;

  const model = googleClient.getGenerativeModel({ model: 'gemini-pro' });
  const response = await model.generateContent(prompt);

  const generatedText = typeof response.response?.text === 'function'
    ? response.response.text()
    : response.response?.text;

  if (!generatedText) {
    throw new Error('No response text from Google Generative AI.');
  }

  const cleanedText = generatedText.trim().replace(/```\n/g, '').replace(/```/g, ''); // 不要な囲み文字を削除
  console.log('Generated JSON:', cleanedText);

  return JSON.parse(cleanedText);
};
```

**ポイント**
- AIのプロンプトに具体的な指示（欠損防止や備考欄の統合ルール）を記載し、出力品質を確保。
- プロンプト設計を工夫することで、データの欠損や乱れを最小化。

--- 

このように、**DeepL APIとGoogle Generative AIを適材適所で組み合わせる**ことで、データ品質を大幅に向上させることができました。

---

## 実行結果

以下のデータを処理した結果：

**入力データ（サンプル）**
```csv
物件名,住所,電話番号,備考
サンプルマンション,東京都新宿区西新宿1-1-1,0120-123-456,新築物件
サンプルマンション,東京都新宿区西新宿１－１－１,0120-123-456,築2年
さんぷるマンション,東京都新宿区西新宿1-1-1,0120123456,リフォーム済み
SAMPLE MANSION,Tokyo Shinjuku-ku Nishi-Shinjuku 1-1-1,0120123456,Free Wi-Fi
SAMPLE MANSION,Tokyo Shinjuku-ku Nishi-Shinjuku 1-1-1,0120123456,駐車場完備
```

**出力データ**
```csv
物件名,住所,電話番号,備考
サンプルマンション,東京都新宿区西新宿1-1-1,0120-123-456,"新築物件, 築2年, リフォーム済み, Free Wi-Fi , 駐車場完備"

```

---

## まとめ

今回作成した不動産名寄せツールでは、**DeepL APIとGoogle Generative AIを役割分担**させることで、データの品質を大幅に向上させることができました。特に、以下の工夫が有効でした：
- 機械的な処理で表記揺れを正規化
- DeepL APIで高品質な英語翻訳を実現
- AIをデータ補完と調整のみに使用することで効率化

このツールは、不動産データ以外の名寄せ処理やデータ統合にも応用可能です。