# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: church.first-timer-to-member
title: First-timer to member
sector: church
source:
  kind: engagement
  date: 2026-10-31
  ref: eng-2026-10-church-a
  notes: >-
    A church's visitor-to-member system, built through the admin API on 2026-10-31.
    Structure only: names, numbers, people and the church's own wording were removed.
derived_from: null
vocabulary:
  person: first-timer
  people: first-timers
  group: team
summary: >-
  Turns the first-timer card into a journey: a welcome email and SMS, a call task for the
  follow-up team with a reminder and escalation, a confirmation step for people who want to
  join, a membership class form that makes them members, and weekly and monthly reports to
  the church office and leaders.
when_to_use:
  - You collect first-timer or visitor cards on Sundays (on paper or a tablet) and want every visitor welcomed and called.
  - Visitors can say they would like to become members, and a membership team confirms them before a membership class.
  - Leaders want a weekly list of first-timers and a monthly "visitors to members" summary by email.

requires:
  data_in:
    - how: public_form
      what: "The First-Timer Card, filled on a phone or tablet, or typed in from paper cards by the office."
    - how: staff_form
      what: "The Membership Confirmation and Membership Class forms, filled by the membership team."
  channels:
    - channel: email
      for: Welcome, class invitation and new-member emails; all reports
      needs: A verified sending domain for {{ params.email_from }}, or Origamy's test sender while you try it.
    - channel: sms
      for: Thank-you SMS, class invitation and new-member SMS
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC). The free plan allows 30 SMS a month before verification.
  events:
    - onboarding.submitted   # sent by Origamy forms on every submission
  traits:
    - lifecycle_status       # first_timer → member, set by hidden form fields
    - membership_interest    # yes | no | not_sure
    - membership_decision    # confirmed | not_joining | undecided, set by the membership team

parameters:
  - key: org_name
    ask: true
    question: What should messages call your church?
    label: Church name
    type: string
    example: Grace Community Church
  - key: follow_up_team
    ask: true
    question: Who calls first-timers? (a team; you add the people later)
    label: Follow-up team
    type: team_name
    default: Follow-up team
  - key: escalation_team
    ask: true
    question: Who should hear if a call isn't made in time?
    label: Escalation team
    type: team_name
    default: Pastors
  - key: membership_team
    ask: true
    question: Who talks to people who want to join, and confirms them?
    label: Membership team
    type: team_name
    default: Membership team
  - key: office_team
    ask: true
    question: Who enters paper cards and gets the reports?
    label: Office team
    type: team_name
    default: Church office
  - key: email_from
    ask: true
    question: Which address should emails come from?
    help: It must be on a domain you verify in Settings → Channels.
    label: Sender address
    type: email
    example: welcome@yourchurch.example
  - key: baptismal_class
    ask: true
    question: Do you also run a baptismal class people register for?
    label: Baptismal class
    type: boolean
    default: true
  - key: timezone
    label: Time zone for reports
    type: timezone
    default: "{{ workspace.timezone }}"
  - key: default_country
    label: Default phone country on the forms
    type: country_code
    default: "{{ workspace.country }}"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: Friend
  - key: welcome_delay
    label: Welcome email after the card is entered
    type: duration
    default: 2h
  - key: sms_delay
    label: Thank-you SMS after the welcome email
    type: duration
    default: 2d
  - key: call_delay
    label: Call task created after the card is entered
    type: duration
    default: 2d
  - key: call_due_hours
    label: Hours the follow-up team has to make the call
    type: hours
    default: 48
  - key: escalate_after_hours
    label: Hours after which an unmade call escalates
    type: hours
    default: 48
  - key: weekly_report_cron
    label: Weekly first-timer list
    type: cron
    default: "0 8 * * 6"   # Saturday 08:00
  - key: entry_check_cron
    label: Sunday check that cards were entered
    type: cron
    default: "0 18 * * 0"  # Sunday 18:00
  - key: monthly_report_cron
    label: Monthly summary
    type: cron
    default: "0 9 1 * *"   # 1st of the month, 09:00

timings:
  - Welcome email {{ params.welcome_delay }} after the card is entered, thank-you SMS {{ params.sms_delay }} later.
  - Call task {{ params.call_delay }} after the card, due in {{ params.call_due_hours }} hours, with reminders before it is due; escalates to {{ params.escalation_team }} after {{ params.escalate_after_hours }} hours.
  - The membership team's confirmation task has no deadline.
  - "Reports (cron, in {{ params.timezone }}): Sunday card check {{ params.entry_check_cron }}, weekly list {{ params.weekly_report_cron }}, monthly summary {{ params.monthly_report_cron }}. The new-members report is sent by its follow-up, at most once a day."

customer_must:
  - Add people to the {{ params.follow_up_team }}, {{ params.escalation_team }}, {{ params.membership_team }} and {{ params.office_team }} teams (Settings → Collaborators → Teams). Tasks and reports go nowhere while a team is empty.
  - Verify the sending domain for {{ params.email_from }}, and connect an SMS provider or finish business verification for managed SMS.
  - Review and publish the forms (First-Timer Card, Membership Confirmation, Membership Class, and Baptismal Class if you run one). Publishing makes their links live; nothing sends.
  - Switch the follow-ups and reports on (a follow-up only runs once it's on), run one test person through the card using your own phone and email, check the welcome, call task and segments, then erase the test person.

objects:
  - ref: settings_tz
    kind: settings
    about: Sets the workspace time zone first; schedules and reports take it when they're saved.
    body: { timezone: "{{ params.timezone }}" }

  # ---- teams (empty; people are added by the customer) ----
  - ref: team_follow_up
    kind: team
    about: Gets the first-timer call tasks.
    body: { name: "{{ params.follow_up_team }}" }
  - ref: team_escalation
    kind: team
    about: Hears about calls not made in time; gets the monthly summary.
    body: { name: "{{ params.escalation_team }}" }
  - ref: team_membership
    kind: team
    about: Confirms people who want to join; gets the weekly list.
    body: { name: "{{ params.membership_team }}" }
  - ref: team_office
    kind: team
    about: Enters paper cards; gets the Sunday check, new-members report and monthly summary.
    body: { name: "{{ params.office_team }}" }

  # ---- forms ----
  - ref: form_card
    kind: form
    about: The first-timer card. Sets lifecycle_status to first_timer and records whether they want to join.
    body:
      name: First-Timer Card
      slug: first-timer-card
      description: Filled by first-timers, or typed in from paper cards by the office team.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Submit, success_message: "Thank you for worshipping with us at {{ params.org_name }}!" }
        pages:
          - id: p1
            title: Welcome to {{ params.org_name }}
            description: We're glad you came. Please tell us a little about yourself.
            fields:
              - { id: f_date, key: visit_date, type: date, label: Date, required: true, maps_to: { mode: custom, key: visit_date }, config: { default_today: true } }
              - { 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_addr, key: home_address, type: address, label: Home address, required: false, maps_to: { mode: mapped, slot: profile.address }, config: { parts: [street, city, state], part_labels: { city: City or area } } }
              - { id: f_bday, key: birthday, type: date, label: Birthday, required: false, maps_to: { mode: custom, key: birthday, trait: true }, config: { month_day: true, month_trait: birth_month, day_trait: birth_day } }
              - { id: f_invited, key: invited_by, type: short_text, label: Invited by, required: false, maps_to: { mode: custom, key: invited_by, trait: true } }
              - id: f_member_elsewhere
                key: church_member
                type: multiple_choice
                label: Are you a member of any church?
                required: true
                maps_to: { mode: custom, key: church_member, trait: true }
                options: [{ value: "yes", label: "Yes" }, { value: "no", label: "No" }]
              - id: f_interest
                key: membership_interest
                type: multiple_choice
                label: Would you like to become a member of our church?
                required: false
                maps_to: { mode: custom, key: membership_interest, trait: true }
                options: [{ value: "yes", label: "Yes" }, { value: "no", label: "No" }, { value: not_sure, label: Not sure yet }]
              - { id: f_prayer, key: prayer_request, type: long_text, label: Prayer request, required: false, maps_to: { mode: custom, key: prayer_request } }
              - { id: f_stage, key: stage, type: hidden, label: Stage, required: false, default: first_timer, maps_to: { mode: custom, key: lifecycle_status, trait: true }, config: { param: stage } }
      verification_settings: { required: false }
  - ref: form_confirm
    kind: form
    about: Staff-only form the membership team fills to record a decision. Matched to the person by phone.
    body:
      name: Membership Confirmation
      slug: membership-confirmation
      description: For the membership team. Use the phone number from the first-timer card.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Save decision, success_message: Saved. Thank you. }
        pages:
          - id: p1
            title: Membership confirmation
            description: Membership team only.
            fields:
              - { id: f_phone, key: phone, type: phone, label: Their phone number, required: true, maps_to: { mode: mapped, slot: profile.phone }, config: { country_picker: true, default_country: "{{ params.default_country }}", verify: "off" } }
              - { id: f_first, key: first_name, type: short_text, label: Their first name, required: true, maps_to: { mode: custom, key: first_name, trait: true } }
              - { id: f_last, key: last_name, type: short_text, label: Their surname, required: true, maps_to: { mode: custom, key: last_name, trait: true } }
              - id: f_decision
                key: membership_decision
                type: multiple_choice
                label: Decision
                required: true
                maps_to: { mode: custom, key: membership_decision, trait: true }
                options:
                  - { value: confirmed, label: "Confirmed: will attend the membership class" }
                  - { value: not_joining, label: Not joining }
                  - { value: undecided, label: Still deciding }
              - { id: f_by, key: confirmed_by, type: short_text, label: Your name, required: true, maps_to: { mode: custom, key: confirmed_by } }
              - { id: f_notes, key: membership_notes, type: long_text, label: Notes, required: false, maps_to: { mode: custom, key: membership_notes } }
      verification_settings: { required: false }
  - ref: form_class
    kind: form
    about: Filled at the membership class. A hidden field sets lifecycle_status to member.
    body:
      name: Membership Class
      slug: membership-class
      description: Filled at the membership class. Submitting it makes the person a member.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Submit, success_message: "Welcome to the {{ params.org_name }} family!" }
        pages:
          - id: p1
            title: Membership class
            description: Please fill this in at the membership class.
            fields:
              - { id: f_date, key: class_date, type: date, label: Class date, required: true, maps_to: { mode: custom, key: class_date }, config: { default_today: true } }
              - { 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_route
                key: membership_route
                type: single_select
                label: How are you joining?
                required: true
                maps_to: { mode: custom, key: membership_route, trait: true }
                options: [{ value: first_timer, label: I first came as a visitor }, { value: transfer, label: By transfer from another church }]
              - { id: f_prev, key: previous_church, type: long_text, label: Your previous church, required: true, maps_to: { mode: custom, key: previous_church, trait: true }, visible_if: { source: form_answer, key: membership_route, op: eq, value: transfer } }
              - { id: f_stage, key: stage, type: hidden, label: "", required: false, default: member, maps_to: { mode: custom, key: lifecycle_status, trait: true } }
      verification_settings: { required: false }

  - ref: form_baptism
    kind: form
    when: { param: baptismal_class, equals: true }
    about: Registration for the baptismal class. Sets baptismal_class to registered.
    body:
      name: Baptismal Class
      slug: baptismal-class
      description: Registration for the next baptismal class.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Register, success_message: We will contact you about the next baptismal class. }
        pages:
          - id: p1
            title: Baptismal class
            description: Register for the next baptismal class.
            fields:
              - { id: f_date, key: registration_date, type: date, label: Date, required: true, maps_to: { mode: custom, key: registration_date }, config: { default_today: true } }
              - { 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_bc, key: baptismal_class, type: hidden, label: "", required: false, default: registered, maps_to: { mode: custom, key: baptismal_class, trait: true } }
      verification_settings: { required: false }

  # ---- segments ----
  - ref: seg_first_timers
    kind: segment
    about: Everyone whose stage is first_timer.
    body:
      name: First-timers
      description: Everyone whose stage is first_timer (set by the First-Timer Card).
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: lifecycle_status, cond_operator: eq, value: first_timer }
  - ref: seg_potential
    kind: segment
    about: First-timers who said yes to membership and weren't marked "not joining". Entering it starts the membership team's task.
    body:
      name: Potential members
      description: Said yes to "Would you like to become a member?", not a member yet, not marked "Not joining".
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: membership_interest, cond_operator: eq, value: "yes" }
          - { type: trait, trait_key: lifecycle_status, cond_operator: neq, value: member }
          - operator: or
            conditions:
              - { type: trait, trait_key: membership_decision, cond_operator: not_exists }
              - { type: trait, trait_key: membership_decision, cond_operator: neq, value: not_joining }
  - ref: seg_visiting
    kind: segment
    about: First-timers who didn't say yes to membership.
    body:
      name: Visitors (not joining yet)
      description: First-timers who said no, not sure, or left it blank.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: lifecycle_status, cond_operator: eq, value: first_timer }
          - operator: or
            conditions:
              - { type: trait, trait_key: membership_interest, cond_operator: neq, value: "yes" }
              - { type: trait, trait_key: membership_interest, cond_operator: not_exists }
  - ref: seg_members
    kind: segment
    about: Everyone whose stage is member.
    body:
      name: Members
      description: Stage is member (set by the Membership Class form).
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: lifecycle_status, cond_operator: eq, value: member }

  # ---- templates ----
  - ref: tmpl_welcome_email
    kind: template
    about: Welcome email to a first-timer.
    body:
      name: First-timer welcome (email)
      channel: email
      category: Onboarding
      style: html
      status: active
      subject: "Thank you for worshipping with us, {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "" } }
      variables: {}
      body: >-
        <p>Dear {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>Thank you for worshipping with us at {{ params.org_name }}. It was a joy to have you,
        and you are welcome back any time.</p>
        <p>Someone from our team will give you a call this week. If you have a question or a prayer
        request, just reply to this email.</p>
        <p>God bless you,<br>{{ params.org_name }}</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_thanks_sms
    kind: template
    about: Thank-you SMS a couple of days later.
    body:
      name: First-timer thank-you (SMS)
      channel: sms
      category: Onboarding
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, thank you for visiting {{ params.org_name }}. We'd love to see you again this Sunday. God bless you. Reply STOP to opt out."
  - ref: tmpl_invite_email
    kind: template
    about: Membership class invitation, sent once the membership team confirms.
    body:
      name: Membership class invitation (email)
      channel: email
      category: Onboarding
      style: html
      status: active
      subject: "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, you're invited to our membership class"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "" } }
      variables: {}
      body: >-
        <p>Dear {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>Thank you for letting us know you would like to become a member of {{ params.org_name }}.
        The next step is our membership class: what we believe, how the church works, and how you
        can belong and serve. Our membership team will contact you with the date and time.</p>
        <p>God bless you,<br>The membership team</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_invite_sms
    kind: template
    about: Membership class invitation by SMS.
    body:
      name: Membership class invitation (SMS)
      channel: sms
      category: Onboarding
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, thank you for choosing to join {{ params.org_name }}! Our membership team will contact you with the date of the next membership class. Reply STOP to opt out."
  - ref: tmpl_member_email
    kind: template
    about: Welcome email to a new member.
    body:
      name: Welcome new member (email)
      channel: email
      category: Onboarding
      style: html
      status: active
      subject: "Welcome to the {{ params.org_name }} family, {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}!"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "" } }
      variables: {}
      body: >-
        <p>Dear {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>Congratulations, and welcome! Having completed the membership class, you are now a member
        of {{ params.org_name }}. Ask about our ministries and fellowship groups on Sunday, and we
        will help you find your place.</p>
        <p>God bless you,<br>{{ params.org_name }}</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_member_sms
    kind: template
    about: Welcome SMS to a new member.
    body:
      name: Welcome new member (SMS)
      channel: sms
      category: Onboarding
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Dear {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, welcome to the {{ params.org_name }} family! You are now a member. We are glad to be family with you. Reply STOP to opt out."

  - ref: tmpl_baptism_sms
    kind: template
    when: { param: baptismal_class, equals: true }
    about: Confirmation SMS for baptismal class registrations.
    body:
      name: Baptismal class registration (SMS)
      channel: sms
      category: Onboarding
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, thank you for registering for the baptismal class at {{ params.org_name }}. We will contact you with the class date. Reply STOP to opt out."

  # ---- questions behind the reports and dashboard ----
  - ref: q_cards_today
    kind: saved_question
    about: How many first-timer cards were entered (used by the Sunday check).
    body:
      name: "Report: first-timer cards entered"
      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_card.id }}" }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: people }]
  - ref: q_want_join_list
    kind: saved_question
    about: Table of first-timers who want to join (for the weekly list).
    body:
      name: "Report: first-timers who want to join (list)"
      data_plane_id: default
      viz_type: table
      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_card.id }}" }
            - { field: properties.membership_interest, op: eq, value: "yes" }
        aggregations: [{ fn: count, alias: forms }]
        breakouts: [{ field: properties.visit_date }, { field: properties.first_name }, { field: properties.last_name }, { field: properties.invited_by }]
        limit: 500
  - ref: q_visiting_list
    kind: saved_question
    about: Table of first-timers just visiting (for the weekly list).
    body:
      name: "Report: first-timers just visiting (list)"
      data_plane_id: default
      viz_type: table
      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_card.id }}" }
            - { field: properties.membership_interest, op: neq, value: "yes" }
        aggregations: [{ fn: count, alias: forms }]
        breakouts: [{ field: properties.visit_date }, { field: properties.first_name }, { field: properties.last_name }, { field: properties.membership_interest }]
        limit: 500
  - ref: q_confirmed
    kind: saved_question
    about: People the membership team confirmed.
    body:
      name: "Report: confirmed by the membership team"
      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_confirm.id }}" }
            - { field: properties.membership_decision, op: eq, value: confirmed }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: people }]
  - ref: q_new_members
    kind: saved_question
    about: New members (Membership Class forms).
    body:
      name: "Report: new members"
      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_class.id }}" }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: people }]
  - ref: q_new_members_list
    kind: saved_question
    about: Table of new members.
    body:
      name: "Report: new members (list)"
      data_plane_id: default
      viz_type: table
      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_class.id }}" }
        aggregations: [{ fn: count, alias: forms }]
        breakouts: [{ field: properties.class_date }, { field: properties.first_name }, { field: properties.last_name }, { field: properties.membership_route }]
        limit: 500
  - ref: q_stage_first_timers
    kind: saved_question
    about: First-timers now (reads traits, which stay correct when segment membership counts lag).
    body:
      name: First-timers (now)
      data_plane_id: default
      viz_type: number
      viz_settings: {}
      visibility: workspace
      query_ast:
        table: user_traits
        filters:
          operator: and
          conditions:
            - { field: trait_key, op: eq, value: lifecycle_status }
            - { field: value_string, op: eq, value: first_timer }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: people }]
  - ref: q_stage_members
    kind: saved_question
    about: Members now.
    body:
      name: Members (now)
      data_plane_id: default
      viz_type: number
      viz_settings: {}
      visibility: workspace
      query_ast:
        table: user_traits
        filters:
          operator: and
          conditions:
            - { field: trait_key, op: eq, value: lifecycle_status }
            - { field: value_string, op: eq, value: member }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: people }]
  - ref: q_first_timers_weekly
    kind: saved_question
    about: First-timers per week, for the dashboard.
    body:
      name: First-timers per week
      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_card.id }}" }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: people }]
        breakouts: [{ field: timestamp, bucket: week }]
        order_by: [{ field: timestamp, direction: asc }]
  - ref: funnel_journey
    kind: saved_funnel
    about: Card → confirmed → membership class, over 90 days.
    body:
      name: First-timer to member journey
      data_plane_id: default
      visibility: workspace
      definition:
        range: 180d
        window: 7776000
        view: steps
        breakdown: ""
        interval: ""
        compare: false
        steps:
          - { type: track, name: onboarding.submitted, filters: [{ key: form_id, op: eq, value: "{{ ref.form_card.id }}" }] }
          - { type: track, name: onboarding.submitted, filters: [{ key: form_id, op: eq, value: "{{ ref.form_confirm.id }}" }, { key: membership_decision, op: eq, value: confirmed }] }
          - { type: track, name: onboarding.submitted, filters: [{ key: form_id, op: eq, value: "{{ ref.form_class.id }}" }] }

  # ---- dashboard ----
  - ref: dash_journey
    kind: dashboard
    about: First-timers to members at a glance.
    body:
      name: First-timers to members
      description: First-timers, potential members, the membership class and new members.
      visibility: workspace
      cards:
        - { saved_funnel_id: "{{ ref.funnel_journey.id }}", x: 0, y: 0, w: 12, h: 6 }
        - { saved_question_id: "{{ ref.q_stage_first_timers.id }}", viz_type: number, x: 0, y: 6, w: 6, h: 3 }
        - { saved_question_id: "{{ ref.q_stage_members.id }}", viz_type: number, x: 6, y: 6, w: 6, h: 3 }
        - { saved_question_id: "{{ ref.q_first_timers_weekly.id }}", viz_type: bar, x: 0, y: 9, w: 12, h: 5 }

  # ---- reports (all land off) ----
  - ref: report_entry_check
    kind: report
    about: Sunday evening check to the office team, with an alert when no cards were entered that day.
    body:
      name: "Sunday: first-timer cards entered today"
      description: Flags a Sunday with no cards entered, so paper cards can be typed in.
      schedule_cron: "{{ params.entry_check_cron }}"
      timezone: "{{ params.timezone }}"
      enabled: false
      spec:
        period: today
        compare: none
        week_start: sunday
        sections:
          - id: cards_today
            kind: saved_question
            title: First-timer cards entered today
            viz: number
            params: { question_id: "{{ ref.q_cards_today.id }}" }
            alert: { op: eq, value: 0, text: "No first-timer cards were entered today. If there were paper cards, please enter them on the First-Timer Card form." }
          - { id: howto, kind: text, params: { body: "Enter paper cards here: {{ ref.form_card.url }}" } }
      recipients: [{ type: team, id: "{{ ref.team_office.id }}" }]
      channel_policy: { mode: email_only }
  - ref: report_weekly
    kind: report
    about: The week's first-timers, split into potential members and visitors, to the follow-up and membership teams.
    body:
      name: "Weekly: this week's first-timers"
      description: The week's first-timers, split into potential members and visitors.
      schedule_cron: "{{ params.weekly_report_cron }}"
      timezone: "{{ params.timezone }}"
      enabled: false
      spec:
        period: last_7_days
        compare: none
        week_start: sunday
        sections:
          - { id: want_join, kind: saved_question, title: Want to become members, viz: table, params: { question_id: "{{ ref.q_want_join_list.id }}" } }
          - { id: visiting, kind: saved_question, title: Just visiting, viz: table, params: { question_id: "{{ ref.q_visiting_list.id }}" } }
          - id: stages
            kind: segment_sizes
            title: Where everyone is now
            params:
              segment_ids: ["{{ ref.seg_potential.id }}", "{{ ref.seg_visiting.id }}"]
      recipients: [{ type: team, id: "{{ ref.team_follow_up.id }}" }, { type: team, id: "{{ ref.team_membership.id }}" }]
      channel_policy: { mode: email_only, pii_in_email: true }
  - ref: report_new_members
    kind: report
    about: New members, sent by the new-member follow-up (no schedule).
    body:
      name: New members
      description: Sent by the new-member follow-up on any day someone completes the Membership Class form.
      schedule_cron: ""
      timezone: "{{ params.timezone }}"
      enabled: false
      spec:
        period: today
        compare: none
        week_start: sunday
        sections:
          - { id: new_count, kind: saved_question, title: New members today, viz: number, params: { question_id: "{{ ref.q_new_members.id }}" } }
          - { id: new_list, kind: saved_question, title: New members, viz: table, params: { question_id: "{{ ref.q_new_members_list.id }}" } }
      recipients: [{ type: team, id: "{{ ref.team_office.id }}" }, { type: team, id: "{{ ref.team_escalation.id }}" }]
      channel_policy: { mode: email_only, pii_in_email: true }
  - ref: report_monthly
    kind: report
    about: Last month's journey from first-timer to member, to the office team and leaders.
    body:
      name: "Monthly: first-timers to members"
      description: Last month's journey from first-timer to member.
      schedule_cron: "{{ params.monthly_report_cron }}"
      timezone: "{{ params.timezone }}"
      enabled: false
      spec:
        period: previous_month
        compare: previous_period
        week_start: sunday
        sections:
          - { id: m_cards, kind: saved_question, title: First-timers, viz: number, params: { question_id: "{{ ref.q_cards_today.id }}" } }
          - { id: m_conf, kind: saved_question, title: Confirmed by the membership team, viz: number, params: { question_id: "{{ ref.q_confirmed.id }}" } }
          - { id: m_new, kind: saved_question, title: New members, viz: number, params: { question_id: "{{ ref.q_new_members.id }}" } }
          - id: m_stages
            kind: segment_sizes
            title: Where everyone is now
            params:
              segment_ids: ["{{ ref.seg_first_timers.id }}", "{{ ref.seg_potential.id }}", "{{ ref.seg_visiting.id }}", "{{ ref.seg_members.id }}"]
          - { id: m_tasks, kind: tasks_summary, title: Follow-up tasks, params: {} }
          - { id: m_link, kind: text, params: { body: "See the full journey on the dashboard: {{ ref.dash_journey.url }}" } }
      recipients: [{ type: team, id: "{{ ref.team_office.id }}" }, { type: team, id: "{{ ref.team_escalation.id }}" }]
      channel_policy: { mode: email_only }

  # ---- follow-ups (all land off) ----
  - ref: wf_welcome
    kind: workflow
    about: Welcome email after the card, thank-you SMS later.
    body:
      name: "1. First-timer welcome"
      description: When the First-Timer Card is submitted, a welcome email, then a thank-you SMS.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_card.id }}" }
      steps:
        - { id: s1, type: delay, config: { duration: "{{ params.welcome_delay }}" } }
        - { id: s2, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_welcome_email.id }}" } } } }
        - { id: s3, type: delay, config: { duration: "{{ params.sms_delay }}" } }
        - { id: s4, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_thanks_sms.id }}" } } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_call
    kind: workflow
    about: Call task for the follow-up team; escalates if not done. The outcome and note are saved on the person.
    body:
      name: "2. First-timer follow-up call"
      description: A call task for the follow-up team, with reminders and escalation. The call outcome and note are saved on the person.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_card.id }}" }
      steps:
        - { id: s1, type: delay, config: { duration: "{{ params.call_delay }}" } }
        - id: s2
          type: human_task
          config:
            human_task:
              title: "Call first-timer: {{ traits.first_name }} {{ traits.last_name }}"
              instructions: |-
                Please call {{ traits.first_name }} {{ traits.last_name }} on {{ traits.phone }} to welcome them and thank them for worshipping with us.
                Would like to be a member: {{ traits.membership_interest | default: "not answered" }}
                Prayer request: {{ properties.prayer_request | default: "none" }}

                When done, choose Called or Could not reach and add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_follow_up.id }}" }]
              strategy: all
              completion_policy: first_response_wins
              outcome_options:
                - { id: called, label: Called, intent: positive }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: "{{ params.call_due_hours }}"
              wait: true
              wait_timeout_hours: 168
              notify: immediate
              escalation_ladder:
                - { after_hours: "{{ params.escalate_after_hours }}", to: [{ type: team, id: "{{ ref.team_escalation.id }}" }] }
        - { id: s3, type: action, config: { action_type: set_trait, traits: { follow_up_call: "{{ properties.task_outcome | default: \"unknown\" }}", follow_up_note: "{{ properties.task_note }}" } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_confirm
    kind: workflow
    about: When someone becomes a potential member, the membership team gets a task (no deadline) to talk with them and fill the confirmation form.
    body:
      name: "3. Potential member: confirmation"
      description: The membership team speaks with the person and records the decision on the Membership Confirmation form.
      enabled: false
      trigger_type: segment_enter
      trigger_config: { segment_id: "{{ ref.seg_potential.id }}" }
      steps:
        - id: s1
          type: human_task
          config:
            human_task:
              title: "Confirm membership: {{ traits.first_name }} {{ traits.last_name }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) said they would like to become a member.

                1. Speak with them about joining.
                2. Record the decision on the Membership Confirmation form: {{ ref.form_confirm.url }}
                3. Mark this task done.
              assignee_refs: [{ type: team, id: "{{ ref.team_membership.id }}" }]
              strategy: all
              completion_policy: first_response_wins
              outcome_options: [{ id: done, label: Decision recorded, intent: positive }]
              due_hours: 0
              wait: false
              notify: immediate
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_invite
    kind: workflow
    about: When the membership team records "Confirmed", the class invitation goes by email and SMS.
    body:
      name: "4. Confirmed: class invitation"
      description: Membership class invitation by email and SMS once the membership team confirms.
      enabled: false
      trigger_type: event
      trigger_config:
        event_name: onboarding.submitted
        form_id: "{{ ref.form_confirm.id }}"
        property_filters: [{ property: membership_decision, cond_operator: eq, value: confirmed }]
        properties: { $origamy_property_filters: true }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_invite_email.id }}" } } } }
        - { id: s2, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_invite_sms.id }}" } } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_new_member
    kind: workflow
    about: Welcome for new members, and the new-members report (at most once a day).
    body:
      name: "5. New member: welcome and report"
      description: When the Membership Class form is submitted, a welcome email and SMS, and the New members report.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_class.id }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_member_email.id }}" } } } }
        - { id: s2, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_member_sms.id }}" } } } }
        - { id: s3, type: send_report, config: { send_report: { report_id: "{{ ref.report_new_members.id }}", every: day } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []


  - ref: wf_baptism
    kind: workflow
    when: { param: baptismal_class, equals: true }
    about: Confirmation SMS when someone registers for the baptismal class.
    body:
      name: "6. Baptismal class registration"
      description: When the Baptismal Class form is submitted, a confirmation SMS.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_baptism.id }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_baptism_sms.id }}" } } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []

eval:
  name: 57_church_first_timer_to_member
  mode: task
  vertical: church
  # The real request, verbatim (it names no people or places).
  prompt: "we want to track visitors basically, and see how they can become members finally.... we are looking at first timer, if indication to be a member is on, keep in a segment(potential member), for first timer, we want to send a welcome message after like 2 hours of inputing it. send sms after two days, create task for the follow up group to call manually and report if call was made or not and drop comments.... if task not done, send a reminder after a day and after 2 days if no  action escalate. if no input on a sunday, send a mail to the church office collaborator(to see if they can fill in the first timer form that has been imputed manualy) by saturday of every week, send list of first timer (potential members and just visitors) and  to the visitation and follow-up team and to the membership team. the membership team have the task to confirm membership or not... and that has no time limit.  membership should also have form for baptismal class and membership class (by transfer or from first timer flow).... on filling membership class, members segment should move to membership. if there are new members (those that indicated to join the church) for reporting send  a report to the church office, church pastors. monthly report form funnel to church office, church posters and church deacons (group). we can keep all this flows as seperate implemennttions."
  expect_artifacts: [segment, form, message_template, workflow, team, report]
  forbid_terms: [discount, cart, checkout, purchase, revenue, customers, upsell]
