Cookwala / حزمة التطوير والسيناريوهات / 070

A relief offer with hot rice at 52 °C is blocked by two packs

The disaster-kitchen flow records lentils, rice and cooked rice trays hot-held at 52 °C. The basic pack and the care pack each block the hot line; a nutrition pack has nothing to say about temperature.

الفئة: kitchen operator · الهدف: المساعدة في إنهاء الجوع, صحة أفضل للناس · المستوى: beginner

اقرأ أولًا: /docs/HUMANITARIAN-PROFILE/ /humanitarian/

الخطوات

  1. With no pack named the hub applies basic-nutrition-food-safety: one block finding on line 3. humanitarianCheck

  2. The care pack has its own hot-hold rule for vulnerable groups. humanitarianCheck

  3. Both packs together: two findings on the same line. humanitarianCheck

  4. The sodium-reduction pack alone: no temperature rules, no findings. humanitarianCheck

  5. Had this line come in by SMS, the gateway would have refused to list it before any document existed. parseSms

النتيجة

You have seen a block finding with its rule id, severity and the line it concerns. Next: scenario 076 compares it with an offer whose readings pass.

سطر الأوامر

cookwala humanitarian examples/humanitarian/flows/disaster-kitchen/offer.json
cookwala humanitarian --pack profiles/humanitarian/basic-nutrition-food-safety.rulepack.json --pack profiles/humanitarian/care-vulnerable-groups.rulepack.json examples/humanitarian/flows/disaster-kitchen/offer.json

المخرجات المسجلة

الحالة: ok

[1] humanitarianCheck: {"results": [{"id": "of-2026-11-03-rel-004", "kind": "Offer", "findings": [{"rule": "safety.hot_hold_min", "severity": "block", "detail": "line 3: 52 °C"}]}]}
[2] humanitarianCheck: {"results": [{"id": "of-2026-11-03-rel-004", "kind": "Offer", "findings": [{"rule": "care.hot_hold_min", "severity": "block", "detail": "line 3: 52 °C"}]}]}
[3] humanitarianCheck: {"results": [{"id": "of-2026-11-03-rel-004", "kind": "Offer", "findings": [{"rule": "safety.hot_hold_min", "severity": "block", "detail": "line 3: 52 °C"}, {"rule": "care.hot_hold_min", "severity": "block", "detail": "line 3: 52 °C"}]}]}
[4] humanitarianCheck: {"results": [{"id": "of-2026-11-03-rel-004", "kind": "Offer", "findings": []}]}
[5] parseSms: {"ok": true, "command": "OFFER", "kg": 18.0, "item": "cooked rice trays", "storage": "hot_held", "tempC": 52.0, "foodClasses": ["cooked_food", "cooked_rice"], "findings": ["safety.hot_hold_min"]}

الكود

التشغيل: python scenarios/out/070-disaster-kitchen-offer-hot-held-below-60/python.py

# Scenario 070: A relief offer with hot rice at 52 °C is blocked by two packs
# The disaster-kitchen flow records lentils, rice and cooked rice trays hot-held at 52 °C. The basic pack and the care pack each block the hot line; a nutrition pack has nothing to say about temperature.
# 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: With no pack named the hub applies basic-nutrition-food-safety: one block finding on line 3.
basic = c.humanitarian_check([{"profile": "0.2.0", "kind": "Offer", "id": "of-2026-11-03-rel-004", "donor": "did:web:wholesaler.example", "createdAt": "2026-11-02T16:00:00+02:00", "items": [{"lineId": "1", "name": "Red lentils", "ingredientId": "cw.ing.lentils_red", "kg": 200, "storage": "ambient", "origin": "wholesale", "dateMark": {"kind": "best_before", "date": "2027-03-01"}, "allergens": []}, {"lineId": "2", "name": "Rice", "ingredientId": "cw.ing.rice_short", "kg": 150, "storage": "ambient", "origin": "wholesale", "dateMark": {"kind": "best_before", "date": "2027-06-01"}, "allergens": []}, {"lineId": "3", "name": "Cooked rice trays", "kg": 18, "storage": "hot_held", "origin": "food_service", "cooked": True, "cookedAt": "2026-11-02T13:30:00+02:00", "allergens": [], "foodClasses": ["cooked_rice"]}], "window": {"from": "2026-11-02T16:30:00+02:00", "to": "2026-11-02T22:00:00+02:00"}, "site": {"country": "EG", "admin1": "Example province", "siteName": "Wholesale depot"}, "readings": [{"at": "2026-11-02T16:25:00+02:00", "tempC": 52, "method": "probe", "lineId": "3"}], "needs": ["same_day"], "state": "distributed", "version": 5}])
assert pick(basic, "results[0].findings.length") == 1, pick(basic, "results[0].findings.length")
print("ok", "results[0].findings.length", "=", 1)
assert pick(basic, "results[0].findings[0].rule") == "safety.hot_hold_min", pick(basic, "results[0].findings[0].rule")
print("ok", "results[0].findings[0].rule", "=", "safety.hot_hold_min")
assert pick(basic, "results[0].findings[0].severity") == "block", pick(basic, "results[0].findings[0].severity")
print("ok", "results[0].findings[0].severity", "=", "block")
assert pick(basic, "results[0].findings[0].detail") == "line 3: 52 °C", pick(basic, "results[0].findings[0].detail")
print("ok", "results[0].findings[0].detail", "=", "line 3: 52 °C")

# Step 2: The care pack has its own hot-hold rule for vulnerable groups.
care = c.humanitarian_check([{"profile": "0.2.0", "kind": "Offer", "id": "of-2026-11-03-rel-004", "donor": "did:web:wholesaler.example", "createdAt": "2026-11-02T16:00:00+02:00", "items": [{"lineId": "1", "name": "Red lentils", "ingredientId": "cw.ing.lentils_red", "kg": 200, "storage": "ambient", "origin": "wholesale", "dateMark": {"kind": "best_before", "date": "2027-03-01"}, "allergens": []}, {"lineId": "2", "name": "Rice", "ingredientId": "cw.ing.rice_short", "kg": 150, "storage": "ambient", "origin": "wholesale", "dateMark": {"kind": "best_before", "date": "2027-06-01"}, "allergens": []}, {"lineId": "3", "name": "Cooked rice trays", "kg": 18, "storage": "hot_held", "origin": "food_service", "cooked": True, "cookedAt": "2026-11-02T13:30:00+02:00", "allergens": [], "foodClasses": ["cooked_rice"]}], "window": {"from": "2026-11-02T16:30:00+02:00", "to": "2026-11-02T22:00:00+02:00"}, "site": {"country": "EG", "admin1": "Example province", "siteName": "Wholesale depot"}, "readings": [{"at": "2026-11-02T16:25:00+02:00", "tempC": 52, "method": "probe", "lineId": "3"}], "needs": ["same_day"], "state": "distributed", "version": 5}], ["care-vulnerable-groups"])
assert pick(care, "results[0].findings[0].rule") == "care.hot_hold_min", pick(care, "results[0].findings[0].rule")
print("ok", "results[0].findings[0].rule", "=", "care.hot_hold_min")
assert pick(care, "results[0].findings[0].severity") == "block", pick(care, "results[0].findings[0].severity")
print("ok", "results[0].findings[0].severity", "=", "block")

# Step 3: Both packs together: two findings on the same line.
both = c.humanitarian_check([{"profile": "0.2.0", "kind": "Offer", "id": "of-2026-11-03-rel-004", "donor": "did:web:wholesaler.example", "createdAt": "2026-11-02T16:00:00+02:00", "items": [{"lineId": "1", "name": "Red lentils", "ingredientId": "cw.ing.lentils_red", "kg": 200, "storage": "ambient", "origin": "wholesale", "dateMark": {"kind": "best_before", "date": "2027-03-01"}, "allergens": []}, {"lineId": "2", "name": "Rice", "ingredientId": "cw.ing.rice_short", "kg": 150, "storage": "ambient", "origin": "wholesale", "dateMark": {"kind": "best_before", "date": "2027-06-01"}, "allergens": []}, {"lineId": "3", "name": "Cooked rice trays", "kg": 18, "storage": "hot_held", "origin": "food_service", "cooked": True, "cookedAt": "2026-11-02T13:30:00+02:00", "allergens": [], "foodClasses": ["cooked_rice"]}], "window": {"from": "2026-11-02T16:30:00+02:00", "to": "2026-11-02T22:00:00+02:00"}, "site": {"country": "EG", "admin1": "Example province", "siteName": "Wholesale depot"}, "readings": [{"at": "2026-11-02T16:25:00+02:00", "tempC": 52, "method": "probe", "lineId": "3"}], "needs": ["same_day"], "state": "distributed", "version": 5}], ["basic-nutrition-food-safety", "care-vulnerable-groups"])
assert pick(both, "results[0].findings.length") == 2, pick(both, "results[0].findings.length")
print("ok", "results[0].findings.length", "=", 2)

# Step 4: The sodium-reduction pack alone: no temperature rules, no findings.
sodium = c.humanitarian_check([{"profile": "0.2.0", "kind": "Offer", "id": "of-2026-11-03-rel-004", "donor": "did:web:wholesaler.example", "createdAt": "2026-11-02T16:00:00+02:00", "items": [{"lineId": "1", "name": "Red lentils", "ingredientId": "cw.ing.lentils_red", "kg": 200, "storage": "ambient", "origin": "wholesale", "dateMark": {"kind": "best_before", "date": "2027-03-01"}, "allergens": []}, {"lineId": "2", "name": "Rice", "ingredientId": "cw.ing.rice_short", "kg": 150, "storage": "ambient", "origin": "wholesale", "dateMark": {"kind": "best_before", "date": "2027-06-01"}, "allergens": []}, {"lineId": "3", "name": "Cooked rice trays", "kg": 18, "storage": "hot_held", "origin": "food_service", "cooked": True, "cookedAt": "2026-11-02T13:30:00+02:00", "allergens": [], "foodClasses": ["cooked_rice"]}], "window": {"from": "2026-11-02T16:30:00+02:00", "to": "2026-11-02T22:00:00+02:00"}, "site": {"country": "EG", "admin1": "Example province", "siteName": "Wholesale depot"}, "readings": [{"at": "2026-11-02T16:25:00+02:00", "tempC": 52, "method": "probe", "lineId": "3"}], "needs": ["same_day"], "state": "distributed", "version": 5}], ["sodium-reduction"])
assert pick(sodium, "results[0].findings.length") == 0, pick(sodium, "results[0].findings.length")
print("ok", "results[0].findings.length", "=", 0)

# Step 5: Had this line come in by SMS, the gateway would have refused to list it before any document existed.
sms = c.parse_sms("OFFER 18KG COOKED RICE TRAYS H T52C")
assert pick(sms, "findings") == ["safety.hot_hold_min"], pick(sms, "findings")
print("ok", "findings", "=", ["safety.hot_hold_min"])

print("scenario complete")

scenarios/070-disaster-kitchen-offer-hot-held-below-60.json

السابق: 069 التالي: 071