package openai import ( "context" "fmt" "sync" "github.com/food-ai/backend/internal/adapters/ai" ) // caloricFractions maps each meal type to its share of the daily calorie budget. var caloricFractions = map[string]float64{ "breakfast": 0.25, "second_breakfast": 0.10, "lunch": 0.35, "afternoon_snack": 0.10, "dinner": 0.20, "snack": 0.10, } // defaultMealTypes is used when MenuRequest.MealTypes is nil. var defaultMealTypes = []string{"breakfast", "lunch", "dinner"} // GenerateMenu produces a meal plan by issuing one parallel GenerateRecipes call per meal // type. The number of recipes per type equals the number of days requested. func (c *Client) GenerateMenu(ctx context.Context, req ai.MenuRequest) ([]ai.DayPlan, error) { mealTypes := req.MealTypes if len(mealTypes) == 0 { mealTypes = defaultMealTypes } days := req.Days if len(days) == 0 { days = make([]int, 7) for dayIndex := range days { days[dayIndex] = dayIndex + 1 } } recipeCount := len(days) type mealResult struct { recipes []ai.Recipe err error } results := make([]mealResult, len(mealTypes)) var wg sync.WaitGroup for slotIndex, mealType := range mealTypes { wg.Add(1) go func(idx int, mealTypeName string) { defer wg.Done() fraction, ok := caloricFractions[mealTypeName] if !ok { fraction = 0.15 } mealCalories := int(float64(req.DailyCalories) * fraction) recipes, generateError := c.GenerateRecipes(ctx, ai.RecipeRequest{ UserGoal: req.UserGoal, DailyCalories: mealCalories * recipeCount, Restrictions: req.Restrictions, CuisinePrefs: req.CuisinePrefs, Count: recipeCount, AvailableProducts: req.AvailableProducts, Lang: req.Lang, }) results[idx] = mealResult{recipes, generateError} }(slotIndex, mealType) } wg.Wait() for slotIndex, result := range results { if result.err != nil { return nil, fmt.Errorf("generate %s: %w", mealTypes[slotIndex], result.err) } if len(result.recipes) == 0 { return nil, fmt.Errorf("no %s recipes returned", mealTypes[slotIndex]) } // Pad to exactly recipeCount by repeating the last recipe. for len(results[slotIndex].recipes) < recipeCount { last := results[slotIndex].recipes[len(results[slotIndex].recipes)-1] results[slotIndex].recipes = append(results[slotIndex].recipes, last) } } dayPlans := make([]ai.DayPlan, recipeCount) for dayIndex, dayOfWeek := range days { meals := make([]ai.MealEntry, len(mealTypes)) for slotIndex, mealType := range mealTypes { meals[slotIndex] = ai.MealEntry{ MealType: mealType, Recipe: results[slotIndex].recipes[dayIndex], } } dayPlans[dayIndex] = ai.DayPlan{Day: dayOfWeek, Meals: meals} } return dayPlans, nil }