195 lines
4.9 KiB
Dart
195 lines
4.9 KiB
Dart
// さけのわAPI データモデル
|
||
// API Documentation: https://muro.sakenowa.com/sakenowa-data/api/
|
||
|
||
/// 銘柄データ
|
||
class SakenowaBrand {
|
||
final int id;
|
||
final String name;
|
||
final int breweryId;
|
||
|
||
const SakenowaBrand({
|
||
required this.id,
|
||
required this.name,
|
||
required this.breweryId,
|
||
});
|
||
|
||
factory SakenowaBrand.fromJson(Map<String, dynamic> json) {
|
||
return SakenowaBrand(
|
||
id: json['id'] as int,
|
||
name: json['name'] as String,
|
||
breweryId: json['breweryId'] as int,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 蔵元データ
|
||
class SakenowaBrewery {
|
||
final int id;
|
||
final String name;
|
||
final int areaId;
|
||
|
||
const SakenowaBrewery({
|
||
required this.id,
|
||
required this.name,
|
||
required this.areaId,
|
||
});
|
||
|
||
factory SakenowaBrewery.fromJson(Map<String, dynamic> json) {
|
||
return SakenowaBrewery(
|
||
id: json['id'] as int,
|
||
name: json['name'] as String,
|
||
areaId: json['areaId'] as int,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 地域データ
|
||
class SakenowaArea {
|
||
final int id;
|
||
final String name;
|
||
|
||
const SakenowaArea({
|
||
required this.id,
|
||
required this.name,
|
||
});
|
||
|
||
factory SakenowaArea.fromJson(Map<String, dynamic> json) {
|
||
return SakenowaArea(
|
||
id: json['id'] as int,
|
||
name: json['name'] as String,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 6軸フレーバーチャート
|
||
/// f1: 華やか, f2: 芳醇, f3: 重厚, f4: 穏やか, f5: 軽快, f6: ドライ
|
||
class SakenowaFlavorChart {
|
||
final int brandId;
|
||
final double f1; // 華やか
|
||
final double f2; // 芳醇
|
||
final double f3; // 重厚
|
||
final double f4; // 穏やか
|
||
final double f5; // 軽快
|
||
final double f6; // ドライ
|
||
|
||
const SakenowaFlavorChart({
|
||
required this.brandId,
|
||
required this.f1,
|
||
required this.f2,
|
||
required this.f3,
|
||
required this.f4,
|
||
required this.f5,
|
||
required this.f6,
|
||
});
|
||
|
||
factory SakenowaFlavorChart.fromJson(Map<String, dynamic> json) {
|
||
return SakenowaFlavorChart(
|
||
brandId: json['brandId'] as int,
|
||
f1: (json['f1'] as num).toDouble(),
|
||
f2: (json['f2'] as num).toDouble(),
|
||
f3: (json['f3'] as num).toDouble(),
|
||
f4: (json['f4'] as num).toDouble(),
|
||
f5: (json['f5'] as num).toDouble(),
|
||
f6: (json['f6'] as num).toDouble(),
|
||
);
|
||
}
|
||
|
||
/// 軸名のラベル
|
||
static const axisLabels = {
|
||
'f1': '華やか',
|
||
'f2': '芳醇',
|
||
'f3': '重厚',
|
||
'f4': '穏やか',
|
||
'f5': '軽快',
|
||
'f6': 'ドライ',
|
||
};
|
||
|
||
/// レーダーチャート用のリストに変換
|
||
List<double> toList() => [f1, f2, f3, f4, f5, f6];
|
||
|
||
/// 五味チャート形式に変換(互換性維持)
|
||
/// 既存の五味チャート(aroma, sweetness, acidity, bitterness, body)に対応
|
||
Map<String, double> toFiveAxisTaste() {
|
||
return {
|
||
'aroma': f1, // 華やか → 香り
|
||
'sweetness': (f2 + f4) / 2, // 芳醇・穏やか → 甘み
|
||
'acidity': f5, // 軽快 → 酸味
|
||
'bitterness': f6, // ドライ → キレ
|
||
'body': f3, // 重厚 → コク
|
||
};
|
||
}
|
||
|
||
/// 五味チャート形式(1-5スケール)に変換
|
||
/// Gemini AI解析結果との互換性維持
|
||
Map<String, int> toFiveAxisTasteInt() {
|
||
final fiveAxis = toFiveAxisTaste();
|
||
return fiveAxis.map((k, v) => MapEntry(k, (v * 5).round().clamp(1, 5)));
|
||
}
|
||
|
||
/// レーダーチャート表示用(6軸、ラベル付き)
|
||
List<MapEntry<String, double>> toRadarData() {
|
||
return [
|
||
MapEntry(axisLabels['f1']!, f1),
|
||
MapEntry(axisLabels['f2']!, f2),
|
||
MapEntry(axisLabels['f3']!, f3),
|
||
MapEntry(axisLabels['f4']!, f4),
|
||
MapEntry(axisLabels['f5']!, f5),
|
||
MapEntry(axisLabels['f6']!, f6),
|
||
];
|
||
}
|
||
|
||
/// 英語ラベルでのレーダーチャート用
|
||
List<MapEntry<String, double>> toRadarDataEn() {
|
||
return [
|
||
MapEntry('Elegant', f1), // 華やか
|
||
MapEntry('Rich', f2), // 芳醇
|
||
MapEntry('Full', f3), // 重厚
|
||
MapEntry('Mild', f4), // 穏やか
|
||
MapEntry('Light', f5), // 軽快
|
||
MapEntry('Dry', f6), // ドライ
|
||
];
|
||
}
|
||
}
|
||
|
||
/// ランキングエントリ
|
||
class SakenowaRanking {
|
||
final int rank;
|
||
final int brandId;
|
||
final int? areaId;
|
||
final double score;
|
||
|
||
const SakenowaRanking({
|
||
required this.rank,
|
||
required this.brandId,
|
||
this.areaId,
|
||
required this.score,
|
||
});
|
||
|
||
factory SakenowaRanking.fromJson(Map<String, dynamic> json) {
|
||
return SakenowaRanking(
|
||
rank: json['rank'] as int,
|
||
brandId: json['brandId'] as int,
|
||
areaId: json['areaId'] as int?,
|
||
score: (json['score'] as num).toDouble(),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// フレーバータグ
|
||
class SakenowaFlavorTag {
|
||
final int id;
|
||
final String tag;
|
||
|
||
const SakenowaFlavorTag({
|
||
required this.id,
|
||
required this.tag,
|
||
});
|
||
|
||
factory SakenowaFlavorTag.fromJson(Map<String, dynamic> json) {
|
||
return SakenowaFlavorTag(
|
||
id: json['id'] as int,
|
||
tag: json['tag'] as String,
|
||
);
|
||
}
|
||
}
|