98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
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
|
|
) -> 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
|
|
)
|
|
|
|
# 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
|
|
} |