# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: schools.fee-reminders
title: Fee reminders
sector: schools
source:
  kind: synthesized
  notes: >-
    The school we built admissions with had no fee reminders yet, so this is written from the
    base renewal reminder, in school words, with a guardian contact and a bursary-office call
    when fees are late. Its stage and dedup advice comes from that engagement.
derived_from: base.payment-reminder
vocabulary:
  person: student
  people: students
  group: bursary team
summary: >-
  Reminds students (or their guardians) before school fees or an instalment are due, on the
  day, and when they're late, and gives the bursary team a call task for late fees.
when_to_use:
  - Fees are paid per term, semester or in instalments, on dates your student system knows.
  - Your student system can keep the next due date and whether it's paid on each profile.

requires:
  data_in:
    - how: sheet_sync
      what: "The bursar's fee sheet in Google Sheets or Excel, synced every hour as People: payer phone and email, next_fee_due, fee_status."
  channels:
    - channel: email
      for: The first reminder
      needs: A verified sending domain for {{ params.email_from }}.
    - channel: sms
      for: Due-day and late reminders
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
  events: []
  traits:
    - "{{ params.due_trait }}"
    - fee_status

parameters:
  - key: org_name
    ask: true
    question: What should messages call your school?
    label: School name
    type: string
    example: Northwind Academy
  - key: due_trait
    ask: true
    question: Which trait holds the next fee due date?
    label: Due-date trait
    type: trait_key
    default: next_fee_due
  - key: pay_url
    ask: true
    question: Where do families pay fees?
    label: Payment link
    type: url
    example: https://pay.yourschool.example
  - key: bursary_team
    ask: true
    question: Which team calls when fees are late?
    label: Bursary team
    type: team_name
    default: Bursary
  - key: remind_before
    ask: true
    question: How long before the due date should the first reminder go?
    label: First reminder
    type: duration
    default: 14d
  - key: email_from
    label: Sender address
    type: email
    default: "{{ workspace.default_sender }}"
  - key: send_time
    label: Daily send time
    type: time
    default: "09:00"
  - key: timezone
    label: Time zone for the daily run
    type: timezone
    default: "{{ workspace.timezone }}"

timings:
  - Every day at {{ params.send_time }} ({{ params.timezone }}).
  - An email {{ params.remind_before }} before the due date, an SMS on the day, and an SMS plus a bursary call task when fees are 3 days late.
  - One reminder of each kind per term (re-entry cooldown).

customer_must:
  - "Fill fee_status for everyone (paid or due). A blank status never matches, so that person gets no reminder."
  - "Keep {{ params.due_trait }} (an ISO date) and fee_status (paid or unpaid) up to date on each student's profile, and the payer's email and phone (a guardian's, for younger students)."
  - Add people to the {{ params.bursary_team }} team.
  - Verify the sending domain; for SMS, connect a provider or finish business verification.
  - Check the segments' sizes, then switch the follow-ups on.

objects:
  - ref: team_bursary
    kind: team
    about: Calls families whose fees are late.
    body: { name: "{{ params.bursary_team }}" }
  - ref: seg_due_soon
    kind: segment
    about: Fees due within the reminder window, unpaid.
    body:
      name: Fees due soon
      description: "{{ params.due_trait }} within {{ params.remind_before }}, fee_status not paid."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.due_trait }}", cond_operator: within_next, value: "{{ params.remind_before }}" }
          - { type: trait, trait_key: fee_status, cond_operator: neq, value: paid }
  - ref: seg_due_today
    kind: segment
    about: Fees due today, unpaid.
    body:
      name: Fees due today
      description: "{{ params.due_trait }} today, fee_status not paid."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.due_trait }}", cond_operator: within_last, value: 1d }
          - { type: trait, trait_key: fee_status, cond_operator: neq, value: paid }
  - ref: seg_late
    kind: segment
    about: Fees 3 to 60 days late, unpaid.
    body:
      name: Fees late
      description: "{{ params.due_trait }} 3 to 60 days ago, fee_status not paid."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.due_trait }}", cond_operator: older_than, value: 3d }
          - { type: trait, trait_key: "{{ params.due_trait }}", cond_operator: within_last, value: 60d }
          - { type: trait, trait_key: fee_status, cond_operator: neq, value: paid }
  - ref: tmpl_soon
    kind: template
    about: Reminder email before the due date.
    body:
      name: School fees due soon (email)
      channel: email
      category: Fees
      style: html
      status: active
      subject: "School fees reminder from {{ params.org_name }}"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "" } }
      variables: {}
      body: >-
        <p>Dear {{ traits.first_name | default: "Parent or guardian" }},</p>
        <p>This is a reminder that the next school fees payment to {{ params.org_name }} is due
        soon. You can pay online at any time.</p>
        <p><a href="{{ params.pay_url }}">Pay school fees</a></p>
        <p>If you've already paid, thank you. If you need to discuss a payment plan, reply to this email.</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_today
    kind: template
    about: SMS on the due day.
    body:
      name: School fees due today (SMS)
      channel: sms
      category: Fees
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: school fees are due today. Pay here: {{ params.pay_url }}. Already paid? Thank you. Reply STOP to opt out."
  - ref: tmpl_late
    kind: template
    about: SMS when fees are late.
    body:
      name: School fees overdue (SMS)
      channel: sms
      category: Fees
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: we haven't received this term's school fees yet. If you need help or a payment plan, please contact the bursary. Pay: {{ params.pay_url }} Reply STOP to opt out."
  - ref: wf_soon
    kind: workflow
    about: Daily run over "fees due soon".
    body:
      name: "Fees 1: due soon"
      description: Every day, an email to families whose fees are due soon.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_due_soon.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: email, template_id: "{{ ref.tmpl_soon.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 720
      goal: {}
      exit_conditions: []
  - ref: wf_today
    kind: workflow
    about: Daily run over "fees due today".
    body:
      name: "Fees 2: due today"
      description: Every day, an SMS to families whose fees are due today.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_due_today.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_today.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 48
      goal: {}
      exit_conditions: []
  - ref: wf_late
    kind: workflow
    about: Daily run over "fees late"; an SMS and a bursary call task.
    body:
      name: "Fees 3: late"
      description: Every day, an SMS and a bursary call for families whose fees are late.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_late.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_late.id }}" } } } }
        - id: s2
          type: human_task
          config:
            human_task:
              title: "Late fees: {{ traits.first_name }} {{ traits.last_name }}"
              instructions: |-
                Fees for {{ traits.first_name }} {{ traits.last_name }} are late. Payer phone: {{ traits.phone }}
                Call to check in and agree a date or a payment plan. Note what was agreed.
              assignee_refs: [{ type: team, id: "{{ ref.team_bursary.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: paying, label: Will pay, intent: positive }
                - { id: plan, label: Payment plan agreed, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: 72
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s3, type: action, config: { action_type: set_trait, traits: { fee_call: "{{ properties.task_outcome | default: \"unknown\" }}", fee_call_note: "{{ properties.task_note }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 720
      goal: {}
      exit_conditions: []

eval:
  name: 69_schools_fee_reminders
  mode: task
  vertical: ""
  prompt: >-
    Remind parents two weeks before school fees are due, text them on the due date, and if fees
    are three days late text again and have the bursary call them. The due date is in
    next_fee_due and fee_status says whether it's paid.
  expect_artifacts: [segment, workflow, message_template]
  forbid_terms: []
