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.