name: meal-planning description: >- Meal plans, recipes, and recipe favorites. Load this skill when the user asks for: plan repas / meal plan, recette / recipe, idée de plat / dish idea / suggestion, menu de la semaine / weekly menu, "génère un plan" / "create a plan", recette spécifique (e.g. "crêpes au saumon", "poulet curry"), gérer favoris / manage favorites — ajouter / save / "add to favorites", lister / list / "mes favoris" / "my favorites", chercher / find / "trouve la recette", supprimer / delete / remove a favorite. 2 routes: DB recipes (fast default) or custom LLM (when a specific dish is named).
category: planning
Meal Planning
Quand utiliser
- Demande de plan de repas (1+ jours)
- Demande de recette spécifique
- Récupération d'un plan existant
- Ajouter une recette en favoris / sauvegarder une recette
- Consulter ses recettes favorites / mes favoris / mes recettes sauvegardées
- Retrouver une recette favorite par nom
- Supprimer un favori / retirer une recette des favoris
Comportement agent
- TOUJOURS poser 1 question groupée avant de générer, sauf si l'utilisateur dit explicitement "go" / "lance" / "par défaut". La question doit couvrir :
- Durée : combien de jours ? (défaut 3)
- Batch cooking : préparer les mêmes plats plusieurs jours ? Si oui, combien ?
- Préférences repas : un plat ou ingrédient spécifique ? (petit-déj, déjeuner, dîner)
- Variété petit-déj : même petit-déj chaque jour ou varier ?
- Si "go", "lance", "génère" sans contexte → défauts automatiques, exécution immédiate
- Défaut : 3 jours, même petit-déj, repas variés, PAS de batch cooking (chaque repas est différent sauf le petit-déj). Ne PAS passer
batch_dayssauf si l'utilisateur le demande explicitement. - JAMAIS annoncer des recettes avant la génération
- JAMAIS improviser une recette en texte — toujours via script
- JAMAIS retenter un appel
run_skill_scriptsimplifié après une erreur de validation (Pydantic, JSON Schema). Si un param est rejeté, arrêter et dire à l'utilisateur : "Je n'ai pas pu appliquer ta demande [détail]. Veux-tu que je réessaie autrement ?" - JAMAIS décrire dans le chat des plats absents du JSON retourné par le script. Le résumé doit refléter exactement
meal_plan.days[]— pas la demande initiale de l'utilisateur si elle n'a pas été honorée. - Toute recette générée a un
recipe.id— le conserver pour la liste de courses
2 routes de génération
Route 1 : Recettes DB (défaut)
- Aucune préférence spécifique → le script pioche dans la recipe DB
- Rapide, pas d'appel LLM
- Scaling mathématique pour atteindre les macros cibles
Route 2 : Recettes custom (LLM)
- L'utilisateur mentionne un plat ou ingrédient spécifique → route LLM
- Passer via
meal_preferences→ déclenchegenerate_custom_recipe(Claude Haiku 4.5) - Recette sauvegardée en DB pour réutilisation future
Règle clé — recette custom + plan : quand l'utilisateur demande un plan incluant une recette custom générée dans la conversation (qui a un recipe_id), d'abord appeler add_favorite_recipe avec ce recipe_id, puis generate_week_plan avec une entrée meals[] scope="day" (ou scope="all") référençant le nom de la recette. Le pipeline retrouvera la recette via le lookup favoris.
Règle clé : toute mention de plat ou d'ingrédient spécifique = route custom.
- "fais-moi un plan" → route DB (pas de
meals[]) - "je veux de la baguette le matin" →
meals: [{meal_type: "petit-dejeuner", scope: "all", request: "baguette et chocolat chaud"}] - "risotto mardi" →
meals: [{meal_type: "dejeuner", scope: "day", day: "Mardi", request: "risotto"}]
Scripts (appels agent)
| Script | Quand |
|---|---|
generate_week_plan |
Générer un plan (1-7 jours) |
generate_custom_recipe |
Recette unique sur demande |
fetch_stored_meal_plan |
Récupérer un plan existant |
add_favorite_recipe |
Ajouter une recette aux favoris de l'utilisateur |
get_user_favorites |
Lister les recettes favorites avec macros + ingredients (filtre optionnel par nom) |
remove_favorite_recipe |
Supprimer une recette des favoris |
# Plan par défaut (1 jour)
run_skill_script("meal-planning", "generate_week_plan", {})
# Plan 7 jours avec préférence globale (même plat TOUS les jours)
run_skill_script("meal-planning", "generate_week_plan", {
"num_days": 7,
"meals": [
{"meal_type": "petit-dejeuner", "scope": "all",
"request": "omelette aux oeufs et épinards"}
]
})
# Plan avec demande pour un JOUR + REPAS spécifique
run_skill_script("meal-planning", "generate_week_plan", {
"num_days": 3,
"meals": [
{"meal_type": "dejeuner", "scope": "day", "day": "Mardi",
"request": "poulet ratatouille pommes de terre"},
{"meal_type": "diner", "scope": "day", "day": "Mercredi",
"request": "saumon grillé avec légumes"}
]
})
# Batch cooking 3 jours avec plat précis (résout Bug C)
run_skill_script("meal-planning", "generate_week_plan", {
"num_days": 7,
"meals": [
{"meal_type": "dejeuner", "scope": "batch", "days": 3,
"request": "poulet ratatouille"}
]
})
# Combiné : matin global + batch midi + override soir
run_skill_script("meal-planning", "generate_week_plan", {
"num_days": 7,
"meals": [
{"meal_type": "petit-dejeuner", "scope": "all", "request": "porridge protéiné"},
{"meal_type": "dejeuner", "scope": "batch", "days": 3, "request": "poulet curry"},
{"meal_type": "diner", "scope": "day", "day": "Vendredi", "request": "saumon grillé"}
]
})
# Recette sur demande
run_skill_script("meal-planning", "generate_custom_recipe", {
"recipe_request": "salade niçoise protéinée",
"meal_type": "dejeuner"
})
# ENUM meal_type : "petit-dejeuner" | "dejeuner" | "diner" | "collation"
# After: include the `ui_marker` field from the response verbatim at END of your text
# Récupérer un plan existant
run_skill_script("meal-planning", "fetch_stored_meal_plan", {"week_start": "2026-02-23"})
# Ajouter une recette aux favoris (recipe_id vient de generate_custom_recipe)
run_skill_script("meal-planning", "add_favorite_recipe", {
"recipe_id": "abc-123-uuid",
"notes": "Ma recette d'escalope maison"
})
# Lister tous les favoris
run_skill_script("meal-planning", "get_user_favorites", {})
# Chercher un favori par nom
run_skill_script("meal-planning", "get_user_favorites", {"name": "poulet grillé"})
# Supprimer par nom
run_skill_script("meal-planning", "remove_favorite_recipe", {"recipe_name": "poulet grillé"})
# Supprimer par ID (retourné par get_user_favorites)
run_skill_script("meal-planning", "remove_favorite_recipe", {"favorite_id": "abc-123-uuid"})
Paramètres generate_week_plan
| Param | Type | Défaut | Description |
|---|---|---|---|
num_days |
int | 1 | Nombre de jours (1-7). 7 uniquement si demandé |
start_date |
str | aujourd'hui (ou lundi si num_days≥7) | Format YYYY-MM-DD |
meals |
list[dict] | [] | Specs repas par scope. Méthode recommandée. Voir tableau ci-dessous |
meal_structure |
str | auto | 3_consequent_meals (3 repas, 0 collation), 3_meals_1_preworkout (3 repas + 1 collation), 3_meals_2_snacks, 4_meals. Auto-détecté si omis (≥2500 kcal → ajoute collation) |
Structure d'une entrée meals[]
| Clé | Type | Obligatoire | Description |
|---|---|---|---|
meal_type |
str | oui | Slug : petit-dejeuner / dejeuner / diner / collation |
scope |
str | oui | all (tous les jours), day (un jour précis), batch (N jours consécutifs), auto (IA pioche, équivalent à omettre l'entrée) |
request |
str | non | Description du plat. Omis ou scope="auto" → la DB pioche |
day |
str | si scope="day" |
Jour FR (Lundi…Dimanche, ou Demain / Aujourd'hui / Après-demain) |
days |
int | si scope="batch" |
Nombre de jours consécutifs (taille du block) |
start |
str | non (scope="batch" uniquement) |
Jour de début du block batch (défaut : jour 0 du plan) |
Priorité de résolution : day > batch > all > auto. Une entrée plus spécifique remplace silencieusement une plus large.
Conflits : deux entrées explicites couvrant le même (jour, meal_type) avec des plats différents → erreur MEAL_SPEC_CONFLICT. Ex : scope="day" Mardi midi "X" + scope="batch" midi days=3 "Y" → conflit.
Annexe — Paramètres legacy (dépréciés, encore acceptés)
Encore fonctionnels en attendant la migration complète. Si le nouveau schéma meals[] est aussi fourni, le legacy est ignoré avec un warning. Préférer meals[].
| Param legacy | Équivalent meals[] |
|---|---|
meal_preferences: {"petit-dejeuner": "X"} |
[{meal_type: "petit-dejeuner", scope: "all", request: "X"}] |
custom_requests: {"Mardi": {"dejeuner": "X"}} |
[{meal_type: "dejeuner", scope: "day", day: "Mardi", request: "X"}] |
batch_days: 3 + meal_preferences: {"dejeuner": "X"} |
[{meal_type: "dejeuner", scope: "batch", days: 3, request: "X"}] |
notes: "Mardi risotto" |
[{meal_type: "dejeuner", scope: "day", day: "Mardi", request: "risotto"}] |
vary_breakfast: false (avec petit-déj fixé) |
Implicite : scope: "all" sur petit-déjeuner |
Paramètres add_favorite_recipe
| Param | Type | Obligatoire | Description |
|---|---|---|---|
recipe_id |
str | oui | UUID de la recette (retourné par generate_custom_recipe) |
notes |
str | non | Note libre sur la recette |
Paramètres get_user_favorites
| Param | Type | Obligatoire | Description |
|---|---|---|---|
name |
str | non | Filtre par nom (recherche fuzzy par mots) |
Paramètres remove_favorite_recipe
| Param | Type | Obligatoire | Description |
|---|---|---|---|
favorite_id |
str | non* | UUID du favori (retourné par get_user_favorites) |
recipe_name |
str | non* | Nom de la recette (recherche fuzzy). *Au moins un des deux requis. |
Paramètres generate_custom_recipe
| Param | Type | Obligatoire | Description |
|---|---|---|---|
recipe_request |
str | oui | Description de la recette souhaitée |
meal_type |
str | oui | Slug : petit-dejeuner / dejeuner / diner / collation |
num_servings |
int | non | Nombre de portions (défaut 1, borné 1-12). Quantités TOTALES pour N portions ; macros stockées PAR PORTION (total / N). |
ingredient_list |
list[str] | non | Ingrédients à utiliser en priorité (ex. inventaire frigo). Vide → aucune contrainte. |
Conversion préférences utilisateur → meals[]
Préférence globale (tous les jours) → scope: "all"
- "omelette le matin" →
[{meal_type: "petit-dejeuner", scope: "all", request: "omelette aux oeufs"}] - "du poisson le soir" →
[{meal_type: "diner", scope: "all", request: "plat à base de poisson"}] - "go" / "par défaut" →
meals: [](ou paramètre omis → défauts automatiques)
Demande pour un jour précis → scope: "day"
- "mardi midi poulet ratatouille" →
[{meal_type: "dejeuner", scope: "day", day: "Mardi", request: "poulet ratatouille"}] - "jeudi soir saumon grillé" →
[{meal_type: "diner", scope: "day", day: "Jeudi", request: "saumon grillé"}] - "samedi matin pancakes" →
[{meal_type: "petit-dejeuner", scope: "day", day: "Samedi", request: "pancakes protéinés"}] - "demain midi poulet grillé" →
[{meal_type: "dejeuner", scope: "day", day: "Demain", request: "poulet grillé"}](résolu automatiquement)
Batch cooking → scope: "batch"
- "3 jours du même midi" (sans plat précisé) →
[{meal_type: "dejeuner", scope: "batch", days: 3}] - "3 jours de poulet ratatouille midi" →
[{meal_type: "dejeuner", scope: "batch", days: 3, request: "poulet ratatouille"}] - "batch cooking 3 jours midi + Mardi je veux du poulet" →
[{meal_type: "dejeuner", scope: "batch", days: 3, request: "poulet"}](le batch capture la demande de Mardi sur tout le block)
Structure repas → meal_structure
- "3 repas sans collation" / "supprime la collation" / "juste 3 repas" →
meal_structure: "3_consequent_meals" - "ajoute une collation pre-workout" →
meal_structure: "3_meals_1_preworkout" - "4 repas egaux" →
meal_structure: "4_meals"
Règle clé : un seul tableau meals[] exprime toutes les préférences ; chaque entrée a un scope explicite. Ne pas mélanger avec les anciens kwargs (meal_preferences, custom_requests, notes, batch_days).
Clés jour : Lundi, Mardi, Mercredi, Jeudi, Vendredi, Samedi, Dimanche. Aussi accepté : Demain, Aujourd'hui, Après-demain (résolu automatiquement).
Clés repas : petit-dejeuner, dejeuner, diner, collation (slugs DB, sans accents).
Mots-clés repas : "matin/petit-déj" → petit-dejeuner, "midi/déjeuner" → dejeuner, "soir/dîner" → diner, "collation/goûter" → collation.
Présentation du résultat — FORMAT OBLIGATOIRE
Le script retourne un JSON avec meal_plan_id, meal_plan.days[], weekly_summary. Tu DOIS utiliser ce JSON exactement.
Étape 1 : Résumé court (3-4 lignes max)
- Nombre de jours, calories moyennes (
weekly_summary.average_calories), protéines moyennes - Sécurité allergènes : validation passée ✅
Étape 2 : Résumé par jour (texte simple)
Pour chaque jour, afficher un résumé compact :
Lundi (2026-03-09) — 2 964 kcal, 172g protéines
- Petit-déjeuner : Omelette aux épinards
- Déjeuner : Poulet grillé aux légumes
- Dîner : Saumon teriyaki
NE PAS émettre de marqueurs <!--UI:DayPlanCard:...--> — le détail complet
(ingrédients, macros, instructions) est sur la page dédiée via le lien ci-dessous.
Étape 3 : Lien plan complet — OBLIGATOIRE
Copie-colle le champ plan_link du JSON tel quel, seul sur une ligne, sans emoji ni mise en forme :
{response.plan_link}
NE PAS entourer de **bold**, 📖, ni de texte additionnel sur la même ligne.
Sans ce lien, l'utilisateur ne peut pas accéder au plan ni l'ajouter en favori.
Étape 4 : QuickReplyChips
<!--UI:QuickReplyChips:{"options":[{"label":"Liste de courses","value":"generate_grocery_list"},{"label":"Régénérer le plan","value":"regenerate_plan"}]}-->
CE QUI EST INTERDIT
- Émettre des marqueurs
<!--UI:DayPlanCard:...-->— le détail est sur la page dédiée - Omettre le lien
/plans/{meal_plan_id}— sinon pas de favori possible - Inventer des valeurs — tout vient du JSON retourné par le script
MealCard — OBLIGATOIRE pour toute recette
Chaque fois que tu proposes une recette (individuelle OU dans un plan), tu DOIS émettre un marqueur MealCard à la fin de ta réponse. Les scripts de recette retournent un champ ui_marker — copie-le tel quel. Sans ce marqueur, la recette n'est pas sauvegardable.
[ton texte avec instructions, ingrédients, etc.]
<!--UI:MealCard:{"meal_type":"dejeuner","recipe_name":"Poulet rôti aux herbes","calories":650,"macros":{"protein_g":52,"carbs_g":68,"fat_g":15},"prep_time":35,"ingredients":["Filet de poulet 200g","Riz complet 80g","Brocoli 150g"],"instructions":"1. Préchauffer le four à 200°C\n2. Assaisonner le poulet\n3. Cuire 25 min\n4. Cuire le riz et le brocoli en parallèle\n5. Dresser l'assiette"}-->
Sécurité allergènes
Tolérance zéro — filtrage Python automatique en 2 couches.
Voir references/allergen_families.md.