# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: restaurants.table-feedback-and-return
title: Table feedback and bring-them-back texts
sector: restaurants
source:
  kind: synthesized
  notes: >-
    No restaurant has built this with us yet. Built from the base feedback playbook (a rating
    form whose score starts a follow-up through a property filter) plus the daily
    segment-and-schedule pattern on the date of the last visit. The offers consent field and the
    capture of birthdays are new.
derived_from: base.feedback-follow-up
vocabulary:
  person: guest
  people: guests
  group: team
summary: >-
  A QR code on each table opens a short feedback form. A low score gives your manager a task to
  call within two hours, every guest gets a thank-you with your review link, and guests who said
  yes to offers get an invitation back if they haven't visited in a month.
when_to_use:
  - You run a restaurant, cafe, bar or lounge and most unhappy guests never tell you.
  - You want more Google reviews from the guests who did enjoy it.
  - You want a way to bring guests back that doesn't depend on a big marketing budget.

requires:
  data_in:
    - how: public_form
      what: "The Table feedback form this playbook creates. Print its link as a QR code for tables, receipts and the bill folder."
  channels:
    - channel: sms
      for: The thank-you and the invitation back
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
  events:
    - onboarding.submitted
  traits:
    - last_rating
    - last_visit_at
    - offers_ok

parameters:
  - key: org_name
    ask: true
    question: What should messages call your restaurant?
    label: Restaurant name
    type: string
    example: Northwind Kitchen
  - key: manager_team
    ask: true
    question: Which team calls guests who gave a low score? (can be just the manager on shift)
    label: Manager team
    type: team_name
    default: Managers
  - key: review_url
    ask: true
    question: Where should guests leave a review? (your Google link; leave empty to just say thank you)
    label: Review link
    type: url
    default: ""
  - key: weekly_special
    ask: true
    question: What should the invitation tell guests about this week?
    help: One short line. You can change it any week in the invitation template.
    label: This week's special
    type: text
    example: "our pepper soup and grilled fish special, Thursday to Saturday"
  - key: low_score
    ask: true
    question: Which scores count as unhappy? (out of 5)
    label: Unhappy at or below
    type: number
    default: 2
  - key: return_after_days
    label: Days since the last visit before the invitation
    type: days
    default: 30
  - key: call_due_hours
    label: Hours the manager has to call
    type: hours
    default: 2
  - key: restaurant_phone
    label: Number guests call to book
    type: string
    default: "{{ workspace.phone }}"
  - key: send_time
    label: Daily send time for invitations
    type: time
    default: "11:00"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: there
  - key: default_country
    label: Default phone country on the form
    type: country_code
    default: "{{ workspace.country }}"
  - key: timezone
    label: Time zone
    type: timezone
    default: "{{ workspace.timezone }}"

timings:
  - A score of {{ params.low_score }} or lower creates a call task straight away, due in {{ params.call_due_hours }} hours.
  - Every guest who leaves a phone number gets the thank-you text an hour later, at most once a month. Low scores also get the manager's call.
  - Scores in between are saved on the guest with no message.
  - Every day at {{ params.send_time }}, guests who said yes to offers and last visited between {{ params.return_after_days }} and 90 days ago get the invitation. At most one a month.

customer_must:
  - Add your managers to the {{ params.manager_team }} team. A task for an empty team reaches nobody.
  - Publish the form and turn its link into a QR code for the tables. Ask staff to point guests to it with the bill.
  - Edit the invitation text when your special changes (Templates → "Come back and see us (SMS)").
  - Connect an SMS provider or finish business verification, then check the segment size and switch the three follow-ups on.
  - Only guests who said yes on the form get invitations. Don't add guests to that list any other way without asking them.

objects:
  - ref: team_managers
    kind: team
    about: Calls guests who gave a low score.
    body: { name: "{{ params.manager_team }}" }

  - ref: form_feedback
    kind: form
    about: The table feedback form. Rating, comment, contact details, optional birthday and an offers question.
    body:
      name: Table feedback
      slug: table-feedback
      description: Tell us how your visit was.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Send, success_message: "Thank you for telling us. We hope to see you again at {{ params.org_name }}." }
        pages:
          - id: p1
            title: How was your visit?
            description: It takes less than a minute.
            fields:
              - { id: f_rating, key: rating, type: rating, label: "How was your visit today?", required: true, maps_to: { mode: custom, key: last_rating, trait: true }, validation: { min: 1, max: 5 } }
              - { id: f_comment, key: comment, type: long_text, label: Anything you'd like to tell us?, required: false, maps_to: { mode: custom, key: comment } }
              - { id: f_first, key: first_name, type: short_text, label: Your first name, required: true, maps_to: { mode: custom, key: first_name, trait: true } }
              - { id: f_phone, key: phone, type: phone, label: Your phone number, help: So a manager can put things right if we got something wrong., required: true, maps_to: { mode: mapped, slot: profile.phone }, config: { country_picker: true, default_country: "{{ params.default_country }}", verify: "off" } }
              - { id: f_bday, key: birthday, type: date, label: Your birthday, help: Month and day only. We may send you a birthday message., required: false, maps_to: { mode: custom, key: birthday, trait: true }, config: { month_day: true, month_trait: birth_month, day_trait: birth_day } }
              - id: f_offers
                key: offers_ok
                type: multiple_choice
                label: Can we text you offers and news? You can stop any time.
                required: true
                maps_to: { mode: custom, key: offers_ok, trait: true }
                options: [{ value: "yes", label: "Yes, please" }, { value: "no", label: "No, thanks" }]
              - { id: f_date, key: visit_date, type: date, label: Date of your visit, required: true, maps_to: { mode: custom, key: last_visit_at, trait: true }, config: { default_today: true } }
      verification_settings: { required: false }

  - ref: seg_come_back
    kind: segment
    about: Said yes to offers and last visited between the gap and 90 days ago.
    body:
      name: Due an invitation back
      description: Said yes to offers; last visit between {{ params.return_after_days }} and 90 days ago.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: offers_ok, cond_operator: eq, value: "yes" }
          - { type: trait, trait_key: last_visit_at, cond_operator: older_than, value: "{{ params.return_after_days }}d" }
          - { type: trait, trait_key: last_visit_at, cond_operator: within_last, value: 90d }

  - ref: tmpl_thanks
    kind: template
    about: Thank-you to every guest, with a review link if you gave one.
    body:
      name: Thanks for your feedback (SMS)
      channel: sms
      category: Feedback
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, thank you for telling us about your visit to {{ params.org_name }}.{% if \"{{ params.review_url }}\" != \"\" %} Would you share it in a quick review? {{ params.review_url }}{% endif %} Reply STOP to opt out."
  - ref: tmpl_invite
    kind: template
    about: Invitation back, with this week's special.
    body:
      name: Come back and see us (SMS)
      channel: sms
      category: Marketing
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, we'd love to see you again at {{ params.org_name }}. This week: {{ params.weekly_special }}. To book a table, call or message {{ params.restaurant_phone }}. Reply STOP to opt out."

  - ref: wf_low
    kind: workflow
    about: A low score gives the manager a call task; the outcome is saved on the guest.
    body:
      name: "Table feedback: low score call"
      description: When a score at or below the unhappy line comes in, a manager calls within hours.
      enabled: false
      trigger_type: event
      trigger_config:
        event_name: onboarding.submitted
        form_id: "{{ ref.form_feedback.id }}"
        property_filters: [{ property: rating, cond_operator: lte, value: "{{ params.low_score }}" }]
        properties: { $origamy_property_filters: true }
      steps:
        - id: s1
          type: human_task
          config:
            human_task:
              title: "Unhappy guest: {{ traits.first_name | default: \"a guest\" }}"
              instructions: |-
                {{ traits.first_name | default: "A guest" }} rated their visit {{ properties.rating }} out of 5 on {{ properties.visit_date }}.
                Their comment: {{ properties.comment | default: "none" }}
                Phone: {{ traits.phone }}

                Call, listen, say sorry and put it right if you can. Note what you agreed.
              assignee_refs: [{ type: team, id: "{{ ref.team_managers.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: resolved, label: Put right, intent: positive }
                - { id: follow_up, label: Needs more follow-up, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: "{{ params.call_due_hours }}"
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s2, type: action, config: { action_type: set_trait, traits: { feedback_follow_up: "{{ properties.task_outcome | default: \"unknown\" }}", feedback_note: "{{ properties.task_note }}" } } }
      re_entry_policy: every_time
      re_entry_cooldown_hours: 0
      goal: {}
      exit_conditions: []
  - ref: wf_high
    kind: workflow
    about: Every guest gets a thank-you and the review link an hour later (asking only happy guests for reviews breaks Google's rules).
    body:
      name: "Table feedback: thank you"
      description: A thank-you text with the review link for every guest who leaves feedback.
      enabled: false
      trigger_type: event
      trigger_config:
        event_name: onboarding.submitted
        form_id: "{{ ref.form_feedback.id }}"
      steps:
        - { id: s1, type: delay, config: { duration: 1h } }
        - { id: s2, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_thanks.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 720
      goal: {}
      exit_conditions: []
  - ref: wf_invite
    kind: workflow
    about: Daily run over "due an invitation back"; one invitation a month.
    body:
      name: "Invitation back"
      description: Every day, an invitation to guests who said yes to offers and haven't visited in a while.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_come_back.id }}", rrule: FREQ=DAILY, starts_at: "{{ workspace.tomorrow }}T{{ params.send_time }}", timezone: "{{ params.timezone }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_invite.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 720
      goal: {}
      exit_conditions: []

eval:
  name: 79_restaurants_table_feedback_and_return
  mode: task
  vertical: ""
  prompt: >-
    We run a restaurant. Make a feedback form we can put on a QR code at every table: rating out
    of 5, comment, name, phone, birthday, and whether they want offers by text. If someone gives
    1 or 2, our manager should get a task to call within 2 hours. Text everyone a
    thank-you with our Google review link. Guests who said yes to offers and haven't visited in
    a month should get a text inviting them back with this week's special.
  expect_artifacts: [form, segment, workflow, message_template]
  forbid_terms: [coupon]
