Cookwala / SDK and scenarios / 060

A farm offers produce by SMS with the harvest date

FARM is an OFFER with origin farm. A harvest date HV, a best-before BB and the storage code are the whole message; the gateway records the offer and the rule packs read the origin and date later.

Audience: farmer · Goal: Help end hunger · Level: beginner

Read first: /docs/HUMANITARIAN-PROFILE/ /humanitarian/

Steps

  1. A cooperative offers 120 kg of tomatoes, ambient, harvested 4 November. parseSms

  2. The same offer with a best-before date instead; this is the message recorded in the robot-kitchen example flow. parseSms

  3. A plain OFFER may also carry HV. The item words imply the food class egg, which the storage checks use. parseSms

  4. Day 32 of month 13 is not a date: the gateway answers bad_date and writes nothing. parseSms

  5. The recorded offer for the robot-kitchen flow, checked with the basic food-safety pack: ambient produce with a date mark in the future, no findings. humanitarianCheck

Outcome

You can offer produce from a basic phone and know what the gateway records. Next: scenario 061 claims an offer.

Command line

cookwala sms "FARM 120KG TOMATO A HV0411"
cookwala sms "FARM 120KG TOMATO A HV3213"
cookwala humanitarian examples/humanitarian/flows/robot-kitchen/offer.json

Recorded output

Status: ok

[1] parseSms: {"ok": true, "command": "OFFER", "kg": 120.0, "item": "tomato", "storage": "ambient", "origin": "farm", "harvested": "04-11", "findings": []}
[2] parseSms: {"ok": true, "command": "OFFER", "kg": 120.0, "item": "tomato", "storage": "ambient", "origin": "farm", "dateMark": {"kind": "best_before", "dayMonth": "24-11"}, "findings": []}
[3] parseSms: {"ok": true, "command": "OFFER", "kg": 50.0, "item": "eggs", "storage": "chilled", "tempC": 4.0, "harvested": "03-11", "foodClasses": ["egg"], "findings": []}
[4] parseSms: {"ok": false, "error": "bad_date"}
[5] humanitarianCheck: {"results": [{"id": "of-2026-11-20-rk-002", "kind": "Offer", "findings": []}]}

Code

Run: python scenarios/out/060-farm-offer-with-a-harvest-date/python.py

# Scenario 060: A farm offers produce by SMS with the harvest date
# FARM is an OFFER with origin farm. A harvest date HV, a best-before BB and the storage code are the whole message; the gateway records the offer and the rule packs read the origin and date later.
# 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: A cooperative offers 120 kg of tomatoes, ambient, harvested 4 November.
farm = c.parse_sms("FARM 120KG TOMATO A HV0411")
assert pick(farm, "ok") == True, pick(farm, "ok")
print("ok", "ok", "=", True)
assert pick(farm, "command") == "OFFER", pick(farm, "command")
print("ok", "command", "=", "OFFER")
assert pick(farm, "origin") == "farm", pick(farm, "origin")
print("ok", "origin", "=", "farm")
assert pick(farm, "harvested") == "04-11", pick(farm, "harvested")
print("ok", "harvested", "=", "04-11")
assert pick(farm, "kg") == 120.0, pick(farm, "kg")
print("ok", "kg", "=", 120.0)
assert pick(farm, "storage") == "ambient", pick(farm, "storage")
print("ok", "storage", "=", "ambient")
assert pick(farm, "findings.length") == 0, pick(farm, "findings.length")
print("ok", "findings.length", "=", 0)

# Step 2: The same offer with a best-before date instead; this is the message recorded in the robot-kitchen example flow.
bb = c.parse_sms("FARM 120KG TOMATO A BB2411")
assert pick(bb, "dateMark.kind") == "best_before", pick(bb, "dateMark.kind")
print("ok", "dateMark.kind", "=", "best_before")
assert pick(bb, "dateMark.dayMonth") == "24-11", pick(bb, "dateMark.dayMonth")
print("ok", "dateMark.dayMonth", "=", "24-11")

# Step 3: A plain OFFER may also carry HV. The item words imply the food class egg, which the storage checks use.
eggs = c.parse_sms("OFFER 50KG EGGS C T4C HV0311")
assert pick(eggs, "harvested") == "03-11", pick(eggs, "harvested")
print("ok", "harvested", "=", "03-11")
assert pick(eggs, "foodClasses") == ["egg"], pick(eggs, "foodClasses")
print("ok", "foodClasses", "=", ["egg"])
assert pick(eggs, "tempC") == 4.0, pick(eggs, "tempC")
print("ok", "tempC", "=", 4.0)
assert pick(eggs, "origin") == None, pick(eggs, "origin")
print("ok", "origin", "=", None)

# Step 4: Day 32 of month 13 is not a date: the gateway answers bad_date and writes nothing.
bad = c.parse_sms("FARM 120KG TOMATO A HV3213")
assert pick(bad, "ok") == False, pick(bad, "ok")
print("ok", "ok", "=", False)
assert pick(bad, "error") == "bad_date", pick(bad, "error")
print("ok", "error", "=", "bad_date")

# Step 5: The recorded offer for the robot-kitchen flow, checked with the basic food-safety pack: ambient produce with a date mark in the future, no findings.
checked = c.humanitarian_check([{"profile": "0.2.0", "kind": "Offer", "id": "of-2026-11-20-rk-002", "donor": "did:web:farmcoop.example", "createdAt": "2026-11-20T05:30:00+02:00", "items": [{"lineId": "1", "name": "Tomatoes", "ingredientId": "cw.ing.tomato", "kg": 120, "storage": "ambient", "origin": "farm", "harvestedAt": "2026-11-19", "dateMark": {"kind": "best_before", "date": "2026-11-24"}, "allergens": []}], "window": {"from": "2026-11-20T06:00:00+02:00", "to": "2026-11-20T12:00:00+02:00"}, "site": {"country": "EG", "admin1": "Beheira", "siteName": "Cooperative packing shed"}, "state": "distributed", "version": 5, "note": "SMS: FARM 120KG TOMATO A BB2411"}], ["basic-nutrition-food-safety"])
assert pick(checked, "results[0].id") == "of-2026-11-20-rk-002", pick(checked, "results[0].id")
print("ok", "results[0].id", "=", "of-2026-11-20-rk-002")
assert pick(checked, "results[0].kind") == "Offer", pick(checked, "results[0].kind")
print("ok", "results[0].kind", "=", "Offer")
assert pick(checked, "results[0].findings.length") == 0, pick(checked, "results[0].findings.length")
print("ok", "results[0].findings.length", "=", 0)

print("scenario complete")

scenarios/060-farm-offer-with-a-harvest-date.json

Previous: 059 Next: 061