Creating Surveys with QML¶
This guide explains how to create surveys using QML (Questionnaire Markup Language), a YAML-based language designed for sophisticated questionnaires with formal verification, dynamic flow control, and data validation.
Quick Start¶
Every QML questionnaire begins with this structure:
qmlVersion: "1.0"
questionnaire:
title: "Your Survey Title"
blocks:
- id: b_first_section
title: "First Section"
items:
- id: q_first_question
kind: Question
title: "Your first question"
input:
control: Radio
labels:
1: "Option 1"
2: "Option 2"
Core Concepts¶
Blocks: Organizing Your Survey¶
Blocks group related questions into logical sections:
blocks:
- id: b_demographics
title: "About You"
items:
# Questions about the respondent
- id: b_preferences
title: "Your Preferences"
items:
# Questions about preferences
Best practices:
- Use descriptive IDs starting with
b_(e.g.,b_demographics,b_experience) - Group thematically related questions
- Keep dependencies within blocks when possible
Items: Types of Questions¶
QML supports four item types:
1. Comment (Instructions)¶
Display text without collecting responses:
2. Question (Single Response)¶
Ask one question and collect one response:
3. QuestionGroup (Multiple Related Questions)¶
Ask several questions with the same response format:
- id: q_satisfaction
kind: QuestionGroup
title: "Rate your satisfaction with:"
questions:
- "Customer service"
- "Product quality"
- "Delivery speed"
input:
control: Radio
labels:
1: "Very Dissatisfied"
2: "Dissatisfied"
3: "Neutral"
4: "Satisfied"
5: "Very Satisfied"
4. MatrixQuestion (Grid)¶
Collect responses in a row-by-column format:
- id: q_skills
kind: MatrixQuestion
title: "Rate your proficiency in:"
rows:
- "Python"
- "JavaScript"
- "SQL"
columns:
- "Beginner"
- "Intermediate"
- "Advanced"
input:
control: Radio
labels:
1: "No experience"
2: "Some experience"
3: "Proficient"
Input Controls¶
Every question must specify how responses are collected.
Switch (Yes/No)¶
Binary choice:
Radio (Choose One)¶
Select one option from several:
Checkbox (Choose Multiple)¶
Select zero or more options:
Checkbox Values
Use powers of 2 (1, 2, 4, 8, 16...) for checkbox options. Selected options are combined using bitwise OR.
Dropdown (Choose from List)¶
Compact selection from many options:
input:
control: Dropdown
labels:
1: "High School"
2: "Bachelor's"
3: "Master's"
4: "PhD"
left: "Education:"
Editbox (Number Input)¶
Free-form number entry:
Slider (Visual Scale)¶
Visual selection along a scale:
Range (Interval Selection)¶
Select an interval with two values (from-to). The two integers are encoded into a single value using Szudzik's pairing function.
Szudzik Pairing
The Range control encodes two integers \((a, b)\) into a single integer using Szudzik's elegant pairing function, an alternative to Cantor's pairing:
This bijection \(\mathbb{Z} \times \mathbb{Z} \to \mathbb{Z}\) allows two values to be represented as a single integer outcome while preserving the ability to decode back to the original pair. The encoding is more space-efficient than Cantor's pairing for small values.
Conditional Logic¶
Showing Questions Conditionally (Preconditions)¶
Use preconditions to show questions based on previous answers:
- id: q_has_car
kind: Question
title: "Do you own a car?"
input:
control: Switch
- id: q_car_brand
kind: Question
title: "What brand is your car?"
precondition:
- predicate: q_has_car.outcome == 1
input:
control: Dropdown
labels:
1: "Toyota"
2: "Honda"
3: "Ford"
The q_car_brand question only appears if the respondent answered "Yes" to owning a car.
Complex preconditions:
Validating Responses (Postconditions)¶
Use postconditions to ensure logical consistency:
- id: q_household_size
kind: Question
title: "How many people live in your household?"
input:
control: Editbox
min: 1
max: 20
- id: q_children
kind: Question
title: "How many children under 18 live in your household?"
postcondition:
- predicate: q_children.outcome < q_household_size.outcome
hint: "Number of children must be less than household size"
input:
control: Editbox
min: 0
max: 15
If a respondent enters invalid data (e.g., 5 children in a household of 3), the hint message is shown.
Code Blocks and Variables¶
QML allows embedded Python code for calculations and dynamic logic.
Initializing Variables¶
Use codeInit to set up tracking variables:
Computing After Responses¶
Attach code blocks to items to run calculations:
- id: q_satisfaction
kind: Question
title: "Rate your overall satisfaction (0-10)"
codeBlock: |
# Calculate satisfaction category
if q_satisfaction.outcome >= 9:
satisfaction_category = 3 # Promoter
elif q_satisfaction.outcome >= 7:
satisfaction_category = 2 # Passive
else:
satisfaction_category = 1 # Detractor
# Add to running total
total_score += q_satisfaction.outcome
input:
control: Slider
min: 0
max: 10
Global Variables
All variables in QML are global. Variables created in any code block are accessible everywhere.
Supported Python Features¶
QML supports a subset of Python:
✓ Supported:
- Basic arithmetic:
+,-,*,// - Comparisons:
<,<=,>,>=,==,!= - Boolean logic:
and,or,not - Conditionals:
if,elif,else - For loops:
for i in range(n)(n ≤ 20) - Lists, tuples, sets (≤ 20 elements)
- Variable assignment:
x = value,x += value - Accessing outcomes:
q_age.outcome
✗ Not supported:
- Imports, functions, classes
- While loops, break, continue
- Dictionary operations
- String methods
- List comprehensions
- Built-in functions (except
range) - Print statements
Common Patterns¶
Progressive Disclosure¶
Show questions progressively based on prior answers:
- id: q_employed
kind: Question
title: "Are you currently employed?"
input:
control: Switch
- id: q_job_title
kind: Question
title: "What is your job title?"
precondition:
- predicate: q_employed.outcome == 1
input:
control: Dropdown
labels:
1: "Manager"
2: "Professional"
3: "Technician"
- id: q_years_employed
kind: Question
title: "How many years have you worked there?"
precondition:
- predicate: q_employed.outcome == 1
input:
control: Editbox
min: 0
max: 50
Scoring and Categorization¶
Calculate scores and categorize respondents:
questionnaire:
codeInit: |
risk_score = 0
blocks:
- id: b_assessment
items:
- id: q_factor1
kind: Question
title: "Do you smoke?"
codeBlock: |
if q_factor1.outcome == 1:
risk_score += 20
input:
control: Switch
- id: q_factor2
kind: Question
title: "Do you exercise regularly?"
codeBlock: |
if q_factor2.outcome == 0:
risk_score += 15
input:
control: Switch
- id: q_result
kind: Comment
title: "Assessment complete"
codeBlock: |
if risk_score >= 30:
risk_category = "high"
elif risk_score >= 15:
risk_category = "moderate"
else:
risk_category = "low"
Data Quality Validation¶
Ensure response quality with cross-item validation:
- id: q_age
kind: Question
title: "Your age"
input:
control: Editbox
min: 18
max: 100
- id: q_work_experience
kind: Question
title: "Years of work experience"
postcondition:
- predicate: q_work_experience.outcome <= (q_age.outcome - 16)
hint: "Work experience cannot exceed your potential working years"
input:
control: Editbox
min: 0
max: 60
Best Practices¶
Question Design¶
- Be specific: "What is your annual household income?" vs "Income?"
- Use appropriate controls: Editbox for precise numbers, Slider for subjective ratings
- Set realistic ranges: min/max should reflect real-world constraints
- Provide context: Use
left/righttext for units and clarifications
Dependency Management¶
- Keep related items together in the same block
- Order matters: Questions must appear before they are referenced
- Test all paths: Ensure every question can be reached
Validation Strategy¶
- Set domain constraints via input control min/max
- Use postconditions for logical consistency
- Provide helpful hints: Clear error messages guide respondents
- Consider edge cases: What if someone enters extreme values?
Code Block Guidelines¶
- Initialize early: Set up variables in
codeInit - Keep it simple: Complex calculations can be hard to debug
- Remember scope: All variables are global
- Test thoroughly: Run the questionnaire validator
Formal Verification¶
QML questionnaires are automatically validated for:
- Reachability: Can every question be reached?
- Consistency: Are validation rules satisfiable?
- Cycles: Do dependencies form cycles?
- Completeness: Is there at least one valid completion path?
The validator uses mathematical analysis (SMT solving) to detect design errors before deployment.
See the Theory section for mathematical details.
Complete Example¶
qmlVersion: "1.0"
questionnaire:
title: "Customer Feedback Survey"
codeInit: |
nps_score = 0
follow_up_needed = 0
blocks:
- id: b_intro
title: "Welcome"
items:
- id: q_welcome
kind: Comment
title: "Thank you for taking our survey. It will take about 3 minutes."
- id: b_experience
title: "Your Experience"
items:
- id: q_nps
kind: Question
title: "How likely are you to recommend us? (0 = Not at all, 10 = Extremely likely)"
codeBlock: |
nps_score = q_nps.outcome
if nps_score <= 6:
follow_up_needed = 1
input:
control: Slider
min: 0
max: 10
labels:
0: "Not likely"
5: "Maybe"
10: "Very likely"
- id: q_reason
kind: Question
title: "What is the primary reason for your score?"
precondition:
- predicate: follow_up_needed == 1
input:
control: Dropdown
labels:
1: "Product quality"
2: "Customer service"
3: "Pricing"
4: "Other"
- id: q_aspects
kind: QuestionGroup
title: "Please rate these aspects:"
questions:
- "Product quality"
- "Customer service"
- "Value for money"
input:
control: Radio
labels:
1: "Poor"
2: "Fair"
3: "Good"
4: "Excellent"
- id: b_closing
title: "Thank You"
items:
- id: q_comments
kind: Comment
title: "Thank you for your valuable feedback!"
Next Steps¶
- Explore Data Analysis for analyzing survey results
- Read the Theory section for mathematical foundations
- Check the API Reference for programmatic access
Further Reading¶
For detailed technical specifications:
- Questionnaire Analysis - Mathematical foundations
- Preconditions & Postconditions - Conditional logic theory
- Complex Types - Advanced question types