/// Nutritional macros for a single recipe serving. /// /// Pure Dart (no framework imports) so the value can travel from the /// schema.org scraper through the drift-backed repository into the /// presentation layer without leaking infrastructure concerns into the /// domain. class MacroProfile { const MacroProfile({ this.calories = 0, this.proteinGrams = 0, this.carbsGrams = 0, this.fatGrams = 0, }); /// Energy content in kilocalories per serving. final int calories; /// Protein in grams; the primary lean-mass tracking metric for GLP-1 /// therapy (muscle-preservation target). final double proteinGrams; /// Carbohydrate content in grams per serving. final double carbsGrams; /// Fat content in grams per serving. final double fatGrams; /// `true` when no macro could be extracted (e.g. OpenGraph-only imports). bool get isEmpty => calories == 0 && proteinGrams == 0 && carbsGrams == 0 && fatGrams == 0; MacroProfile copyWith({ int? calories, double? proteinGrams, double? carbsGrams, double? fatGrams, }) => MacroProfile( calories: calories ?? this.calories, proteinGrams: proteinGrams ?? this.proteinGrams, carbsGrams: carbsGrams ?? this.carbsGrams, fatGrams: fatGrams ?? this.fatGrams, ); @override bool operator ==(Object other) => other is MacroProfile && other.calories == calories && other.proteinGrams == proteinGrams && other.carbsGrams == carbsGrams && other.fatGrams == fatGrams; @override int get hashCode => Object.hash(calories, proteinGrams, carbsGrams, fatGrams); @override String toString() => 'MacroProfile($calories kcal, P $proteinGrams g, ' 'C $carbsGrams g, F $fatGrams g)'; }