Skip to content

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:

- id: q_intro
  kind: Comment
  title: "Please answer honestly. Your responses are confidential."

2. Question (Single Response)

Ask one question and collect one response:

- id: q_age
  kind: Question
  title: "What is your age?"
  input:
    control: Editbox
    min: 18
    max: 120

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:

input:
  control: Switch
  on: "Yes"
  off: "No"

Radio (Choose One)

Select one option from several:

input:
  control: Radio
  labels:
    1: "Option A"
    2: "Option B"
    3: "Option C"

Checkbox (Choose Multiple)

Select zero or more options:

input:
  control: Checkbox
  labels:
    1: "Feature A"
    2: "Feature B"
    4: "Feature C"
    8: "Feature D"

Checkbox Values

Use powers of 2 (1, 2, 4, 8, 16...) for checkbox options. Selected options are combined using bitwise OR.

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:

input:
  control: Editbox
  min: 0
  max: 1000000
  left: "Annual income: $"

Slider (Visual Scale)

Visual selection along a scale:

input:
  control: Slider
  min: 0
  max: 10
  step: 1
  labels:
    0: "Not at all"
    5: "Somewhat"
    10: "Extremely"

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.

input:
  control: Range
  min: 0
  max: 100
  step: 5
  labels:
    0: "Minimum"
    50: "Mid"
    100: "Maximum"

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:

\[ \text{pair}(a, b) = \begin{cases} a^2 + a + b & \text{if } a \geq b \\ a + b^2 & \text{if } a < b \end{cases} \]

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:

precondition:
  - predicate: q_age.outcome >= 25 and q_employment.outcome in [1, 2, 3]

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:

questionnaire:
  codeInit: |
    total_score = 0
    risk_level = 0

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/right text 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

Further Reading

For detailed technical specifications: