Cookwala / SDK and scenarios / 016
Read a recipe as a document: yield, allergens, steps, end conditions and a critical control point
A Cookwala recipe is a typed document a class can read without any device: how much it makes, which allergens it carries, which operation each step is, and the measurable condition that ends it.
Read first: /docs/RECIPE-FORMAT/ /docs/CORE/
Steps
Kofta in the oven: four servings, no declared allergens, ten steps; the bake ends when the core probe reads 71 degC, a critical control point for ground meat.
getRecipeShakshuka declares eggs under both the EU and US allergen lists and may be scaled from half to double.
getRecipeThe number in the recipe is not arbitrary: the default limits pack has the same 71 degC minimum for ground meat, with its source.
safetyLimits
Outcome
You can show students where a safe temperature comes from and how a recipe states it as a condition a sensor can check. Next: scenario 036 looks at the knife-work rules in the vocabulary.
Command line
curl -s http://localhost:7878/v1/tools/recipes/kofta-oven
curl -s http://localhost:7878/v1/tools/recipes/shakshuka
curl -s http://localhost:7878/v1/safety-limitsRecorded output
Status: ok
[1] getRecipe: {"$schema": "https://cookwala.ai/v1/schemas/recipe.schema.json", "cookwala": "0.2.0", "id": "example-kofta-oven", "revision": 1, "updated": "2026-10-04", "verification": {"level": "V1", "evidence": {"simulatorRuns": 0, "humanReviews": 1, "fieldReports": 0}, "reviewedBy": "cookwala.ai editorial (structure only)", "notes": "Example recipe written to exercise the standard: structured steps, end conditions, hazards and critical control points. Not simulated and not field-verified; values are typical home-cooking values, not measurements."}, "dish": {"names": {"en": "Oven-baked beef kofta", "ar": "…
[2] getRecipe: {"$schema": "https://cookwala.ai/v1/schemas/recipe.schema.json", "cookwala": "0.1.0", "id": "example-shakshuka", "revision": 2, "updated": "2026-10-03", "hash": "sha256:2ea6fab93cc673fc6784f9bc9fe401d2387bb7779dc537df97286b73807c8841", "verification": {"level": "V2", "evidence": {"simulatorRuns": 1, "humanReviews": 1, "fieldReports": 0}, "reviewedBy": "fifi.cooking editorial", "notes": "Illustrative example for the spec; values are realistic but not field-verified."}, "dish": {"names": {"en": "Shakshuka", "ar": "شكشوكة", "fr": "Chakchouka"}, "cuisine": ["TN", "MA", "DZ", "LY"], "course": "main…
[3] safetyLimits: {"core": "0.2.0", "kind": "SafetyLimits", "id": "cookwala-default", "version": "0.2.0", "status": "draft", "limits": [{"id": "oil.max_temp", "kind": "max_temp", "action": "cut_heat", "message": "Cut heat above 200 °C: most cooking oils smoke and fire risk rises.", "source": "Common frying-oil smoke points; fire-safety practice", "appliesTo": {"medium": "oil", "ops": ["cw.op.fry", "cw.op.deep_fry"]}, "max": 200, "unit": "degC"}, {"id": "pan.max_surface", "kind": "max_temp", "action": "cut_heat", "message": "Cut heat above 280 °C pan surface (coatings degrade, oil ignites).", "source": "Cookware…Code
Run: python scenarios/out/016-read-a-recipe-as-a-document/python.py
# Scenario 016: Read a recipe as a document: yield, allergens, steps, end conditions and a critical control point
# A Cookwala recipe is a typed document a class can read without any device: how much it makes, which allergens it carries, which operation each step is, and the measurable condition that ends it.
# Run a hub first: python hub/cookwala_hub.py --recipes examples
import json, os
from cookwala.client import CookwalaClient, CookwalaProblem
def load(path):
with open(path, encoding="utf-8") as f:
return json.load(f)
def pick(obj, path):
for part in path.replace("]", "").replace("[", ".").split("."):
obj = obj[int(part)] if part.isdigit() else (len(obj) if part == "length" else obj.get(part))
return obj
c = CookwalaClient(os.environ.get("COOKWALA_HUB", "http://localhost:7878"))
# Step 1: Kofta in the oven: four servings, no declared allergens, ten steps; the bake ends when the core probe reads 71 degC, a critical control point for ground meat.
kofta = c.get_recipe("kofta-oven")
assert pick(kofta, "id") == "example-kofta-oven", pick(kofta, "id")
print("ok", "id", "=", "example-kofta-oven")
assert pick(kofta, "yield.servings") == 4, pick(kofta, "yield.servings")
print("ok", "yield.servings", "=", 4)
assert pick(kofta, "verification.level") == "V1", pick(kofta, "verification.level")
print("ok", "verification.level", "=", "V1")
assert pick(kofta, "safety.allergens.us9.length") == 0, pick(kofta, "safety.allergens.us9.length")
print("ok", "safety.allergens.us9.length", "=", 0)
assert pick(kofta, "process.nodes.length") == 10, pick(kofta, "process.nodes.length")
print("ok", "process.nodes.length", "=", 10)
assert pick(kofta, "process.nodes[7].op") == "cw.op.bake", pick(kofta, "process.nodes[7].op")
print("ok", "process.nodes[7].op", "=", "cw.op.bake")
assert pick(kofta, "process.nodes[7].params.tempC") == 200, pick(kofta, "process.nodes[7].params.tempC")
print("ok", "process.nodes[7].params.tempC", "=", 200)
assert pick(kofta, "process.nodes[7].until.all[0].sensor") == "cw.sense.core_temp", pick(kofta, "process.nodes[7].until.all[0].sensor")
print("ok", "process.nodes[7].until.all[0].sensor", "=", "cw.sense.core_temp")
assert pick(kofta, "process.nodes[7].until.all[0].gte") == 71, pick(kofta, "process.nodes[7].until.all[0].gte")
print("ok", "process.nodes[7].until.all[0].gte", "=", 71)
assert pick(kofta, "process.nodes[7].ccp") == "ccp_ground_meat", pick(kofta, "process.nodes[7].ccp")
print("ok", "process.nodes[7].ccp", "=", "ccp_ground_meat")
# Step 2: Shakshuka declares eggs under both the EU and US allergen lists and may be scaled from half to double.
shakshuka = c.get_recipe("shakshuka")
assert pick(shakshuka, "verification.level") == "V2", pick(shakshuka, "verification.level")
print("ok", "verification.level", "=", "V2")
assert pick(shakshuka, "safety.allergens.eu14[0]") == "eggs", pick(shakshuka, "safety.allergens.eu14[0]")
print("ok", "safety.allergens.eu14[0]", "=", "eggs")
assert pick(shakshuka, "safety.allergens.us9[0]") == "eggs", pick(shakshuka, "safety.allergens.us9[0]")
print("ok", "safety.allergens.us9[0]", "=", "eggs")
assert pick(shakshuka, "yield.scaling.minScale") == 0.5, pick(shakshuka, "yield.scaling.minScale")
print("ok", "yield.scaling.minScale", "=", 0.5)
assert pick(shakshuka, "yield.scaling.maxScale") == 2, pick(shakshuka, "yield.scaling.maxScale")
print("ok", "yield.scaling.maxScale", "=", 2)
# Step 3: The number in the recipe is not arbitrary: the default limits pack has the same 71 degC minimum for ground meat, with its source.
limits = c.safety_limits()
assert pick(limits, "limits[4].id") == "core.ground_meat", pick(limits, "limits[4].id")
print("ok", "limits[4].id", "=", "core.ground_meat")
assert pick(limits, "limits[4].min") == 71, pick(limits, "limits[4].min")
print("ok", "limits[4].min", "=", 71)
assert pick(limits, "limits[4].action") == "ask_human", pick(limits, "limits[4].action")
print("ok", "limits[4].action", "=", "ask_human")
print("scenario complete")