Files
Nils-Johan Gynther 9fe85a719c
Test Suite / test (24.15.0) (push) Has been cancelled
feat: implement recipe analysis service and data models
- 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.
2026-05-06 07:54:03 +02:00

140 lines
4.5 KiB
Dart

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(),
);
}
}