feat(meal-planner): structured output for get_meal_plan (v0.10.1)

- Map mplistinterval response to clean JSON list (id, date, type, name,
  recipe_id, can_update, can_delete) — no more raw dump
- SPEC.md: document verified mplistinterval response structure
- Fix two pre-existing ruff SIM warnings (SIM102, SIM105)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-17 11:13:22 +02:00
parent 500ad278a4
commit a7b21c1ede
7 changed files with 68 additions and 21 deletions
+35 -6
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import contextlib
import json
import logging
import os
@@ -1826,10 +1827,8 @@ def get_recipe_categories() -> str:
"""
# Get the family ID to construct standard category IDs
family_id: str | None = None
try:
with contextlib.suppress(RuntimeError):
family_id = _get_family_id()
except RuntimeError:
pass
# Collect all category IDs: standard categories + categories found in recipes
seen_ids: set[str] = set()
@@ -2224,15 +2223,45 @@ def get_meal_plan(date_from: str, date_to: str) -> str:
date_to: End date in ISO format (e.g. ``"2026-04-19"``).
Returns:
Raw JSON response from the API for initial verification.
JSON list of meal plan entries with keys: id, date, type, name,
recipe_id, can_update, can_delete. ``type`` is one of
``BREAKFAST``, ``LUNCH``, ``SNACK``, ``DINNER``.
``recipe_id`` is the linked recipe metaId or ``null`` when the
entry is a free-text dish (not linked to a recipe).
Returns an error message string on failure.
"""
try:
data = _authenticated_call("mplistinterval", {"from": date_from, "to": date_to})
except RuntimeError as exc:
return f"Error: {exc}"
# Return raw JSON for verification — response structure not yet known
return json.dumps(data, ensure_ascii=False, indent=2)
try:
raw_list = data["a00"]["r"]["r"]["list"]
if not isinstance(raw_list, list):
raise TypeError("a00.r.r.list is not a list")
except (KeyError, TypeError):
return json.dumps(
{"warning": "Unexpected mplistinterval response structure", "raw": data},
ensure_ascii=False,
indent=2,
)
result = []
for dish in raw_list:
rights = dish.get("rights") or {}
result.append(
{
"id": dish.get("metaId"),
"date": dish.get("date"),
"type": dish.get("type"),
"name": dish.get("name"),
"recipe_id": dish.get("recipeId") or None,
"can_update": rights.get("canUpdate") == "true",
"can_delete": rights.get("canDelete") == "true",
}
)
return json.dumps(result, ensure_ascii=False, indent=2)
# ---------------------------------------------------------------------------