ponshu-room-lite/RECOMMENDATION_EXPANSION_PL...

407 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 「あわせて飲みたい」機能拡張計画
**作成日**: 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
**レビュー**: 開発者(必要に応じて修正してください)