feat: implement recipe analysis service and data models
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
- Added RecipeAnalysisService to handle recipe ingredient analysis, including methods for checking ingredient availability and calculating quantities. - Introduced new TypeScript definitions for recipe analysis results, including ingredient status and summary. - Created corresponding Dart models for recipe analysis, including RecipeIngredientAnalysis, RecipeAnalysisSummary, and RecipeShoppingCandidate. - Updated Flutter UI to reflect changes in ingredient availability status. - Fixed color opacity issue in recipe image card.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
enum RecipeIngredientAvailabilityStatus {
|
||||
exactMatch,
|
||||
coveredByPantry,
|
||||
substitutable,
|
||||
missing,
|
||||
}
|
||||
|
||||
class RecipeIngredientAnalysis {
|
||||
final int ingredientId;
|
||||
final String rawName;
|
||||
final double quantity;
|
||||
final String unit;
|
||||
final String? note;
|
||||
final RecipeIngredientAvailabilityStatus status;
|
||||
final int? matchedProductId;
|
||||
final String? matchedProductName;
|
||||
final String? source;
|
||||
final double availableQuantity;
|
||||
final double missingQuantity;
|
||||
|
||||
const RecipeIngredientAnalysis({
|
||||
required this.ingredientId,
|
||||
required this.rawName,
|
||||
required this.quantity,
|
||||
required this.unit,
|
||||
this.note,
|
||||
required this.status,
|
||||
this.matchedProductId,
|
||||
this.matchedProductName,
|
||||
this.source,
|
||||
required this.availableQuantity,
|
||||
required this.missingQuantity,
|
||||
});
|
||||
|
||||
factory RecipeIngredientAnalysis.fromJson(Map<String, dynamic> json) {
|
||||
final rawStatus = json['status'] as String? ?? 'missing';
|
||||
final status = switch (rawStatus) {
|
||||
'exact_match' => RecipeIngredientAvailabilityStatus.exactMatch,
|
||||
'covered_by_pantry' => RecipeIngredientAvailabilityStatus.coveredByPantry,
|
||||
'substitutable' => RecipeIngredientAvailabilityStatus.substitutable,
|
||||
_ => RecipeIngredientAvailabilityStatus.missing,
|
||||
};
|
||||
|
||||
return RecipeIngredientAnalysis(
|
||||
ingredientId: (json['ingredientId'] as num?)?.toInt() ?? 0,
|
||||
rawName: (json['rawName'] as String? ?? '').trim(),
|
||||
quantity: (json['quantity'] as num? ?? 0).toDouble(),
|
||||
unit: json['unit'] as String? ?? '',
|
||||
note: json['note'] as String?,
|
||||
status: status,
|
||||
matchedProductId: (json['matchedProductId'] as num?)?.toInt(),
|
||||
matchedProductName: json['matchedProductName'] as String?,
|
||||
source: json['source'] as String?,
|
||||
availableQuantity: (json['availableQuantity'] as num? ?? 0).toDouble(),
|
||||
missingQuantity: (json['missingQuantity'] as num? ?? 0).toDouble(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RecipeAnalysisSummary {
|
||||
final int exactCount;
|
||||
final int pantryCount;
|
||||
final int substituteCount;
|
||||
final int missingCount;
|
||||
|
||||
const RecipeAnalysisSummary({
|
||||
required this.exactCount,
|
||||
required this.pantryCount,
|
||||
required this.substituteCount,
|
||||
required this.missingCount,
|
||||
});
|
||||
|
||||
factory RecipeAnalysisSummary.fromJson(Map<String, dynamic> json) {
|
||||
return RecipeAnalysisSummary(
|
||||
exactCount: (json['exactCount'] as num?)?.toInt() ?? 0,
|
||||
pantryCount: (json['pantryCount'] as num?)?.toInt() ?? 0,
|
||||
substituteCount: (json['substituteCount'] as num?)?.toInt() ?? 0,
|
||||
missingCount: (json['missingCount'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RecipeShoppingCandidate {
|
||||
final int ingredientId;
|
||||
final String rawName;
|
||||
final double quantity;
|
||||
final String unit;
|
||||
final double missingQuantity;
|
||||
|
||||
const RecipeShoppingCandidate({
|
||||
required this.ingredientId,
|
||||
required this.rawName,
|
||||
required this.quantity,
|
||||
required this.unit,
|
||||
required this.missingQuantity,
|
||||
});
|
||||
|
||||
factory RecipeShoppingCandidate.fromJson(Map<String, dynamic> json) {
|
||||
return RecipeShoppingCandidate(
|
||||
ingredientId: (json['ingredientId'] as num?)?.toInt() ?? 0,
|
||||
rawName: (json['rawName'] as String? ?? '').trim(),
|
||||
quantity: (json['quantity'] as num? ?? 0).toDouble(),
|
||||
unit: json['unit'] as String? ?? '',
|
||||
missingQuantity: (json['missingQuantity'] as num? ?? 0).toDouble(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RecipeAnalysis {
|
||||
final int recipeId;
|
||||
final List<RecipeIngredientAnalysis> ingredients;
|
||||
final RecipeAnalysisSummary summary;
|
||||
final List<RecipeShoppingCandidate> shoppingListCandidates;
|
||||
|
||||
const RecipeAnalysis({
|
||||
required this.recipeId,
|
||||
required this.ingredients,
|
||||
required this.summary,
|
||||
required this.shoppingListCandidates,
|
||||
});
|
||||
|
||||
factory RecipeAnalysis.fromJson(Map<String, dynamic> json) {
|
||||
final rawIngredients = json['ingredients'] as List<dynamic>? ?? const [];
|
||||
final rawShopping = json['shoppingListCandidates'] as List<dynamic>? ?? const [];
|
||||
|
||||
return RecipeAnalysis(
|
||||
recipeId: (json['recipeId'] as num?)?.toInt() ?? 0,
|
||||
ingredients: rawIngredients
|
||||
.map((e) => RecipeIngredientAnalysis.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
summary: RecipeAnalysisSummary.fromJson(
|
||||
json['summary'] as Map<String, dynamic>? ?? const {},
|
||||
),
|
||||
shoppingListCandidates: rawShopping
|
||||
.map((e) => RecipeShoppingCandidate.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user