from typing import Dict, Any from APIs.edamam import EdamamAPI from microwaveDishAnalyzer import MicrowaveDishAnalyzer from microwaveThermalEngine import MicrowaveThermalEngine, DishThermalState class MicrowaveCookPlanner: """Orchestrates Edamam API, Dish Analyzer, and Thermal Engine into a single workflow.""" def __init__(self, cm_per_pixel: float = 0.05): self.edamam_api = EdamamAPI() self.analyzer = MicrowaveDishAnalyzer(cm_per_pixel=cm_per_pixel) self.engine = MicrowaveThermalEngine() def _extract_edamam_data(self, edamam_resp: Dict[str, Any]) -> tuple[str, float, Dict[str, float]]: """Parses Edamam Vision response to extract label, total mass, and macronutrient grams.""" recipe = edamam_resp.get("combined", {}).get("recipe", {}) # Fallback to first dish if 'combined' is empty if not recipe and edamam_resp.get("dishes"): recipe = edamam_resp["dishes"][0].get("recipe", {}) label = recipe.get("label", "Unknown Dish") total_weight = float(recipe.get("totalWeight", 300.0)) # Default 300g fallback nutrients = recipe.get("totalNutrients", {}) # Extract macronutrients in grams (Edamam nutrient codes) fat_g = float(nutrients.get("FAT", {}).get("quantity", 0.0)) protein_g = float(nutrients.get("PROCNT", {}).get("quantity", 0.0)) carbs_g = float(nutrients.get("CHOCDF", {}).get("quantity", 0.0)) # Water is sometimes omitted in Edamam; infer remaining mass as water if missing if "WATER" in nutrients: water_g = float(nutrients["WATER"].get("quantity", 0.0)) else: water_g = max(0.0, total_weight - (fat_g + protein_g + carbs_g)) macros = { "water_g": water_g, "fat_g": fat_g, "protein_g": protein_g, "carbs_g": carbs_g, } return label, total_weight, macros def generate_plan( self, image_path: str, height_cm: float, initial_temp_c: float, microwave_wattage: int = 900, defrost_mode: bool = False ) -> Dict[str, Any]: """Main pipeline call to parse an image and return cooking parameters.""" # 1. Vision & Nutrient Analysis edamam_resp = self.edamam_api.analyze_dish_image(image_path) food_label, edamam_mass_g, macros = self._extract_edamam_data(edamam_resp) # 2. Geometric Volume Calculation vol_data = self.analyzer.estimate_volume( image_path=image_path, height_cm=height_cm, food_label=food_label ) # 3. Mass Cross-Validation & Density Check mass_data = self.analyzer.reconcile_mass( edamam_mass_g=edamam_mass_g, volume_cm3=vol_data["volume_cm3"], food_label=food_label ) final_mass_g = mass_data["final_mass_g"] # 4. Thermal State Creation thermal_state = DishThermalState( food_name=food_label, macronutrients=macros, estimated_mass_g=final_mass_g, initial_temp_c=initial_temp_c, volume_cm3=vol_data["volume_cm3"] ) # 5. Cook Plan Calculation cook_plan = self.engine.calculate_cook_plan( state=thermal_state, microwave_wattage=microwave_wattage, defrost_mode=defrost_mode ) # Return consolidated output return { "dish_name": food_label, "reconciled_mass_g": final_mass_g, "mass_validation_status": mass_data["status"], "cook_plan": cook_plan, "geometry": vol_data }