UI web d'admin + garde-fou recettes premium (supplément hors abonnement)

- Refus des recettes payantes (chargeSetting) à la sélection, override allow_premium
- Recipe.surcharge_cents/is_premium exposés dans summary(); propose() les exclut
- hellofresh/webui.py : page d'admin + API JSON montées sur FastMCP (/, /api/*)
  édition à chaud des excludes et préférences (liked/disliked)
This commit is contained in:
jerem
2026-06-18 18:07:12 +02:00
parent a14ce4664b
commit 61ee7f02a4
6 changed files with 358 additions and 10 deletions

View File

@@ -20,11 +20,15 @@ from mcp.server.fastmcp import FastMCP
load_dotenv()
from hellofresh import api, auth, filter as hf_filter # noqa: E402
from hellofresh import api, auth, filter as hf_filter, webui # noqa: E402
PORT = int(os.environ.get("ANTICOCO_PORT", "9200"))
mcp = FastMCP("AntiCoco", host="0.0.0.0", port=PORT)
# Interface web d'admin (édition à chaud des excludes/préférences) sur le même port.
# MCP reste servi sur /mcp ; l'UI est sur / (cf. hellofresh/webui.py).
webui.register(mcp)
@mcp.tool()
async def hf_auth_status() -> dict:
@@ -159,21 +163,26 @@ async def hf_propose(week: str = "", count: int = 0) -> dict:
recipes = client.get_menu(w)
safe = hf_filter.propose(recipes, count=count or None)
excluded = [r.summary() for r in recipes if r.contains_excluded]
premium = [r.summary() for r in recipes if r.is_premium and not r.contains_excluded]
return {
"week": w,
"proposed": [r.summary() for r in safe],
"excluded_for_coco_etc": excluded,
"note": "Aucune écriture effectuée. Confirme avec hf_confirm_selection(week, recipe_ids).",
"premium_extra_cost": premium,
"note": "Aucune écriture effectuée. Les recettes premium (supplément) sont exclues "
"de la proposition. Confirme avec hf_confirm_selection(week, recipe_ids).",
}
return await anyio.to_thread.run_sync(_impl)
@mcp.tool()
async def hf_confirm_selection(week: str, recipe_ids: list[str], dry_run: bool = False) -> dict:
async def hf_confirm_selection(week: str, recipe_ids: list[str], dry_run: bool = False,
allow_premium: bool = False) -> dict:
"""ÉCRIT la sélection de recettes dans la box de la semaine (après confirmation).
Garde-fou : refuse toute recette contenant un ingrédient exclu (coco !).
Garde-fous : refuse toute recette contenant un ingrédient exclu (coco !) ET toute
recette payante hors abonnement (premium, supplément) sauf si `allow_premium=True`.
`dry_run=True` : construit et renvoie la requête sans l'envoyer (vérification).
"""
def _impl() -> dict:
@@ -188,6 +197,19 @@ async def hf_confirm_selection(week: str, recipe_ids: list[str], dry_run: bool =
"error": "Sélection refusée : recette(s) avec ingrédient exclu (coco ?).",
"offending_ids": bad,
}
premium = [rid for rid in recipe_ids
if rid in by_id and by_id[rid].is_premium]
if premium and not allow_premium:
return {
"ok": False,
"error": "Sélection refusée : recette(s) payante(s) hors abonnement (supplément). "
"Repasse allow_premium=True pour accepter le surcoût.",
"premium_recipes": [
{"id": rid, "name": by_id[rid].name,
"surcharge_eur": round(by_id[rid].surcharge_cents / 100, 2)}
for rid in premium
],
}
unknown = [rid for rid in recipe_ids if rid not in by_id]
if unknown:
return {"ok": False, "error": "Recette(s) inconnue(s) pour cette semaine.",