# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: estates.resident-requests
title: New residents and maintenance requests
sector: estates
source:
  kind: synthesized
  notes: >-
    No estate or facility manager has built this with us yet. Written from the church
    first-timer journey (a form starts a welcome message) and the base call-task pattern (a
    form answer starts a task with a due time and escalation), with the staff-form routing used
    for loan decisions (a trigger filter on one answer picks the workflow). Urgent and normal
    requests get different due times. The resident hears when the task is closed.
derived_from: base.first-contact-follow-up
vocabulary:
  person: resident
  people: residents
  group: estate team
summary: >-
  Welcomes every new resident with a pack by email and SMS: the estate rules, emergency numbers,
  how to pay the service charge and how to report a problem. Residents report problems on a
  maintenance request form, with a photo if they like. Urgent requests give your facility team a
  task due in two hours that escalates to the estate manager, normal requests a task due in two
  days. When the team closes a request as fixed, the resident gets an SMS. The estate manager gets
  a weekly report of requests by category and open tasks.
when_to_use:
  - You run a residential estate, a block of flats or a residents' association, and new residents move in regularly.
  - Maintenance problems come in by WhatsApp, phone and the gateman, and some get lost.
  - Residents don't know whom to call or how to pay, and the estate manager wants to see what is being reported.

requires:
  data_in:
    - how: public_form
      what: "The New Resident form and the Maintenance Request form this playbook creates. Put the New Resident link in your move-in pack and the Request link on a QR code in the lobby, on the estate notice board and in the residents' group chat. The estate office can also fill both in for a resident who phones."
  channels:
    - channel: email
      for: The welcome pack
      needs: A verified sending domain for {{ params.email_from }}.
    - channel: sms
      for: The short welcome, the request acknowledgement and the "we have closed your request" message
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
  events:
    - onboarding.submitted
  traits:
    - unit
    - resident_stage
    - request_outcome
    - request_note

parameters:
  - key: org_name
    ask: true
    question: What should messages call your estate?
    label: Estate name
    type: string
    example: Northwind Gardens Estate
  - key: rules_url
    ask: true
    question: Where are your estate rules? (a link to the page or document)
    label: Estate rules link
    type: url
    example: "https://www.example.com/estate-rules"
  - key: emergency_numbers
    ask: true
    question: What emergency numbers should new residents have? (for example security, fire and the estate office)
    help: "Write them the way you want residents to read them. This goes in the welcome email."
    label: Emergency numbers
    type: text
    example: The security desk number and the nearest fire and ambulance numbers, as printed on the gate notice
  - key: facility_team
    ask: true
    question: Which team handles maintenance requests? (your facility team, one person is fine)
    label: Facility team
    type: team_name
    default: Facility team
  - key: manager_team
    ask: true
    question: Who should hear if an urgent request isn't handled in time, and gets the weekly report? (the estate manager)
    label: Estate manager
    type: team_name
    default: Estate manager
  - key: pay_info
    label: How residents pay the service charge (a few words, without account numbers)
    type: text
    default: by bank transfer or at the estate office, as shown on your service charge notice
  - key: office_phone
    label: Number residents can call with questions
    type: string
    default: "{{ workspace.phone }}"
  - key: urgent_due_hours
    label: Hours the facility team has to respond to an urgent request
    type: hours
    default: 2
  - key: urgent_escalate_hours
    label: Hours after which an urgent request goes to the estate manager
    type: hours
    default: 4
  - key: normal_due_hours
    label: Hours the facility team has to respond to a normal request
    type: hours
    default: 48
  - key: normal_escalate_hours
    label: Hours after which a normal request goes to the estate manager
    type: hours
    default: 72
  - key: report_cron
    label: Weekly report
    type: cron
    default: "0 8 * * 1"
  - key: timezone
    label: Time zone for the report
    type: timezone
    default: "{{ workspace.timezone }}"
  - key: default_country
    label: Default phone country on the forms
    type: country_code
    default: "{{ workspace.country }}"
  - key: email_from
    label: Sender address
    type: email
    default: "{{ workspace.default_sender }}"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: resident

timings:
  - The welcome email and SMS go out as soon as the New Resident form is submitted.
  - "An urgent request gets an SMS straight away and a task for {{ params.facility_team }} due in {{ params.urgent_due_hours }} hours, with reminders before it is due. If nobody has responded after {{ params.urgent_escalate_hours }} hours it escalates to {{ params.manager_team }}."
  - "A normal request gets an SMS straight away and a task due in {{ params.normal_due_hours }} hours. If nobody has responded after {{ params.normal_escalate_hours }} hours it escalates to {{ params.manager_team }}."
  - When the team marks a request as fixed, the resident gets an SMS straight away.
  - "The weekly report to {{ params.manager_team }} runs on cron {{ params.report_cron }} in {{ params.timezone }}."

customer_must:
  - Add your facility staff to the {{ params.facility_team }} team and your estate manager to {{ params.manager_team }} (Settings → Collaborators → Teams). A task assigned to an empty team reaches nobody, so urgent requests would go unseen.
  - Review and publish the New Resident and Maintenance Request forms. Publishing makes the links live; nothing sends. Check the category list matches the problems you really get.
  - Verify the sending domain for the welcome email, and connect an SMS provider or finish business verification.
  - Tell your team to mark a request Fixed only when it really is, and to choose Scheduled when a visit or parts are still to come. Only Fixed sends the "we have closed your request" SMS.
  - Make clear on the form and in the lobby that the form is not for emergencies. For fire, medical or security emergencies, residents should call the emergency numbers in the welcome pack.
  - "Limit who can open submitted forms: the New Resident form holds household details and the request form may hold photos of residents' homes. Give access only to the estate office and the facility team."
  - Switch the follow-ups and the report on and test with your own phone, a fake unit and a test request.

objects:
  - ref: team_facility
    kind: team
    about: Gets the maintenance tasks.
    body: { name: "{{ params.facility_team }}" }
  - ref: team_manager
    kind: team
    about: Hears about requests not handled in time, and gets the weekly report.
    body: { name: "{{ params.manager_team }}" }

  - ref: form_resident
    kind: form
    about: The new-resident form. A hidden field marks the person as a resident.
    body:
      name: New Resident
      slug: new-resident
      description: "Welcome to {{ params.org_name }}. Tell us who is moving in."
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Submit, success_message: "Welcome home! Check your messages for your welcome pack." }
        pages:
          - id: p1
            title: "Welcome to {{ params.org_name }}"
            description: Tell us a little about your household so the estate office can reach you.
            fields:
              - { id: f_first, key: first_name, type: short_text, label: First name, required: true, maps_to: { mode: custom, key: first_name, trait: true } }
              - { id: f_last, key: last_name, type: short_text, label: Surname, required: true, maps_to: { mode: custom, key: last_name, trait: true } }
              - { id: f_phone, key: phone, type: phone, label: Phone number, required: true, maps_to: { mode: mapped, slot: profile.phone }, config: { country_picker: true, default_country: "{{ params.default_country }}", verify: "off" } }
              - { id: f_email, key: email, type: email, label: Email address, required: false, maps_to: { mode: mapped, slot: profile.email } }
              - { id: f_unit, key: unit, type: short_text, label: "Your unit (for example block and flat or house number)", required: true, maps_to: { mode: custom, key: unit, trait: true } }
              - { id: f_household, key: household_size, type: number, label: How many people live in your home?, required: false, maps_to: { mode: custom, key: household_size }, validation: { min: 1, max: 30 } }
              - { id: f_movein, key: move_in_date, type: date, label: Move-in date, required: false, maps_to: { mode: custom, key: move_in_date } }
              - { id: f_consent, key: consent_to_keep_details, type: boolean, label: I agree that the estate office may keep these details to run the estate, required: true, maps_to: { mode: custom, key: consent_to_keep_details } }
              - { id: f_stage, key: stage, type: hidden, label: "", required: false, default: resident, maps_to: { mode: custom, key: resident_stage, trait: true } }
      verification_settings: { required: false }
  - ref: form_request
    kind: form
    about: The maintenance request form, with category, urgency, description and an optional photo.
    body:
      name: Maintenance Request
      slug: maintenance-request
      description: Report a problem in your home or in the estate. For fire, medical or security emergencies, call the emergency numbers instead.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Send request, success_message: "Thank you. We have your request and our team will respond." }
        pages:
          - id: p1
            title: Report a problem
            description: Tell us what is wrong and where. This form is not for emergencies.
            fields:
              - { id: f_first, key: first_name, type: short_text, label: First name, required: true, maps_to: { mode: custom, key: first_name, trait: true } }
              - { id: f_phone, key: phone, type: phone, label: Phone number, required: true, maps_to: { mode: mapped, slot: profile.phone }, config: { country_picker: true, default_country: "{{ params.default_country }}", verify: "off" } }
              - { id: f_unit, key: unit, type: short_text, label: Your unit, required: true, maps_to: { mode: custom, key: unit, trait: true } }
              - id: f_category
                key: category
                type: single_select
                label: What is the problem about?
                required: true
                maps_to: { mode: custom, key: category }
                options:
                  - { value: plumbing, label: Plumbing or water }
                  - { value: electrical, label: Electrical }
                  - { value: security, label: Security }
                  - { value: waste, label: Waste or cleaning }
                  - { value: other, label: Something else }
              - id: f_urgency
                key: urgency
                type: single_select
                label: How urgent is it?
                required: true
                maps_to: { mode: custom, key: urgency }
                options:
                  - { value: urgent, label: "Urgent (a safety risk, flooding or something that cannot wait)" }
                  - { value: normal, label: "Normal (it can wait a day or two)" }
              - { id: f_desc, key: description, type: long_text, label: Describe the problem, required: true, maps_to: { mode: custom, key: description } }
              - { id: f_photo, key: photo, type: file, label: A photo, if it helps, required: false, maps_to: { mode: custom, key: photo }, validation: { mime_types: [image/jpeg, image/png], max_size_mb: 8 } }
      verification_settings: { required: false }

  - ref: seg_residents
    kind: segment
    about: Everyone who has registered as a resident.
    body:
      name: Registered residents
      description: Stage is resident (set by the New Resident form).
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: resident_stage, cond_operator: eq, value: resident }

  - ref: tmpl_welcome_email
    kind: template
    about: Welcome pack email with the rules, emergency numbers, how to pay and how to report problems.
    body:
      name: Welcome pack (email)
      channel: email
      category: Welcome
      style: html
      status: active
      subject: "Welcome to {{ params.org_name }}"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "Everything you need in your first week." } }
      variables: {}
      body: >-
        <p>Hello {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>Welcome to {{ params.org_name }}. We hope you settle in quickly. Here is what you need
        to know.</p>
        <p><strong>Estate rules.</strong> Please read them once: <a href="{{ params.rules_url }}">estate rules</a>.</p>
        <p><strong>Emergency numbers.</strong> {{ params.emergency_numbers }}</p>
        <p><strong>Paying the service charge.</strong> You can pay {{ params.pay_info }}.</p>
        <p><strong>Reporting a problem.</strong> Use the <a href="{{ ref.form_request.url }}">maintenance request form</a>
        and add a photo if it helps. Our team will respond. For anything else, call
        {{ params.office_phone }}.</p>
        <p>Warm regards,<br>{{ params.org_name }}</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_welcome_sms
    kind: template
    about: Short welcome SMS with the link for reporting problems.
    body:
      name: Welcome (SMS)
      channel: sms
      category: Welcome
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Welcome to {{ params.org_name }}, {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}! To report a problem at any time, use this form: {{ ref.form_request.url }} Questions? Call {{ params.office_phone }}. Reply STOP to opt out."
  - ref: tmpl_urgent_sms
    kind: template
    about: Acknowledgement of an urgent request.
    body:
      name: Urgent request received (SMS)
      channel: sms
      category: Maintenance
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, we have your urgent request for {{ traits.unit | default: \"your unit\" }} and our team has been alerted. For a fire, medical or security emergency, please call the emergency numbers in your welcome pack. Reply STOP to opt out."
  - ref: tmpl_normal_sms
    kind: template
    about: Acknowledgement of a normal request.
    body:
      name: Request received (SMS)
      channel: sms
      category: Maintenance
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, we have your maintenance request for {{ traits.unit | default: \"your unit\" }} and our team will respond soon. Reply STOP to opt out."
  - ref: tmpl_closed_sms
    kind: template
    about: Message when the team closes a request as fixed. Invites the resident to say if it isn't.
    body:
      name: Request closed (SMS)
      channel: sms
      category: Maintenance
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, we have closed your maintenance request for {{ traits.unit | default: \"your unit\" }}. If it is not fixed, please tell us by calling {{ params.office_phone }} or sending a new request: {{ ref.form_request.url }} Reply STOP to opt out."

  - ref: q_requests_by_category
    kind: saved_question
    about: Maintenance requests by category, for the weekly report.
    body:
      name: "Report: requests by category"
      data_plane_id: default
      viz_type: bar
      viz_settings: {}
      visibility: workspace
      query_ast:
        table: events
        filters:
          operator: and
          conditions:
            - { field: event_name, op: eq, value: onboarding.submitted }
            - { field: properties.form_id, op: eq, value: "{{ ref.form_request.id }}" }
        aggregations: [{ fn: count, alias: requests }]
        breakouts: [{ field: properties.category }]
  - ref: q_requests_by_urgency
    kind: saved_question
    about: Maintenance requests by urgency, for the weekly report.
    body:
      name: "Report: requests by urgency"
      data_plane_id: default
      viz_type: bar
      viz_settings: {}
      visibility: workspace
      query_ast:
        table: events
        filters:
          operator: and
          conditions:
            - { field: event_name, op: eq, value: onboarding.submitted }
            - { field: properties.form_id, op: eq, value: "{{ ref.form_request.id }}" }
        aggregations: [{ fn: count, alias: requests }]
        breakouts: [{ field: properties.urgency }]
  - ref: q_requests_total
    kind: saved_question
    about: How many maintenance requests came in, for the weekly report.
    body:
      name: "Report: maintenance requests"
      data_plane_id: default
      viz_type: number
      viz_settings: {}
      visibility: workspace
      query_ast:
        table: events
        filters:
          operator: and
          conditions:
            - { field: event_name, op: eq, value: onboarding.submitted }
            - { field: properties.form_id, op: eq, value: "{{ ref.form_request.id }}" }
        aggregations: [{ fn: count, alias: requests }]

  - ref: report_weekly
    kind: report
    about: Each week, requests by category and urgency and the state of the tasks, to the estate manager.
    body:
      name: "Weekly: maintenance requests"
      description: How many requests came in this week, by category and urgency, and how many tasks are open or late.
      schedule_cron: "{{ params.report_cron }}"
      timezone: "{{ params.timezone }}"
      enabled: false
      spec:
        period: last_7_days
        compare: previous_period
        week_start: monday
        sections:
          - { id: total, kind: saved_question, title: Requests this week, viz: number, params: { question_id: "{{ ref.q_requests_total.id }}" } }
          - { id: by_category, kind: saved_question, title: Requests by category, viz: bar, params: { question_id: "{{ ref.q_requests_by_category.id }}" } }
          - { id: by_urgency, kind: saved_question, title: Requests by urgency, viz: bar, params: { question_id: "{{ ref.q_requests_by_urgency.id }}" } }
          - { id: tasks, kind: tasks_summary, title: Open and late tasks, params: {} }
          - { id: residents, kind: segment_sizes, title: Registered residents, params: { segment_ids: ["{{ ref.seg_residents.id }}"] } }
      recipients: [{ type: team, id: "{{ ref.team_manager.id }}" }]
      channel_policy: { mode: email_only }

  - ref: wf_welcome
    kind: workflow
    about: A welcome pack email and a short SMS when a new resident is registered.
    body:
      name: "Resident 1: welcome pack"
      description: When the New Resident form is submitted, the welcome pack by email and a short SMS.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_resident.id }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_welcome_email.id }}" } } } }
        - { id: s2, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_welcome_sms.id }}" } } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_urgent
    kind: workflow
    about: 'An urgent request: an SMS, a task due in hours that escalates to the estate manager, and a closing SMS when it is fixed.'
    body:
      name: "Request 1: urgent"
      description: An urgent maintenance request gets an SMS and a fast task for the facility team, escalated to the estate manager. When it is closed as fixed, the resident gets an SMS.
      enabled: false
      trigger_type: event
      trigger_config:
        event_name: onboarding.submitted
        form_id: "{{ ref.form_request.id }}"
        property_filters: [{ property: urgency, cond_operator: eq, value: urgent }]
        properties: { $origamy_property_filters: true }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_urgent_sms.id }}" } } } }
        - id: s2
          type: human_task
          config:
            human_task:
              title: "URGENT {{ properties.category | default: \"request\" }}: {{ traits.unit | default: \"unit not given\" }}"
              instructions: |-
                {{ traits.first_name }} ({{ traits.phone }}) reported an urgent problem. Category: {{ properties.category | default: "not given" }}.
                What they said: {{ properties.description | default: "nothing written" }}.
                Open the form answer to see the photo, if there is one.

                Respond now: call the resident, send someone and make the situation safe. Choose Fixed only when it is truly fixed. Choose Scheduled if a visit or parts are still to come. Add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_facility.id }}" }]
              strategy: all
              completion_policy: first_response_wins
              outcome_options:
                - { id: fixed, label: Fixed, intent: positive }
                - { id: scheduled, label: "Scheduled, visit or parts to come", intent: neutral }
                - { id: needs_manager, label: Needs the estate manager, intent: neutral }
                - { id: not_reached, label: Could not reach the resident, intent: negative }
              due_hours: "{{ params.urgent_due_hours }}"
              wait: true
              wait_timeout_hours: 168
              notify: immediate
              escalation_ladder:
                - { after_hours: "{{ params.urgent_escalate_hours }}", to: [{ type: team, id: "{{ ref.team_manager.id }}" }] }
        - { id: s3, type: action, config: { action_type: set_trait, traits: { request_outcome: "{{ properties.task_outcome | default: \"unknown\" }}", request_note: "{{ properties.task_note }}" } } }
        - { id: s4, type: filter, config: { condition: { type: task_outcome, trait_key: task_outcome, cond_operator: eq, value: fixed } } }
        - { id: s5, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_closed_sms.id }}" } } } }
      re_entry_policy: every_time
      re_entry_cooldown_hours: 0
      goal: {}
      exit_conditions: []
  - ref: wf_normal
    kind: workflow
    about: 'A normal request: an SMS, a task due in two days, and a closing SMS when it is fixed.'
    body:
      name: "Request 2: normal"
      description: A normal maintenance request gets an SMS and a task for the facility team, escalated to the estate manager if nobody responds. When it is closed as fixed, the resident gets an SMS.
      enabled: false
      trigger_type: event
      trigger_config:
        event_name: onboarding.submitted
        form_id: "{{ ref.form_request.id }}"
        property_filters: [{ property: urgency, cond_operator: eq, value: normal }]
        properties: { $origamy_property_filters: true }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_normal_sms.id }}" } } } }
        - id: s2
          type: human_task
          config:
            human_task:
              title: "Request {{ properties.category | default: \"\" }}: {{ traits.unit | default: \"unit not given\" }}"
              instructions: |-
                {{ traits.first_name }} ({{ traits.phone }}) reported a problem. Category: {{ properties.category | default: "not given" }}.
                What they said: {{ properties.description | default: "nothing written" }}.
                Open the form answer to see the photo, if there is one.

                Arrange the repair and let the resident know when to expect someone. Choose Fixed only when it is truly fixed. Choose Scheduled if a visit or parts are still to come. Add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_facility.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: fixed, label: Fixed, intent: positive }
                - { id: scheduled, label: "Scheduled, visit or parts to come", intent: neutral }
                - { id: needs_manager, label: Needs the estate manager, intent: neutral }
                - { id: not_reached, label: Could not reach the resident, intent: negative }
              due_hours: "{{ params.normal_due_hours }}"
              wait: true
              wait_timeout_hours: 336
              notify: immediate
              escalation_ladder:
                - { after_hours: "{{ params.normal_escalate_hours }}", to: [{ type: team, id: "{{ ref.team_manager.id }}" }] }
        - { id: s3, type: action, config: { action_type: set_trait, traits: { request_outcome: "{{ properties.task_outcome | default: \"unknown\" }}", request_note: "{{ properties.task_note }}" } } }
        - { id: s4, type: filter, config: { condition: { type: task_outcome, trait_key: task_outcome, cond_operator: eq, value: fixed } } }
        - { id: s5, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_closed_sms.id }}" } } } }
      re_entry_policy: every_time
      re_entry_cooldown_hours: 0
      goal: {}
      exit_conditions: []

eval:
  name: 114_estates_resident_requests
  mode: task
  vertical: ""
  prompt: >-
    I run a residential estate. Make a new-resident form (unit, phone, email, household size,
    move-in date) that sends a welcome email with our estate rules, emergency numbers, how to pay
    the service charge and how to report issues. Also make a maintenance request form with unit,
    category (plumbing, electrical, security, waste, other), urgency, description and a photo.
    Urgent requests should give our facility team a task due in 2 hours and escalate to the
    estate manager after 4 hours. Normal requests get a task due in 48 hours. When a task is
    marked fixed, text the resident that we've closed their request. Send the estate manager a
    weekly report of requests by category and open tasks.
  expect_artifacts: [form, workflow, message_template, team, report]
  forbid_terms: [discount, cart, checkout, purchase, revenue]
