Product · medium — un restaurant
Le cas. On choisit des plats, puis le retrait au comptoir ou la livraison. La livraison demande une adresse, vérifie qu'on livre, calcule un prix. Le retrait ne demande rien.
Ce que l'exemple montre. Deux skills à kinds, une dépendance entre elles, deux kinds reconnus
par leur when, dont un traité à part (kinds.pickup), un playbook dont la seconde tâche est un choix (one_of : retrait ou livraison), l'idempotence, une confirmation avant tout
submit, et un téléphone qui identifie la room puis se prouve par un code.
examples/actuelle/product-medium.yaml
# Product · medium — a restaurant: dishes, then pickup OR delivery.
# Two skills, one dependency (no delivery for nothing), one kind that differs (pickup is free and
# asks nothing), one playbook whose second task is a choice — pickup or delivery — a confirmation
# before anything is filed.
station:
id: teranga
name: Teranga Kitchen
identity:
providers:
sms: {mechanism: otp, verifies: phone, via: tenant, send: sendOtp, check: checkOtp}
global:
language: fr
currency: XOF
locale: fr-SN
domain:
domain_type: restaurant
entity_name: plat
entity_name_plural: plats
agent:
tone: friendly
name: Teranga
persona: "un hôte chaleureux qui aide à composer le repas"
rules:
- "Ne jamais inventer un plat : uniquement ce que le menu renvoie."
- "Les prix sont en francs CFA (ex : 8 500 F CFA)."
- "Nous livrons à Dakar uniquement ; ailleurs, proposer le retrait au comptoir."
portals:
web:
widget:
origins: [https://teranga.example.sn]
apis:
- name: cuisine
openapi_url: https://api.teranga.example.sn/openapi.json
base_url: https://api.teranga.example.sn
operations:
- {id: getMenu, access: read}
- {id: placeOrder, access: write}
- {id: getDeliveryOptions, access: read}
- {id: checkDeliverable, access: read}
- {id: getDeliveryPrice, access: read}
- {id: createDelivery, access: write, reversible: false}
- {id: sendOtp, access: write}
- {id: checkOtp, access: write}
auth: {type: api_key, key_name: X-API-Key, key_location: header, key_env: TERANGA_KEY}
skills:
- name: takeMealSelection
auto_trigger: take-meal-order
cognitive:
objective: "montrer la carte pour choisir les plats"
diagnose: [l'envie du moment, les restrictions alimentaires]
invoke: {method: getMenu}
kinds: {dish: {}}
present:
fields:
name: {from: name, type: text, role: title}
price: {from: price, type: price, role: billed}
image: {from: image_url, type: image}
submit:
- name: order
mode: request
tool: placeOrder
args:
body.items: "$selection[kind=dish]"
body.idempotency_key: "$action.key" # a retry never orders twice
line_fields: {ref: slug, quantity: quantity}
render: {component: carousel}
- name: takeDeliveryOption
cognitive:
objective: "proposer le retrait ou la livraison"
invoke: {method: getDeliveryOptions}
kinds: # told by the row's `type`, a presented field
pickup:
when: "$fields.type == 'pickup'"
select: {} # no address, no check, no fee
submit: [{name: collect, mode: self}] # collected at the counter: filed, nothing dispatched
delivery:
when: "$fields.type == 'delivery'"
depends_on: takeMealSelection # no delivery for an empty order…
on_loss: clear # …and it leaves with the last dish
present:
fields:
name: {from: name, type: text, role: title}
type: {from: type, type: text}
select:
max_items: 1 # one option: choosing delivery replaces pickup
guards:
- form:
address: {type: text, required: true, label: "Adresse de livraison"}
phone: {type: phone, required: true, label: "Téléphone du livreur", identifies: true}
- check: checkDeliverable
- update: {price: getDeliveryPrice}
submit:
- name: dispatch
mode: request
tool: createDelivery
args:
body.address: "$item.fields.address"
body.phone: "$item.fields.phone"
body.items: "$selection[kind=dish]"
body.fee: "$item.price" # the amount the guard quoted
render: {component: cards}
playbooks:
- name: take-meal-order
goal: "commander un repas : les plats, puis le retrait ou la livraison"
tasks:
plats: {skill: takeMealSelection, label: "Vos plats"}
remise:
label: "Retrait ou livraison"
after: [plats]
one_of: # two methods, one skill: each is one of its kinds
retrait: {skill: takeDeliveryOption, kind: pickup}
livraison: {skill: takeDeliveryOption, kind: delivery}
choose: customer # the person picks (the default)
catalog:
from_skill: takeMealSelection
mode: live
submit_guard:
confirm: true
Ce qui se passe
| étape | Core | résultat |
|---|---|---|
| l'agent montre la carte | auto_trigger ouvre take-meal-order | le parcours s'affiche : « Vos plats », « Retrait ou livraison » |
| un yassa ajouté | tâche « Vos plats » faite | total 8 500 F CFA |
| « Finaliser » | tâche « Retrait ou livraison » (remise) requise, vide | refusé : « Retrait ou livraison : étape requise de cette commande — ajoutez d'abord pickup ou delivery » |
| retrait choisi | la ligne a type: pickup, donc kinds.pickup, la méthode retrait : aucun formulaire, aucun frais | prêt |
| livraison choisie à la place | select.max_items: 1 ; step_open vérifie la sélection sans le retrait qu'elle remplace | la méthode livraison remplace retrait, et c'est annoncé ; formulaire d'adresse, checkDeliverable, getDeliveryPrice |
| « Finaliser » + confirmation | placeOrder, puis createDelivery avec body.fee | commande passée, reçu dans la room |
| le téléphone donné à la livraison | identifies: true, écrit +221… | la room le garde ; aucun formulaire ne le redemande |
| « envoyez-moi un code » | send_verification_code → sendOtp | le restaurant envoie un SMS ; le modèle ne voit ni le numéro ni le code |
| le code tapé | check_verification_code → checkOtp | numéro vérifié, la room appartient au client |
| le dernier plat retiré (avant le submit) | depends_on + on_loss: clear | la livraison part avec lui |
À retenir
kinds.pickup, avec sonselect: {}et son submitmode: self, est ce qui empêche de facturer une livraison à quelqu'un qui vient chercher son repas.body.fee: "$item.price"transmet le prix calculé. Sans lui, Core refuse la station : le client aurait vu un montant que le restaurant ne reçoit pas.remiseest unone_ofde deux méthodes sur une même skill, chacune un de ses kinds : une seule fait la tâche, et une sélection qui garderait les deux ne pourrait pas être soumise.after: [plats]ouvre « Retrait ou livraison » une fois les plats choisis, dans le parcours ;depends_onsur la skill tient la même dépendance, même hors du parcours.