# 「あわせて飲みたい」機能拡張計画

**作成日**: 2026-01-22  
**ステータス**: Phase 2.0（リリース後1ヶ月）で実装予定

---

## 📊 現状分析

### 実装済み機能
✅ **ローカルレコメンドエンジン**:
- 五味チャートのコサイン類似度計算
- 酒蔵・都道府県・タグによる類似度スコアリング
- スコア順にソート（最大10件）

### 制限事項
❌ **登録済みの銘柄のみ**:
- ユーザーが登録した日本酒からのみレコメンド
- 未知の銘柄（まだ登録していない日本酒）は推薦されない

---

## 🎯 Phase 2.0: 未知の銘柄のレコメンド（拡張計画）

### 1. Synology NAS上の共有データベース構築

**アーキテクチャ**:
```
┌─────────────────────────────────────┐
│  Synology NAS (PostgreSQL)          │
│  posimai-nas.ts.net (Tailscale)     │
│                                      │
│  ┌──────────────────────────────┐  │
│  │  日本酒マスターDB             │  │
│  │                               │  │
│  │  - 銘柄名                     │  │
│  │  - 蔵元                       │  │
│  │  - 都道府県                   │  │
│  │  - 五味チャート（平均値）     │  │
│  │  - タグ                       │  │
│  │  - 人気度（登録回数）         │  │
│  └──────────────────────────────┘  │
│         ↓                            │
│  ┌──────────────────────────────┐  │
│  │  ユーザー登録DB               │  │
│  │                               │  │
│  │  - user_id (匿名化)          │  │
│  │  - sake_id                    │  │
│  │  - 評価・レビュー             │  │
│  │  - 五味チャート              │  │
│  └──────────────────────────────┘  │
└─────────────────────────────────────┘
         ↑ HTTPS (Tailscale)
┌─────────────────────────────────────┐
│  Flutter App                        │
│                                      │
│  - 自分のカード（Hive）            │
│  - 未知の銘柄（API経由）           │
└─────────────────────────────────────┘
```

---

### 2. API設計

#### エンドポイント: `/api/v1/recommendations`

**リクエスト**:
```json
{
  "target": {
    "prefecture": "新潟県",
    "type": "純米吟醸",
    "taste_stats": {
      "aroma": 4,
      "sweetness": 3,
      "acidity": 3,
      "bitterness": 2,
      "body": 4
    },
    "flavor_tags": ["フルーティー", "すっきり"]
  },
  "exclude_ids": ["abc123", "def456"], // 既に登録済みの銘柄
  "limit": 5
}
```

**レスポンス**:
```json
{
  "recommendations": [
    {
      "id": "sake_12345",
      "name": "八海山 純米吟醸",
      "brewery": "八海醸造",
      "prefecture": "新潟県",
      "type": "純米吟醸",
      "taste_stats": {
        "aroma": 4,
        "sweetness": 3,
        "acidity": 3,
        "bitterness": 2,
        "body": 4
      },
      "flavor_tags": ["フルーティー", "すっきり"],
      "similarity_score": 0.92,
      "reason": "新潟県つながり / すっきり / 似た味わい",
      "popularity": 1523 // 何人が登録したか
    },
    // ... 最大5件
  ]
}
```

---

### 3. ハイブリッドレコメンド実装

```dart
// lib/services/sake_recommendation_service.dart
class SakeRecommendationService {
  /// ハイブリッドレコメンド（既存 + 未知の銘柄）
  static Future<List<RecommendedSake>> getHybridRecommendations({
    required SakeItem target,
    required List<SakeItem> ownItems,
    int limit = 10,
  }) async {
    final recommendations = <RecommendedSake>[];
    
    // 1. 既存の銘柄から類似を検索（ローカル）
    final ownRecs = getRecommendations(
      target: target,
      allItems: ownItems,
      limit: 5, // 半分
    );
    recommendations.addAll(ownRecs);
    
    // 2. 未知の銘柄を検索（API経由）
    try {
      final unknownRecs = await _fetchUnknownRecommendations(
        target: target,
        excludeIds: ownItems.map((item) => item.id).toList(),
        limit: 5, // 残り半分
      );
      recommendations.addAll(unknownRecs);
    } catch (e) {
      debugPrint('⚠️ Failed to fetch unknown recommendations: $e');
      // エラー時は既存のみ表示
    }
    
    // 3. スコア順にソート
    recommendations.sort((a, b) => b.score.compareTo(a.score));
    
    return recommendations.take(limit).toList();
  }
  
  /// 未知の銘柄をAPIから取得
  static Future<List<RecommendedSake>> _fetchUnknownRecommendations({
    required SakeItem target,
    required List<String> excludeIds,
    int limit = 5,
  }) async {
    final response = await http.post(
      Uri.parse('https://posimai-nas.ts.net/api/v1/recommendations'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'target': {
          'prefecture': target.displayData.prefecture,
          'type': target.displayData.type,
          'taste_stats': target.hiddenSpecs.tasteStats,
          'flavor_tags': target.hiddenSpecs.flavorTags,
        },
        'exclude_ids': excludeIds,
        'limit': limit,
      }),
    );
    
    if (response.statusCode != 200) {
      throw Exception('API Error: ${response.statusCode}');
    }
    
    final data = jsonDecode(response.body);
    return (data['recommendations'] as List)
        .map((json) => RecommendedSake.fromJson(json))
        .toList();
  }
}
```

---

### 4. UI改善

**Before** (現在):
```dart
Text('五味チャート・タグ・酒蔵・産地から自動選出\n※現在は登録済みの銘柄からおすすめを表示')
```

**After** (Phase 2.0):
```dart
Text('五味チャート・タグ・酒蔵・産地から自動選出\n💡 あなたにおすすめの未知の銘柄も表示中')
```

**未知の銘柄のカードにバッジ表示**:
```dart
// 未知の銘柄には「🆕 未登録」バッジを表示
Stack(
  children: [
    Image.network(unknownSake.imageUrl),
    if (unknownSake.isUnknown)
      Positioned(
        top: 8,
        right: 8,
        child: Container(
          padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
          decoration: BoxDecoration(
            color: Colors.orange,
            borderRadius: BorderRadius.circular(12),
          ),
          child: Text('🆕 未登録', style: TextStyle(color: Colors.white, fontSize: 10)),
        ),
      ),
  ],
)
```

---

### 5. データ収集戦略

#### ステップ1: 既存ユーザーのデータを匿名化して収集

**実装**:
- アプリ初回起動時に「データ提供の同意」を取得
- ユーザーが登録した日本酒のデータをNASに送信
- `user_id`は匿名化（`device_info_plus`でデバイスIDをハッシュ化）

```dart
// lib/services/data_contribution_service.dart
class DataContributionService {
  static Future<void> uploadSakeData() async {
    final userProfile = ref.read(userProfileProvider);
    
    // ユーザーが同意していない場合は送信しない
    if (!userProfile.hasConsentedToDataSharing) return;
    
    final box = Hive.box<SakeItem>('sake_items');
    final allItems = box.values.toList();
    
    final payload = allItems.map((item) => {
      'name': item.displayData.name,
      'brewery': item.displayData.brewery,
      'prefecture': item.displayData.prefecture,
      'type': item.displayData.type,
      'taste_stats': item.hiddenSpecs.tasteStats,
      'flavor_tags': item.hiddenSpecs.flavorTags,
      'smv': item.hiddenSpecs.sakeMeterValue,
    }).toList();
    
    await http.post(
      Uri.parse('https://posimai-nas.ts.net/api/v1/data/upload'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'user_id': await _getAnonymizedUserId(),
        'sake_items': payload,
      }),
    );
  }
}
```

#### ステップ2: 外部データソースから収集

1. **日本酒造組合中央会のデータ**:
   - 公開されている酒蔵リスト
   - 都道府県別の銘柄情報

2. **酒蔵の公式Webサイト**:
   - 各銘柄の説明文
   - 五味チャートの情報（公開されている場合）

3. **Gemini APIによる自動収集**:
   - 日本酒の名前と蔵元から五味チャートを推定
   - コスト: 約5円/銘柄（画像なし、テキストのみ）

---

### 6. データベーススキーマ

**テーブル: `sake_master`**
```sql
CREATE TABLE sake_master (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(255) NOT NULL,
  brewery VARCHAR(255) NOT NULL,
  prefecture VARCHAR(50) NOT NULL,
  type VARCHAR(50), -- 純米吟醸, 大吟醸, etc.
  taste_aroma INT DEFAULT 3,
  taste_sweetness INT DEFAULT 3,
  taste_acidity INT DEFAULT 3,
  taste_bitterness INT DEFAULT 3,
  taste_body INT DEFAULT 3,
  flavor_tags TEXT[], -- {フルーティー, すっきり}
  smv DECIMAL(3,1), -- 日本酒度
  popularity INT DEFAULT 0, -- 何人が登録したか
  image_url TEXT,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_sake_prefecture ON sake_master(prefecture);
CREATE INDEX idx_sake_type ON sake_master(type);
CREATE INDEX idx_sake_popularity ON sake_master(popularity DESC);
```

**テーブル: `user_sake_data`**
```sql
CREATE TABLE user_sake_data (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id_hash VARCHAR(64) NOT NULL, -- 匿名化されたユーザーID
  sake_id UUID REFERENCES sake_master(id),
  taste_aroma INT,
  taste_sweetness INT,
  taste_acidity INT,
  taste_bitterness INT,
  taste_body INT,
  rating INT, -- 1-5
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_user_sake_user ON user_sake_data(user_id_hash);
CREATE INDEX idx_user_sake_sake ON user_sake_data(sake_id);
```

---

### 7. 実装工数見積もり

| 項目 | 工数 | 担当 |
|------|------|------|
| **PostgreSQLセットアップ** | 4時間 | Synology NAS |
| **APIサーバー構築（FastAPI）** | 8時間 | Python |
| **レコメンドアルゴリズム（サーバー側）** | 6時間 | Python |
| **Flutter側の統合** | 6時間 | Dart |
| **データ収集機能** | 4時間 | Dart + Python |
| **テスト・調整** | 4時間 | 総合 |
| **合計** | **32時間** | 約4日 |

---

### 8. リリーススケジュール

#### Phase 1.0（現在）
- ✅ ローカルレコメンドエンジン実装済み
- ✅ 既存の銘柄からの推薦

#### Phase 2.0（リリース後1ヶ月）
- 🔄 Synology NAS環境構築
- 🔄 日本酒マスターDB構築（初期データ: 100銘柄）
- 🔄 ハイブリッドレコメンド実装
- 🔄 データ収集機能実装

#### Phase 3.0（リリース後3ヶ月）
- 🔮 ソーシャル機能統合
- 🔮 「この銘柄を登録している人はこれも登録しています」
- 🔮 コミュニティの人気ランキング

---

### 9. ユーザーへの説明

**現在の表示**:
```
「あわせて飲みたい」
五味チャート・タグ・酒蔵・産地から自動選出
※現在は登録済みの銘柄からおすすめを表示
```

**Phase 2.0での表示**:
```
「あわせて飲みたい」
五味チャート・タグ・酒蔵・産地から自動選出
💡 あなたにおすすめの未知の銘柄も表示中
```

**バッジ**:
- 🆕 未登録: まだ登録していない銘柄
- 📚 登録済み: 自分のカードから推薦

---

## 📝 まとめ

**現状**: 既に優秀なローカルレコメンドエンジンが実装済み

**Phase 2.0での拡張**: 未知の銘柄を含めたハイブリッドレコメンドを実装予定

**実装工数**: 約32時間（4日）

**リリース時期**: リリース後1ヶ月（2026年3月頃）

---

**作成日**: 2026-01-22  
**作成者**: Cursor AI  
**レビュー**: 開発者（必要に応じて修正してください）
