Skip to main content

Survey Authoring Guide

Surveys are defined as YAML files in src/surveys/. Each file describes one survey with its metadata and questions. The survey engine loads all YAML files on startup and resolves composite surveys automatically.

Basic Structure

Every survey needs an id, name, description, and either a questions list or an includes list (never both).

id: daily_checkin
name: Daily Check-In
description: A brief daily well-being survey.
questions:
- id: mood
text: "How are you feeling right now?"
type: likert
scale_min: 1
scale_max: 5
scale_min_label: "Very bad"
scale_max_label: "Very good"

You can also add a source field to cite the instrument the survey is based on:

source: "Thompson, E. R. (2007). Development and validation of an internationally reliable short-form of the Positive and Negative Affect Schedule (PANAS)."

Question Types

Likert

A numeric scale with labeled endpoints. Participants select a value between scale_min and scale_max.

- id: alertness
text: "How alert do you feel right now?"
type: likert
scale_min: 1
scale_max: 5
scale_min_label: "not at all"
scale_max_label: "extremely"
FieldRequiredDescription
scale_minYesMinimum value (usually 1)
scale_maxYesMaximum value (e.g. 5, 7, 10)
scale_min_labelYesLabel for the minimum end
scale_max_labelYesLabel for the maximum end

Choice

A list of options the participant selects from. Rendered as inline buttons.

- id: what_doing
text: "What are you doing right now?"
type: choice
options:
- Work
- Household
- Self-care
- Relaxation
- Sport/physical activity
- Eating/drinking
- Traveling/on the way
- Something else
- Nothing
FieldRequiredDescription
optionsYesList of string options

Yes/No

A simple boolean question rendered as two buttons.

- id: has_napped
text: "Did you nap today?"
type: yesno

The response is stored as "Yes" or "No".

Text

A free-text response. The participant types their answer as a regular message.

- id: notes
text: "Is there anything else you'd like to share?"
type: text

Photo

Asks the participant to upload a photo.

- id: environment
text: "Please take a photo of your current environment."
type: photo

Voice

Asks the participant to send a voice message.

- id: voice_diary
text: "Please record a short voice note about your day."
type: voice

Cognitive

Launches an interactive cognitive assessment powered by m2c2kit. The participant completes the task in a web app, and results are returned automatically.

- id: symbol_search
text: "Symbol Search"
title: "Symbol Search"
description: "Identify matching symbol pairs as quickly and accurately as you can."
type: cognitive
assessment: symbol-search
number_of_trials: 6
FieldRequiredDescription
assessmentYesAssessment type: symbol-search, color-shapes, grid-memory, color-dots, pvt-ba, prices
number_of_trialsNoNumber of trials in the assessment
titleNoTitle shown in the assessment UI
descriptionNoDescription shown before the task starts
assessment_paramsNoAdditional parameters passed to the assessment (e.g. number_of_items for prices)

For assessments that need extra configuration, use assessment_params:

- id: prices
text: "Prices"
type: cognitive
assessment: prices
assessment_params:
number_of_items: 5

Time

Collects a time value. Supports three modes.

# Clock mode — select a specific time of day
- id: wake_time
text: "What time did you wake up?"
type: time
time_mode: clock

# Scroll mode — select hours and minutes
- id: bed_time
text: "What time did you go to bed?"
type: time
time_mode: scroll

# Duration mode — select a duration
- id: nap_duration
text: "How long was your nap?"
type: time
time_mode: duration
max_hours: 4
minute_step: 5
FieldRequiredDescription
time_modeYesOne of clock, scroll, or duration
max_hoursNoMaximum hours for duration mode
minute_stepNoMinute increment for duration mode (e.g. 5 for 5-minute steps)

Webapp Choice

An interactive web-based choice rendered in the PWA or via Telegram WebApp. Use this for rich selection interfaces that go beyond simple buttons.

- id: pain_location
text: "Where do you feel pain?"
type: webapp_choice

Optional Questions

By default, all questions are required. Set required: false to allow participants to skip a question using the /skip command.

- id: notes
text: "Any additional comments?"
type: text
required: false

Conditional Branching

Use show_if to display a question only when a previous answer meets a condition. Supported operators are equals and not_equals.

questions:
- id: has_napped
text: "Did you nap today?"
type: yesno

- id: nap_count
text: "How many times did you nap or doze?"
type: choice
options:
- "1"
- "2"
- "3"
- "4"
- "5+"
show_if:
question_id: has_napped
operator: equals
value: "Yes"

- id: nap_duration
text: "In total, how long did you nap or doze?"
type: time
time_mode: duration
max_hours: 4
minute_step: 5
show_if:
question_id: has_napped
operator: equals
value: "Yes"

When the condition is not met, the question is silently skipped and no response is recorded for it.

warning

show_if conditional branching and randomize cannot be used together in the same survey. This is enforced by the survey engine at load time.

Randomization

Enable randomize: true to shuffle question order for each survey session. This is useful for reducing order effects in repeated measures.

id: mood
name: Mood Questionnaire
randomize: true
questions:
- id: upset
text: "I feel upset right now."
type: likert
scale_min: 1
scale_max: 5
scale_min_label: "not at all"
scale_max_label: "extremely"
# ... more questions

You can also use select_n to randomly pick a subset of questions from the pool:

id: cognitive_test
name: Cognitive Assessment Battery
randomize: true
select_n: 1
questions:
- id: symbol_search
text: "Symbol Search"
type: cognitive
assessment: symbol-search
number_of_trials: 6
- id: color_shapes
text: "Color Shapes"
type: cognitive
assessment: color-shapes
number_of_trials: 6
# ... more assessments

In this example, one assessment is randomly selected from the pool each time the survey is delivered. The actual question order is recorded in the question_order column of the survey_sessions table for analysis.

Composite Surveys

Composite surveys combine multiple existing surveys into a single delivery. Use includes instead of questions to reference other survey IDs.

id: morning
name: Morning Questionnaire
description: Good morning! Please answer the following questions about last night's sleep.
includes:
- morning_ksd
- bed_exit
- sleepiness
- mood
- cognitive_test

Each included survey becomes a section. The survey engine resolves references in two passes, so included surveys must be defined in their own YAML files. Each section maintains its own randomization settings independently.

note

A survey must have either questions or includes, never both. The survey engine validates this at load time.

Deploying Changes

After editing or creating survey YAML files:

  • Telegram: Send /reload_surveys to the bot (admin command) to hot-reload all survey definitions without restarting.
  • API / Production: Restart the bot process to pick up the changes.

Survey changes take effect for new survey sessions only. Any in-progress sessions continue using the definitions that were active when they started.