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"
| Field | Required | Description |
|---|---|---|
scale_min | Yes | Minimum value (usually 1) |
scale_max | Yes | Maximum value (e.g. 5, 7, 10) |
scale_min_label | Yes | Label for the minimum end |
scale_max_label | Yes | Label 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
| Field | Required | Description |
|---|---|---|
options | Yes | List 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
| Field | Required | Description |
|---|---|---|
assessment | Yes | Assessment type: symbol-search, color-shapes, grid-memory, color-dots, pvt-ba, prices |
number_of_trials | No | Number of trials in the assessment |
title | No | Title shown in the assessment UI |
description | No | Description shown before the task starts |
assessment_params | No | Additional 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
| Field | Required | Description |
|---|---|---|
time_mode | Yes | One of clock, scroll, or duration |
max_hours | No | Maximum hours for duration mode |
minute_step | No | Minute 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.
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.
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_surveysto 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.