Cookwala / SDK and scenarios / 079
A food bank's day as the Cairo flow records it: offer, claim, handover, distribution
Follow the food-bank-cairo example flow through its SMS messages, then check and validate the documents the gateway wrote from them. Nothing here is a real program; it is the example flow in the repository.
Read first: /docs/HUMANITARIAN-PROFILE/ /humanitarian/
Steps
The supermarket's first line: tomatoes, ambient, best before 11 November.
parseSmsThe second line: yogurt, chilled at 3.8 °C.
parseSmsThe food bank claims the grouped offer.
parseSmsAt pickup 74 kg are accepted and 6 kg of tomatoes rejected for damaged packaging; the yogurt reads 4.4 °C.
parseSmsThe kitchen reports the next day's distribution.
parseSmsThe offer the gateway wrote, checked with the basic pack: no findings.
humanitarianCheckThe recorded distribution, with its audience groups and an empty findings list, validates.
validate
Outcome
You have read one example day end to end and know which document each message becomes. Next: scenario 075 shows the month's impact summary computed from such days.
Command line
cookwala sms "OFFER 80KG TOMATOES A BB1111"
cookwala sms "HAND 012 74 REJ 6 PACK T4.4"
cookwala humanitarian examples/humanitarian/flows/food-bank-cairo/*.jsonRecorded output
Status: ok
[1] parseSms: {"ok": true, "command": "OFFER", "kg": 80.0, "item": "tomatoes", "storage": "ambient", "dateMark": {"kind": "best_before", "dayMonth": "11-11"}, "findings": []}
[2] parseSms: {"ok": true, "command": "OFFER", "kg": 24.0, "item": "yogurt", "storage": "chilled", "tempC": 3.8, "dateMark": {"kind": "use_by", "dayMonth": "12-11"}, "foodClasses": ["dairy"], "findings": []}
[3] parseSms: {"ok": true, "command": "CLAIM", "offer": "012", "all": true, "findings": []}
[4] parseSms: {"ok": true, "command": "HAND", "offer": "012", "kgAccepted": 74.0, "kgRejected": 6.0, "reason": "packaging_damaged", "reasonCode": "PACK", "tempC": 4.4, "findings": []}
[5] parseSms: {"ok": true, "command": "DIST", "meals": 410, "people": 410, "kg": 96.0, "findings": []}
[6] humanitarianCheck: {"results": [{"id": "of-2026-11-09-012", "kind": "Offer", "findings": []}]}
[7] validate: {"ok": true, "errors": []}Code
Run: python scenarios/out/079-a-food-bank-day-from-offer-to-distribution/python.py
# Scenario 079: A food bank's day as the Cairo flow records it: offer, claim, handover, distribution
# Follow the food-bank-cairo example flow through its SMS messages, then check and validate the documents the gateway wrote from them. Nothing here is a real program; it is the example flow in the repository.
# 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: The supermarket's first line: tomatoes, ambient, best before 11 November.
tomatoes = c.parse_sms("OFFER 80KG TOMATOES A BB1111")
assert pick(tomatoes, "ok") == True, pick(tomatoes, "ok")
print("ok", "ok", "=", True)
assert pick(tomatoes, "dateMark.dayMonth") == "11-11", pick(tomatoes, "dateMark.dayMonth")
print("ok", "dateMark.dayMonth", "=", "11-11")
assert pick(tomatoes, "findings.length") == 0, pick(tomatoes, "findings.length")
print("ok", "findings.length", "=", 0)
# Step 2: The second line: yogurt, chilled at 3.8 °C.
yogurt = c.parse_sms("OFFER 24KG YOGURT C 3.8C UB1211")
assert pick(yogurt, "storage") == "chilled", pick(yogurt, "storage")
print("ok", "storage", "=", "chilled")
assert pick(yogurt, "tempC") == 3.8, pick(yogurt, "tempC")
print("ok", "tempC", "=", 3.8)
assert pick(yogurt, "findings.length") == 0, pick(yogurt, "findings.length")
print("ok", "findings.length", "=", 0)
# Step 3: The food bank claims the grouped offer.
claim = c.parse_sms("CLAIM 012 ALL")
assert pick(claim, "command") == "CLAIM", pick(claim, "command")
print("ok", "command", "=", "CLAIM")
assert pick(claim, "all") == True, pick(claim, "all")
print("ok", "all", "=", True)
# Step 4: At pickup 74 kg are accepted and 6 kg of tomatoes rejected for damaged packaging; the yogurt reads 4.4 °C.
hand = c.parse_sms("HAND 012 74 REJ 6 PACK T4.4")
assert pick(hand, "kgAccepted") == 74.0, pick(hand, "kgAccepted")
print("ok", "kgAccepted", "=", 74.0)
assert pick(hand, "kgRejected") == 6.0, pick(hand, "kgRejected")
print("ok", "kgRejected", "=", 6.0)
assert pick(hand, "reason") == "packaging_damaged", pick(hand, "reason")
print("ok", "reason", "=", "packaging_damaged")
assert pick(hand, "tempC") == 4.4, pick(hand, "tempC")
print("ok", "tempC", "=", 4.4)
# Step 5: The kitchen reports the next day's distribution.
dist = c.parse_sms("DIST 410 MEALS 410 PEOPLE 96KG")
assert pick(dist, "meals") == 410, pick(dist, "meals")
print("ok", "meals", "=", 410)
assert pick(dist, "kg") == 96.0, pick(dist, "kg")
print("ok", "kg", "=", 96.0)
# Step 6: The offer the gateway wrote, checked with the basic pack: no findings.
checked = c.humanitarian_check([{"profile": "0.2.0", "kind": "Offer", "id": "of-2026-11-09-012", "donor": "did:web:supermarket.example", "createdAt": "2026-11-09T17:40:00+02:00", "items": [{"lineId": "1", "name": "Tomatoes", "ingredientId": "cw.ing.tomato", "kg": 80, "storage": "ambient", "origin": "retail", "dateMark": {"kind": "best_before", "date": "2026-11-11"}, "allergens": []}, {"lineId": "2", "name": "Yogurt cups", "gtin": "6221234567890", "kg": 24, "units": 160, "storage": "chilled", "origin": "retail", "dateMark": {"kind": "use_by", "date": "2026-11-12"}, "allergens": ["milk"]}, {"lineId": "3", "name": "Bread", "kg": 30, "storage": "ambient", "origin": "retail", "dateMark": {"kind": "best_before", "date": "2026-11-10"}, "allergens": ["cereals_gluten"]}], "window": {"from": "2026-11-09T18:00:00+02:00", "to": "2026-11-10T08:00:00+02:00"}, "site": {"country": "EG", "admin1": "Cairo", "admin2": "Nasr City", "siteName": "Supermarket loading bay"}, "readings": [{"at": "2026-11-09T17:35:00+02:00", "tempC": 3.8, "method": "fridge_display", "lineId": "2"}], "needs": ["refrigerated_vehicle"], "state": "distributed", "version": 5, "note": "Sent by SMS: OFFER 80KG TOMATOES A BB1111; OFFER 24KG YOGURT C 3.8C UB1211; OFFER 30KG BREAD A BB1011. Gateway grouped them under one offer id."}], ["basic-nutrition-food-safety"])
assert pick(checked, "results[0].findings.length") == 0, pick(checked, "results[0].findings.length")
print("ok", "results[0].findings.length", "=", 0)
# Step 7: The recorded distribution, with its audience groups and an empty findings list, validates.
dist2 = c.validate("humanitarian", load("examples/humanitarian/flows/food-bank-cairo/distribution.json"))
assert pick(dist2, "ok") == True, pick(dist2, "ok")
print("ok", "ok", "=", True)
print("scenario complete")
scenarios/079-a-food-bank-day-from-offer-to-distribution.json