Start here
Self-paced professional course

Survey Solutions for SurveyCTO Power Users

A practical, operations-focused masterclass for experienced SurveyCTO programmers moving into Survey Solutions questionnaire design, fieldwork management, supervision, exports, quality assurance and integrations.

14 modules30–42 hours including practice projectsSelf-pacedMandatory knowledge checksC# syntax drillsCurrent to Sep 2026
Course author

Aubrey Jolex

Senior Research Associate Β· Innovations for Poverty Action
Estimated commitment

12–16 hours for guided reading and checks
10–14 hours for Designer and server practice
8–12 hours for the practice project and capstone

Typical pace: 5–8 weeks at 4–6 hours per week, or 5–6 intensive training days plus independent practice.

How to use this course

Build one questionnaire as you learn. Open the synthetic Practice PDF from the top bar and keep it beside the lesson. Short build-along prompts start in Module 4; by Module 14, you will be finishing and testing a questionnaire you have already built in stages. No private Designer account or shared questionnaire is needed.

Complete the modules in order on your first pass. Each module ends with a mandatory knowledge check. The next module unlocks when you answer every item correctly or use all three attempts. After the third unsuccessful attempt, the course reveals the correct answers and explanations, then allows you to continue.

This is a self-paced course. You can stop and return on the same browser; module access, quiz attempts and progress are stored locally on this device. After completing the course once, use the sidebar as a reference manual.

Training convention: free-response drills give exact variable names and answer codes. Attempt 1 provides a light hint, attempt 2 a stronger hint, and attempt 3 reveals a model answer with an explanation.
Your anchor: SurveyCTO is form-centric. Survey Solutions is more explicitly questionnaire + assignment + interview + supervision workflow-centric.
14sequenced modules
3 attemptsper mandatory checkpoint
30–42 hoursestimated total with practical work
You already know

XLSForm logic, repeats, relevance, constraints, preloads, field data pipelines.

You need to remap

Designer hierarchy, C# expressions, rosters, linked questions, assignments and interview states.

You will gain

A mental model strong enough to build, deploy, supervise, export and troubleshoot a real project.

Official documentation β†—
Module 1

What exactly is Survey Solutions?

You already know how to build surveys. This module is about understanding the product you are moving into: what it is, why organizations use it, which account you actually need, and what is different from simply signing up for SurveyCTO.

Survey Solutions in one sentence

Survey Solutions is a free, open-source platform developed by the World Bank for designing, implementing, managing, and monitoring large-scale surveys and censuses.

It supports interviewer-administered CAPI and web interviewing while integrating questionnaire programming, field assignments, supervision, interview review, paradata, exports, and APIs.

SurveyCTO translation: do not think of Survey Solutions as β€œanother form builder.” Think of it as a survey operations system in which questionnaire design, case assignment, interviewer responsibility, supervision, review, synchronization, and data export are designed to work together.

Why would a project choose Survey Solutions?

It is not universally β€œbetter” than SurveyCTO, ODK, KoboToolbox, or other platforms. Its strengths are especially relevant when a project has a formal field hierarchy, complex household or establishment instruments, large samples, strong supervision requirements, or institutional requirements around World Bank-supported workflows.

Built for complex survey operations

Assignments, supervisors, interviewers, approval/rejection workflows, sample identifiers and interview status are core objects rather than optional add-ons.

Strong offline CAPI model

Android interviewers can work offline and synchronize when connectivity is available, which suits fieldwork in low-connectivity settings.

Rich paradata and audit trail

The system records much more than final answers: interview actions, timing, synchronization and review history can support field monitoring and quality assurance.

Open-source and self-hostable

Organizations can run Survey Solutions on their own infrastructure or cloud environment, which can matter for data governance and institutional control.

Complex questionnaire logic

The visual Designer handles standard survey logic, while C# expressions, rosters, linked questions, calculated variables and lookup tables support advanced instruments.

Automation and integration

Survey Solutions servers expose REST and GraphQL APIs, allowing teams to connect assignments, exports, monitoring systems and external databases.

Where SurveyCTO may still feel easier to you

TaskSurveyCTO experienceSurvey Solutions experience
Questionnaire authoringXLSForm in Google Sheets is fast for power users and easy to bulk-edit.Designer is hierarchical and browser-based; there is no native XLSForm workflow.
Shared server-side reference dataServer datasets, publishing and pulldata() are highly flexible.Static lookup tables, assignments and APIs cover different parts of that job; there is no single 1:1 server-dataset equivalent.
Operational hierarchyFlexible user/group patterns can be lightweight.HQ β†’ Supervisor β†’ Interviewer is much more explicit and central to the system.
HostingUsually feels like a managed SaaS service to project teams.A project may use an organization-managed Survey Solutions server or its own hosted server, so server administration can be more visible.
For your World Bank-funded project: the likely reason is not simply β€œthe World Bank prefers its own software.” Survey Solutions is designed around the kind of large-scale, supervised household and establishment survey operations that World Bank projects often run. Your actual project may also have a specific institutional, data-governance, integration, or implementation requirement, so the project's stated reason remains authoritative.

Accounts: the first thing that confuses new users

Your Designer account and your project data-server account are not the same account. This distinction is fundamental.

1. Questionnaire Designer account

This is your personal account on the online Questionnaire Designer. You use it to create, collaborate on, test and manage questionnaire source documents.

Registration:

  1. Go to designer.mysurvey.solutions.
  2. Choose Register.
  3. Create a login, enter your full name and email, and set a password.
  4. Confirm the registration from the email sent to you.
  5. Sign in; your starting workspace is My Questionnaires.
2. Survey data-server account

This is an account created on a specific Survey Solutions server for actual survey operations. Its role may be Administrator, Headquarters, Supervisor, Interviewer, Observer or API User.

Important: creating a Designer account does not automatically give you access to your project's Headquarters server. The project's server administrator or HQ team must create or assign the appropriate server account.

Your recommended setup for this course

  1. Create a Designer account first. This is where you will program the questionnaire.
  2. Ask the incoming project for the Survey Solutions server URL and your intended role. If they already have a Survey Solutions server, use the server and access details provided by the project.
  3. For training, request a Personal Demo Server (PDS) through the Survey Solutions self-service/PDS portal using your Designer credentials. A PDS is a temporary practice server intended for learning, not real data collection.
  4. Install the Android Tester or Interviewer tools only when needed. You can learn a lot in Designer and the web interface before touching a field device.
PDS is for practice only. Personal Demo Servers are temporary learning environments and their contents are deleted after the demo period. Do not collect real project data there. A real project should use the Survey Solutions server and access arrangements approved by the project.

A simple picture of the identities involved

YOU
β”‚
β”œβ”€ Designer account
β”‚    └─ create / collaborate / test questionnaires
β”‚
└─ Project data-server account
     β”œβ”€ Administrator
     β”œβ”€ Headquarters
     β”œβ”€ Supervisor
     β”œβ”€ Interviewer
     β”œβ”€ Observer
     └─ API User

Designer account β‰  automatic access to project server

Before you continue: make sure you can answer these

Orientation check (guided, not graded):
  1. What is the difference between Designer and Headquarters?
  2. Does registering for Designer automatically create an HQ account on your project's server?
  3. Would you use a Personal Demo Server for real respondent data?
  4. Name two reasons a project might choose Survey Solutions other than β€œbecause the World Bank developed it.”
Show model answers

1. Designer is where questionnaire source documents are created; Headquarters is part of a data server used to deploy and manage real survey operations. 2. No. A server administrator or HQ team creates the relevant server account. 3. No; PDS is for learning and testing. 4. Examples: built-in supervision, offline CAPI, paradata/audit, self-hosting/data control, complex roster-based instruments, or API integration.

Official sources used for this orientation

The course now also includes an embedded component screenshot in the Architecture module so you can connect the written explanations to an actual Survey Solutions visual.

Current documentation checked September 2026. Open the sources directly:

Module 2

How your Designer questionnaire gets onto the project server

This is the Survey Solutions step that is closest to SurveyCTO's Upload form definition. The idea is familiar, but the transfer is initiated from Headquarters.

SurveyCTO
Google Sheet / XLSForm
        ↓
Upload form definition
        ↓
SurveyCTO server
        ↓
Collect / web form
Survey Solutions
Questionnaire in Designer
        ↓
HQ imports questionnaire
        ↓
Project data server
        ↓
Assignments
        ↓
Interviewer / Web Interviewer
The key analogy: SurveyCTO Upload form definition β‰ˆ Survey Solutions Import questionnaire. In Survey Solutions, the import is started from Headquarters, which connects to Designer and copies the questionnaire onto the project server.
Helpful visual cue: the course now includes a component diagram in the Architecture module showing how Designer, Tester, Web-Tester, Headquarters, Supervisor, and Interviewer relate to one another.

Set up your Personal Demo Server before the practical modules

A Personal Demo Server (PDS) gives you Administrator and Headquarters controls for a private practice environment. Use it for the course lab because the public demonstration server is mainly for observing existing content and cannot provide the same controlled setup.

Request the server
  1. Create and confirm your Designer account first.
  2. Open pds.mysurvey.solutions and sign in through the self-service portal.
  3. Submit a Personal Demo Server request. Each user may have one active PDS.
  4. Wait for the creation notice and server credentials. The normal address follows https://username-demo.mysurvey.solutions.
  5. Store the PDS address and administrator credential in your password manager. They are separate from your Designer credentials.
Know the limits
  • The server and all its data are automatically deleted after 30 days.
  • The period cannot be extended; request a new PDS after expiry.
  • There is no backup or recovery service.
  • Use only synthetic practice records. Never load respondent data.
  • The environment is sized for learning and small tests, approximately 1,000 interviews.
The 30-day clock starts when the PDS is created. Request it when you are ready to begin the practical sequence, then finish the server exercises and download your evidence before expiry.

Create the course practice team

Sign in to the PDS as its administrator and create these synthetic accounts. Choose unique temporary passwords and do not reuse a real work password.

Practice loginRolePurpose in the lab
hq_trainHeadquartersImport the questionnaire, upload assignments, review interviews and export data.
sup_trainSupervisorReceive the batch of assignments, allocate cases, review, reject and approve.
enum_01 β€” Mike JohnsonInterviewer under sup_trainReceive directly assigned cases, synchronize the Android app, conduct interviews and correct rejected work.
enum_02 β€” Abel KayembeInterviewer under sup_trainReceive directly assigned cases, run a connected Web Interviewer test and compare it with Android.
Current password rule: every new account password must be at least 10 characters long and contain at least one uppercase English letter, one lowercase English letter, and one digit. Passwords are case-sensitive. A memorable phrase that satisfies the rule is safer than a short predictable pattern; do not reuse a real work password. Current account and password rules β†—

Create users in the current PDS interface

  1. Open the administrator area, select Users, and choose ADD USER for one account or UPLOAD USERS for a tab-delimited batch.
  2. For a manual account, select the workspace and role, enter the login, enter the compliant password twice, then optionally add the full name, email and phone number.
  3. Create sup_train before creating or uploading interviewers whose supervisor column contains sup_train.
Personal Demo Server Users page with Add User and Upload Users buttons.
Choose the account workflow. ADD USER creates one account; UPLOAD USERS opens the batch-user workflow.
Create user account form showing workspace, role, username, new password and confirm password fields.
Required account fields. Workspace, role, username and the two password fields are required. The full-name field lower on the page is optional operational metadata.
Batch-user dependency check: create the supervisor first
Batch user upload verification failure stating that supervisor sup_train is not found in the workspace.
Why the batch failed. Interviewer rows name sup_train, but that supervisor did not yet exist in the selected workspace. Create the supervisor first or include a valid supervisor account in the batch as supported by the current template.
Batch user upload result stating that two interviewer accounts were successfully created.
Verification after correction. Do not assume an upload succeeded because the file was accepted. Read the result and confirm the expected number and roles of created accounts.
Account check: sign in once as each role before continuing. Confirm that the role sees the expected workspace and that both interviewer accounts belong to sup_train. Sign out between roles or use separate browser profiles to avoid confusing sessions.

The practice deployment cycle

Designer
Build the practice questionnaire, compile with zero errors, then test the main branches.
↓
PDS Headquarters
Import the Designer questionnaire as Version 1.
↓
Assignments
Upload the embedded tab-delimited household list. Its _responsible values route cases directly to enum_01 and enum_02.
↓
Supervisor
Monitor the two interviewers, review submitted cases, reject one practice case for correction, and approve the corrected work.
↓
Interviewers
Synchronize or open the web dashboard, execute test scenarios, complete and submit.
↓
Review and evidence
Reject, correct, resubmit, approve, export and inspect paradata before the PDS expires.

Personal Demo Server guidance β†—

First version: step by step

  1. Build and test the questionnaire in Designer and resolve questionnaire errors.
  2. Make sure the Designer account that will be used for import has access to the questionnaire. The owner can share it with another Designer user.
  3. Sign in to the project's Survey Solutions server as Headquarters or Administrator.
  4. Go to Survey Setup β†’ Questionnaires β†’ Import questionnaire.
  5. Headquarters asks you to sign in to Designer. These are Designer credentials, not the project-server credentials.
  6. HQ lists the questionnaires that Designer account can access.
  7. Select the questionnaire and confirm the import.
  8. The questionnaire appears on the project server as Version 1.
  9. Create assignments from that imported questionnaire and assign them to the appropriate field team.
Real PDS import sequence
PDS navigation with Survey Setup opened and Questionnaires available in the menu.
1. Enter Survey Setup. Open Survey Setup in the black navigation bar and choose Questionnaires.
Empty PDS Questionnaires page with a green Import Questionnaire button.
2. Start the import. An empty server shows no questionnaire rows yet; select IMPORT QUESTIONNAIRE.
PDS page listing questionnaires accessible through the signed-in Designer account.
3. Choose the Designer questionnaire. HQ lists the questionnaires accessible to the authenticated Designer account.
PDS import confirmation panel with comment box and Import button.
4. Confirm the copy into HQ. Review the questionnaire summary, optionally add a comment, and select IMPORT.
PDS Questionnaires page showing Household Roster and Education Practice as imported version 1 in CAPI mode.
5. Verify the result. The questionnaire row now shows Version 1 and CAPI mode. Importing succeeds only when this server-side row appears.
Designer and the project server stay separate. Editing the questionnaire in Designer does not automatically change the copy already being used on the project server.

So how are the two accounts linked?

They are connected at the moment of import rather than through one permanent shared login:

Signed in to project server as HQ
        ↓
HQ asks for Designer login
        ↓
Designer checks questionnaires you can access
        ↓
You select questionnaire
        ↓
HQ copies it onto the project server

This is why the same person may have a Designer login and a different project-server login.

What happens when you update the questionnaire?

This is the closest equivalent of uploading a revised XLSForm in SurveyCTO.

SurveyCTO
  1. Edit the XLSForm.
  2. Upload the revised form definition.
  3. The server receives the new form version.
Survey Solutions
  1. Edit the questionnaire in Designer.
  2. Test it and resolve errors.
  3. In HQ, import the same questionnaire again.
  4. The project server stores it as the next questionnaire version.
  5. Decide whether eligible unused assignments should move to that newer version.

Versioning

Household Survey
Version 1   imported Monday
Version 2   imported Wednesday
Version 3   imported Friday

Multiple versions can exist on the same project server. New assignments can be created from a selected imported version, although normally you would use the latest tested version.

Questionnaire changes are not automatically pushed into interviews already underway. Importing Version 2 does not convert interviews that were started, completed, rejected, or approved under Version 1.

What about assignments that have not started?

When a newer questionnaire version is imported, Survey Solutions can offer to upgrade eligible assignments. If an assignment can be moved, the old assignment is archived and the remaining workload is recreated using the newer questionnaire version. Started interviews are not converted.

SurveyCTO comparison

TaskSurveyCTOSurvey Solutions
Create/edit questionnaireUsually Google Sheets / XLSFormQuestionnaire Designer
Put first version on serverUpload form definitionHQ imports questionnaire from Designer
Update questionnaireEdit XLSForm and upload againEdit in Designer and import again
Does editing source automatically update server?NoNo
Questionnaire versions on serverForm versionsImported questionnaire versions
Move unused work to newer versionHandled through your form/case workflowUpgrade eligible assignments

Who can import?

The import is performed from Headquarters. The Designer login used during import must have access to the questionnaire. A collaborator with either View or Edit access in Designer can import that questionnaire to Headquarters.

Common reasons an import fails

  • The questionnaire still contains errors in Designer.
  • The Designer account used during import does not have access to the questionnaire.
  • The project server cannot reach designer.mysurvey.solutions.
  • The questionnaire uses a feature that the version of Survey Solutions on the project server does not yet support.
  • The Designer and project-server software versions are incompatible.
Workflow check (guided, not graded):

You change a validation rule in Designer on Wednesday. Enumerators are already working with Version 1, while 40 assignments have not yet started. What happens?

Show model answer

Test the change in Designer, then import the questionnaire again from HQ to create the next version. Interviews already started with Version 1 remain on Version 1. Eligible assignments that have not started can be moved to the newer version using the assignment-upgrade process. Survey Solutions recommends minimizing questionnaire changes once fieldwork has begun.

Module 3

Architecture: stop thinking β€œform upload”

The first job is to replace your SurveyCTO mental model with the Survey Solutions operational model.

Big-picture component flow

Survey Solutions component diagram showing Designer, Tester, Web-Tester, Headquarters, Supervisor, and Interviewer connections.
This visual helps tie together the main Survey Solutions components: Designer for questionnaire authoring, Tester/Web-Tester for testing, and the fieldwork chain of Headquarters β†’ Supervisor β†’ Interviewer.

Documentation gallery: what the system looks like

The official documentation site blocks some images from being displayed inside a separate local HTML file. To make the course reliable offline, the gallery below uses embedded visual guides recreated from the official documentation workflows. They are not pixel-for-pixel screenshots, but they show the same screens, controls, and steps without depending on external images.

SurveyCTO mental model
Google Sheet / XLSForm
        ↓
SurveyCTO server
        ↓
Collect
        ↓
submission
Survey Solutions mental model
Questionnaire Designer
        ↓
Headquarters
        ↓
Assignment
        ↓
Interviewer
        ↓
Interview
        ↓
Review / approval
        ↓
Export
Key shift: an interview is not just a submitted row. It has responsibility, status, history, comments, review and synchronization state.

Core actors

Survey Solutions componentThink of it asWhat matters
DesignerYour XLSForm authoring environmentQuestionnaire structure + C# logic
Headquarters (HQ)Server + operations consoleQuestionnaires, assignments, monitoring, review, exports
SupervisorBuilt-in field manager tierTeam workload, review, reassignment
InterviewerCollect userReceives work, conducts interviews, syncs
Checkpoint (guided, not graded): Scenario: interviewer int_07 says β€œmy blank form is missing.” Known objects: questionnaire HH_Survey_v3, assignment A-041, and user int_07. Reframe the problem by checking questionnaire version, assignment responsibility, and synchronization.
Module 4

Designer: create, edit, test, and collaborate

Build your own questionnaire first, then learn to collaborate. Work through parts 4A–4F with Designer open in a second tab. Modules 5–8 extend these skills with expressions, calculations, rosters, and linked questions.

Module 4 learning path
  1. 4A. Create your questionnaire
  2. 4B. Edit questions and organize sections
  3. 4C. Categories and questionnaire tools
  4. 4D. Compile, test, and revise
  5. 4E. Share with colleagues
  6. 4F. Receive a shared questionnaire

Allow approximately 2–3 hours including hands-on practice, within the course's overall practice allocation.

4A. Create your first questionnaire

Sign in to Designer. On its home page choose Create new, name the instrument Household practice β€” your name, and select Create. Reopen it later by selecting its title in your questionnaire list. Designer home page β†—

Open Settings, enter a questionnaire variable such as hh_practice, and Save. This variable names the main exported data file; it is different from the visible questionnaire title and from each question's variable name. Questionnaire settings β†—

Plan your sections before adding questions. The Cover holds deliberately chosen identifying information; interview content belongs in sections such as Consent and Household. Add these sections using the section controls. The tree below illustrates how the instrument will grow; you will learn roster construction in Module 7.

Questionnaire
β”œβ”€ Cover
β”‚  β”œβ”€ enumeration_area
β”‚  └─ household_id
β”œβ”€ Section: Consent
β”œβ”€ Section: Household
β”‚  β”œβ”€ hhsize
β”‚  └─ Roster: members
β”‚     β”œβ”€ name
β”‚     β”œβ”€ age
β”‚     └─ sex
└─ Section: Remittances
   └─ ...
SurveyCTO / XLSFormSurvey Solutions
begin groupSection / sub-section
begin repeatRoster
calculateCalculated variable
noteStatic text
relevanceEnabling condition
constraintValidation condition
required=yesCritical questions or rules for submission requirements; see Module 6 for the difference from navigation blocking
Power-user trap: there is no native β€œedit 300 rows in Google Sheets and re-upload XLSForm” workflow. Treat the Designer tree itself as the source instrument.

4B. Edit questions and organize sections

The tree selects what you work on; the right editor changes that element; the narrow left rail opens questionnaire-wide tools. Start by building a small instrument you understand.

  1. Select Consent and choose Add question. Create a categorical single-select named consent with text β€œMay we begin this practice interview?” and question-specific options 1 = Yes and 2 = No. Save.
  2. Select Household. Add a Text question named respondent_name, then an Integer numeric question named respondent_age. Give each clear question text and Save.
  3. Select a question in the tree to reopen it. Question text is the interview wording; variable name identifies it in expressions and exports; variable label provides descriptive metadata. Change wording, then Save. Cancel discards an unsaved edit.
  4. Add interviewer instructions for procedural guidance and Static text for information that needs no answer. Calculated variables are introduced in Module 6.
  5. Drag to reorder within a section. To move to another section, select the element and use Move to. Review its new context, especially if moving into or out of a roster. Use Search for question to locate questions in a large instrument.
  6. Before deleting or renaming an element, inspect references to it. After copying an element, review its name, categories and logic. Compile after structural changes.
Survey Solutions questionnaire tree while a calculated head_name element is being dragged among questions in a household roster.
Drag-and-drop is useful for local organization. Drag an element by its handle to change its order or place it in an accepted visible container. Watch the indentation before dropping: an item indented under a roster becomes roster-level and is repeated.
Survey Solutions Designer context menu opened on a roster with commands to add a question, subsection, roster, static text, or variable, and to copy or delete.
Right-click a group for its context menu. A section, sub-section, or roster can offer commands such as Add question, Add sub-section, Add roster, Add static text, and Add variable, plus Copy, Paste after, and Delete. The exact enabled commands depend on the selected container and nesting rules.
Reliable movement rule: use drag-and-drop for a clearly visible, accepted destination and for reordering within the current section. For a move to another sectionβ€”or whenever the drop target is ambiguousβ€”select the element and use MOVE TO at the bottom-right. After any move, verify indentation, variable scope, enabling conditions, and substitutions, then compile. Questionnaire elements and operations β†—

Keep this first build simple. Modules 5–6 teach enabling and validation expressions; at this stage you are mastering the editor. Elements and ordering β†—

Variable name, variable label, and question text

These three properties describe the same question for different purposes. The name identifies its data; the label describes that data; the question text supplies the interview wording.

PropertyPurposeRequired or optional?Example
Variable nameIdentifier used by expressions and exports.Required for a question; unique and subject to naming rules.member_age
Variable labelShort description associated with the exported variable in statistical software.Optional for ordinary questions; required for every Cover question. If omitted, question text supplies the export label.Age in completed years
Question textWording presented during the interview, including supported substitutions.Supply nonempty text for questions; Designer reports an empty title as WB0269. A variable label does not replace it.How old is %member_name% in completed years?

Ordinary variable labels are not shown on the tablet; identifying-question labels on the Cover are an exception. Question properties β†—

SurveyCTO reference: the XLSForm name corresponds to the variable name, while the respondent-facing label corresponds to Question text. Do not map SurveyCTO's label to Designer's Variable label merely because both use the word β€œlabel.”

When should they be similarβ€”and when should they differ?

Use caseVariable nameVariable labelQuestion text
Short identification field on the Coverhousehold_idHousehold IDHousehold ID
Roster question with a changing member namemember_ageAge in completed yearsHow old is %member_name% in completed years?
Amount with a defined reference periodfood_spend_7dFood expenditure, past 7 days (PHP)During the past seven days, how much did your household spend on food, in pesos?

Identical short wording can work for identifiers. For longer questions, write a concise analytical label that preserves units, population and reference period, while keeping natural interview wording in Question text. Put procedural guidance in Interviewer instruction, rather than burying it in an export label. In multilingual instruments, review translated question wording separately from the team's analytical naming convention.

Substitutions belong in question text, not variable labels. For example, use %member_name% in the age question, but keep the label β€œAge in completed years.” Designer disallows substitutions in variable labels (WB0008); missing Cover labels produce WB0309. These authoring requirements do not make an interview answer compulsory: critical-question/rule settings and HQ treatment govern submission requirements. Designer messages β†—

Published limits are 80 characters for a variable label and 2,000 for question text. Keep both concise and check the exported statistical dataset rather than assuming a CSV header carries a variable label. Length limits β†—

Try it: Give respondent_age the label β€œRespondent age (completed years)” and full interview wording as Question text. Compile and Test. Inspect the label after a practice export. Then inspect household_id on the Cover and confirm it has a label. Changing a label or question wording does not rename the variable used by expressions.

Variable names

Questions, calculated variables and rosters use identifiers that are referenced directly in expressions and appear in exports. Build a naming convention early, just as you would in XLSForm.

hh_size
hh_member_age
remit_received
sender_country

Formatting question and static text

Survey Solutions allows a limited set of HTML tags inside question text and static text. This is the closest equivalent to using formatting markup in SurveyCTO labels. It is not arbitrary CSS: unsupported tags are removed by Designer. Formatting text β†—

You wantSurvey Solutions markupExample
Bold / strong emphasis<strong>...</strong><strong>Important</strong>
Italics<i>...</i> or <em>...</em><i>Read aloud</i>
Underline<u>...</u><u>Do not skip</u>
Line break<br>Line 1<br>Line 2
Blue text<font color="blue">...</font><font color="blue">Enumerator note</font>
Custom color<font color="#2E4085">...</font>Useful when you want an exact brand color.
Larger / smaller text<big>, <small>, or <font size="...">Test carefully across tablet and web.
Subscript / superscript<sub> / <sup>m<sup>2</sup>
Two limitations worth remembering: formatting is supported only through the tags Survey Solutions allows, and font-size rendering can differ between tablet and web interviewing. Also, if a substituted string itself contains HTML, that HTML is shown as text rather than executed; formatting should be placed around the substitution in the questionnaire text. Formatting text β†—
Example with text substitution
Transfer recipient:
<strong><font color="#2E4085">%recipient_name%</font></strong>

<i>Please confirm the name before continuing.</i>

The value of %recipient_name% is piped into the formatted text. Survey Solutions uses the %variable% syntax for text substitution. Text substitution β†—

4C. Categories and questionnaire-wide tools

Reusable categories are named sets of numeric codes and labels stored at questionnaire level. The same set can serve more than one categorical question. This practice reference uses a synthetic education example; you create it yourself in Designer.

Build-along: shared grade choices

Create grade_codes in your own practice questionnaire

Open Reusable categories from Designer's left rail, then choose ADD NEW CATEGORIES (or the equivalent upload command in your version). The PDF lists the complete synthetic code set. For a first test, enter the rows below, save, and bind a categorical question's Source of categories to Reusable categories β†’ grade_codes.

valuetitleUse
10KindergartenCurrent and highest grade
11Grade 1Current and highest grade
12Grade 2Current and highest grade
0No grade completedHighest grade only
60Post-graduateHighest grade only
99Don't knowCurrent and highest grade

These are examples, not an installed list. Complete the remaining grade codes from the practice questionnaire's education-code table before the capstone. In a numeric category, write 0, not a text code 00; record that display label separately.

Where the set lives and what a change affects

  1. In your practice questionnaire, open Reusable categories in the far-left panel of advanced instruments.
  2. Create grade_codes manually or download Designer's XLSX/TAB template and upload a tab-delimited list with value and title headers.
  3. Bind current_grade and highest_grade to that set. Filter out codes that are valid only for highest completed grade from the current-enrollment question.
  4. Save, compile, and test both bound questions. If changing an existing set, download a backup first: an upload replaces its categories.
Shared codes have a wide reach. A label change affects every bound question; a numeric-code change may also affect expressions and exports. Search dependencies and test all bound questions before publishing a revision. Reusable categories guide β†—

The SurveyCTO connection: choice lists versus preloaded data

The closest everyday analogy is a named list in SurveyCTO's choices sheet: several select questions can use the same list. Uploading a category file is also useful for the long lists that SurveyCTO programmers often maintain externally.

SurveyCTO can load choices during an interview from an attached CSV or dataset using search(). A Designer category upload instead incorporates a static list into the questionnaire. It does not establish a live connection to the original spreadsheet or reproduce SurveyCTO's dataset filtering automatically. SurveyCTO preloaded choice lists β†—

Separate the three intentions: offering a list of regions calls for categories; retrieving attributes with SurveyCTO pulldata() calls for a separate data/logic design; supplying a particular household's existing answers calls for assignment preloading (Module 9). Uploading reusable categories does not prefill answers or create assignments.

When adapting a SurveyCTO list, map its choice-value column to value and label column to title. Designer requires numeric category codes: preserve a documented crosswalk for string or leading-zero identifiers rather than silently converting them. Rebuild and test dependent filtering explicitly. Subsequent category changes require the intended updated questionnaire version to reach HQ; editing the source file alone does not update deployed interviews.

Do not confuse four category and reference mechanisms

MechanismWhere it livesUse it forEditing consequence
Question-specific categoriesInside one categorical questionA short list used only by that questionThe edit affects that question.
Reusable categoriesReusable Categories tool in this questionnaireOne coded answer list shared by several single- or multi-select questions; optionally cascadingThe edit affects every question bound to that set.
Classification libraryDesigner library used to copy standardized classificationsFinding and inserting a maintained classification into a questionnaireThe inserted classification is a copy; later edits in the questionnaire do not update the library.
Lookup tableLookup Tables tool in this questionnaireStatic numeric reference data used in expressionsIt supplies values to logic; it is not the displayed answer list for a categorical question.

The complete left sidebar: Panel of Advanced Instruments

From top to bottom, after the Designer logo, the rail in the screenshot opens these ten tools. Icon artwork can change between releases, so learn the tool name and purpose as well as its position. Panel of Advanced Instruments β†—

Order / visual cueToolWhat it opensTypical trainee task
1 Β· three horizontal linesTable of contentsThe questionnaire tree and navigation through sections and elements.Jump to a section, roster, question, static text, or variable.
2 Β· information circleQuestionnaire description and survey informationQuestionnaire metadata and descriptive information.Understand the instrument's purpose and documentation before editing.
3 Β· language charactersTranslationsLanguages and translated questionnaire text.Add or upload translations and check whether category labels are complete in every language.
4 Β· stacked category cardsReusable categoriesQuestionnaire-level coded category sets such as the synthetic grade_codes.Edit a shared list once, then test all bound categorical questions.
5 Β· branching pathScenariosSaved testing scenarios for questionnaire paths.Re-run a known interview path after logic changes.
6 Β· $mMacrosNamed reusable fragments used in expressions.Inspect shared logic before changing a macro that may affect many expressions.
7 Β· book/tableLookup tablesStatic numeric reference tables available to expressions.Maintain numeric parameters or mappings used by calculations and validation.
8 Β· paperclipAttachmentsFiles attached to the questionnaire.Manage images, documents, or category attachments referenced by the instrument.
9 Β· speech bubblesCommentsDesigner collaboration comments.Review open discussions and document a proposed or completed edit.
10 Β· warning triangleCritical rulesQuestionnaire-level conditions checked at submission.Understand why an interview cannot be submitted and preserve submission rules during edits.
Top-bar controls are separate from the left rail. Settings includes questionnaire properties and Access; History shows revisions; Test opens the questionnaire for testing; and Compile checks the questionnaire. Use Compile before and after a shared edit. Questionnaire edit screen β†—

4D. Compile, test, and revise

Use the cycle edit β†’ Save β†’ Compile β†’ Test β†’ revise. Saving preserves an edit; compilation checks the questionnaire structure and expressions; testing checks the interview experience. A successful compile does not establish that the survey logic is correct.

  1. Compile the instrument. Open reported issues, fix their causes, save and compile again. Review warnings too.
  2. Choose Test and enter synthetic answers. Check wording, options, ordering, changing an answer, and returning to a question. Consent does not yet skip Household: you will add that logic in Modules 5–6.
  3. Record expected and observed results. Later, repeat tests for both consent outcomes, missing values and invalid ages after adding logic.
  4. Inspect your own revisions in History. Extract a revision into an independent copy for experiments; reverting changes the working document.
Authoring milestone: Reopen your instrument, locate and edit a question, explain its variable name and category source, compile, and run a synthetic interview. Save its Designer URL for later modules. No Headquarters assignment is needed to test in Designer.

4E. Share your questionnaire with colleagues

Once your draft is ready for review, open Settings β†’ Access. Enter your colleague’s exact registered Designer email or login, choose View or Edit, and select Invite. They must already have a Designer account. View permits inspection; Edit permits changes and Designer comments.

Sharing gives access to the same document; copying creates an independent instrument. Agree who edits which sections and use History to follow changes. An editor can share further. To change View to Edit, revoke access and invite again with Edit. Ownership transfer is separate from sharing. Sharing controls β†—

Collaboration practice: With a training partner, share your draft for one agreed review task and inspect the resulting change. If studying alone, locate Access and explain which mode you would choose; no invitation is required.

4F. When someone shares their questionnaire with you

Others can also share their instruments with you. Open the questionnaire from your Designer list or their link. The editor, categories and testing tools work as described above. What changes is your permission, responsibility to the owner, and need to understand an existing instrument.

How it was sharedWhat you can doWhat to do when you need to edit
Edit accessAdd, remove, and revise questionnaire elements and logic; work with Designer comments; test and compile.Edit only after checking History and the team's version protocol. Your changes affect the shared questionnaire.
View accessInspect, test, preview, view history, and make your own copy, but not modify the shared questionnaire or use its comments.Ask the owner to revoke View access and share again with Edit access, or create a copy when the goal is practice.
Anonymous linkRead-only viewing and testing. An authenticated Designer user can create a personal copy.Sign in and copy it, or ask the owner to share directly with your Designer account.
A safe first pass through a shared questionnaire
  1. Confirm access and ownership. Open Settings β†’ Access. If Access is unavailable, confirm your role with the owner. An inactive Save button alone does not prove read-only access: there may be no unsaved edits or required properties may be incomplete.
  2. Confirm the purpose. Determine whether this is a practice copy, the team's working questionnaire, or a version already imported into Headquarters.
  3. Read before editing. Open History to see who changed what and when. Use an earlier revision for comparison; use Extract when you need a separate copy from that point.
  4. Compile the starting state. Record existing errors and warnings so that you do not attribute old problems to your edit.
  5. Trace dependencies. Search the variable name and inspect enabling conditions, validations, substitutions, rosters, filters, macros, reusable categories, and critical rules that may depend on it.
  6. Make one coherent change. Save it, compile again, and test the affected route. For category changes, test every question bound to the same list.
  7. Leave a usable trail. Add a Designer comment or follow the project change log. Editing Designer does not update Headquarters automatically; HQ must import the intended questionnaire version.
For practice, copy first. When you are learning an unfamiliar instrument, create your own copy instead of experimenting in a shared working questionnaire. The owner can also transfer ownership, but only the owner can delete the original questionnaire. Sharing questionnaires β†—
Apply it to the practice questionnaire. Create the Household Roster and Education Practice shell in your own Designer account. Add caseid to Cover, then create the synthetic grade_codes reusable set from the Practice PDF. Save and compile before moving on.
Module 5

Reading and writing Survey Solutions expressions

This module builds the expression language from the ground up. By the end, you should be able to identify what an expression must return, recognize the type of each value, read a roster lambda such as person => person.age >= 18, and diagnose the most common logic errors.

First mental shift: SurveyCTO expressions are largely XPath-style expressions over ${references}. Survey Solutions expressions use C# syntax and refer to questionnaire variable names directly. You are not expected to learn all of C#; you need the small, survey-specific subset taught here.

1. Start with the job of the expression

Before writing syntax, ask what Designer expects the expression to produce. Most logic errors begin when an expression returns the wrong kind of result.

Where you are writingQuestion to askRequired resultExample
Enabling conditionShould this element be active?Boolean: true or falseconsent == 1
Validation conditionIs this answer acceptable?Booleanself >= 0 && self <= 120
Option filterShould this candidate option be available?Boolean@optioncode != 99
Critical ruleIs the submission requirement satisfied?Booleanconsent == 2 || IsAnswered(end_time)
Calculated variableWhat value should be derived?The variable's selected typemembers.Count(person => person.age >= 18)

An enabling condition cannot return a person's name or a count. A String calculated variable cannot return true. Designer's compiler uses types to catch many such mismatches.

2. What @optioncode means in an option filter

@optioncode is a special Survey Solutions context value available while Designer evaluates an option filter for an ordinary categorical question. It means: the numeric code of the candidate category being considered right now. It is not a questionnaire variable that you create, and it is not the respondent's answer.

Think of the filter as a question asked once for every category: β€œShould the option with this code be shown?” The filter must return true to keep that candidate or false to hide it.
Candidate categoryValue of @optioncodeIf the filter is list_fruits.Contains(@optioncode)
Mango1Show Mango only when code 1 was selected.
Oranges2Show Oranges only when code 2 was selected.
Pineapples3Show Pineapples only when code 3 was selected.
Kiwi4Show Kiwi only when code 4 was selected.

If list_fruits contains codes 1 and 4, the filter is true while Designer tests Mango and Kiwi and false while it tests Oranges and Pineapples. The later question therefore displays only Mango and Kiwi.

Code, label and position are different

ConceptFruit exampleWhat it means
Category code4The stored numeric value used by logic and exports. This is what @optioncode supplies.
Category title or labelKiwiThe text shown to the interviewer. Translating or editing this title does not change @optioncode.
Display positionFourth optionWhere the option appears. Moving it does not turn its code into 4; only the configured value determines the code.

Common patterns

Exclude a special code
@optioncode != 99

Every category except code 99 remains available.

Keep options selected earlier
list_fruits != null &&
list_fruits.Contains(@optioncode)

The null guard handles the time before the earlier multi-select has an answer.

Do not confuse the context symbols

SymbolMeaningTypical location
@optioncodeThe candidate category code currently being tested.Option filter for an ordinary user-defined or reusable categorical list.
selfThe answer currently being validated.The question's validation condition.
@rowcodeThe identity/code of the current roster row.Logic evaluated inside a roster row.
A variable such as list_fruitsAn actual answer stored in the interview.Expressions wherever scope permits the reference.
  • Write @optioncode exactly as shown. Do not put it in quotes and do not use SurveyCTO's ${...} notation.
  • The option filter controls which categories are offered; it does not enable or disable the whole question. Add a separate enabling condition when needed.
  • When one question filters another, their codes must carry the same meaning. Reusable categories are the safest way to keep the codes aligned.
  • A linked categorical question built from a List question or roster has a different candidate context. Do not assume every linked-source filter can use an ordinary @optioncode recipe.
  • After changing an earlier answer, test what happens to a choice already selected in the filtered question on both Web Tester and Interviewer.
SurveyCTO connection: both SurveyCTO choice_filter and a Survey Solutions option filter evaluate candidate choices one at a time. SurveyCTO can filter using columns stored on each row of the choices sheet. In an ordinary Survey Solutions categorical filter, @optioncode exposes the candidate's numeric code; arbitrary XLSForm choice-row columns do not automatically exist and may require reusable-category structure, encoded codes, or a redesigned rule.

The official syntax guide calls @optioncode a substitution variable for non-linked categorical questions. Filtered answer options β†—

3. Variable names replace ${...}

SurveyCTO
${resp_age} >= 18
${sex} = 2
selected(${assets}, '3')
Survey Solutions
resp_age >= 18
sex == 2
assets.Contains(3)
  • resp_age, sex and assets are variable names assigned in Designer.
  • Do not add ${...}. Braces belong to the XLSForm/XPath convention.
  • Answer codes such as 2 and 3 are values, not row positions and not labels.
  • Text literals use double quotes, for example district_name == "Pavia".

4. Know what kind of value you are handling

A method or operator only makes sense for particular types. You cannot use .Contains(3) on a numeric age, add two text labels as though they were numbers, or compare a multi-select array directly with one code.

Survey elementThink of its value asCommon operationsExample
Numeric questionA nullable whole number or decimalComparison, arithmetic, HasValueage.HasValue && age >= 18
Single-select questionOne nullable integer answer code==, !=, InList(...)status.InList(1, 2, 4)
Ordinary multi-selectAn array of selected integer codesContains, Lengthassets.Contains(3)
Text questionA stringLength, StartsWith, Containscaseid.StartsWith("HH-")
Date questionA nullable date/time valueDate comparison and date propertiesvisit_date.HasValue
RosterA collection of rowsAny, All, Count, Wheremembers.Any(person => person.age < 5)

The exact underlying types vary by question kind. In the official type tables, a question mark such as int? means the value may be unanswered. Survey Solutions data types β†—

5. Operators and grouping

MeaningSurvey Solutions syntaxExample
Equals / does not equal== / !=sex == 2
Greater/less than>, >=, <, <=age >= 18
AND&&age >= 18 && sex == 2
OR||status == 1 || status == 2
NOT!!assets.Contains(99)
Add/subtract/multiply/divide+, -, *, /amount + fee
Choose between two valuescondition ? A : Bage >= 18 ? "Adult" : "Minor"
Use parentheses when AND and OR are mixed. Write consent == 1 && (region == 2 || region == 4). Without the parentheses, the expression may compile but implement a different survey rule.

6. Missing is a state, not zero or empty text

Numeric, date and categorical questions can be unanswered. Survey Solutions documents them as nullable types. Decide what an unanswered value should mean in the particular rule instead of assuming it is zero.

Ask whether an answer exists
IsAnswered(resp_age)

resp_age.HasValue

IsAnswered(...) is often the clearest survey-language check. HasValue is useful for nullable values in calculations.

Require an answer before comparing
IsAnswered(resp_age) &&
resp_age >= 18

This states the intended rule explicitly: an unanswered age is not an eligible adult.

Do not translate SurveyCTO's ${income} != '' mechanically. For a numeric Survey Solutions question, use IsAnswered(income) or income.HasValue. A validation is evaluated only when the question has an answer, so IsAnswered(self) does not make an unanswered question mandatory. Use critical questions/rules or downstream enabling when requiredness matters.

7. self means the answer being validated

Use self inside a question's validation condition. If the age question must be between 0 and 120, write self >= 0 && self <= 120. This keeps the validation portable if the question is renamed or copied. self is not used to enable the same question because an element cannot decide whether to turn itself on from an answer it cannot yet receive.

8. Scope: one row versus the whole roster

Inside the current members roster row, a bare reference such as age means the age in that current row. Outside the roster, there are many ages, so you must say how the whole collection should be evaluated.

Inside one member row
age >= 18

Read: β€œIs this member an adult?”

Outside the roster
members.Any(person =>
    person.age >= 18)

Read: β€œDoes at least one member have age 18 or above?”

9. What exactly is person?

person is a temporary row name chosen by the questionnaire author. It represents one row while Survey Solutions checks the rows in members. It is not a question, roster, reserved keyword, respondent, interviewer account, or server user.

Break the expression into five pieces:

PieceMeaning in members.Count(person => person.age >= 18)
membersThe roster collection to inspect.
CountThe action: return how many rows satisfy the test.
personA temporary name for the row currently being tested.
=>Read as β€œsuch that” or β€œfor each row, test whether.”
person.age >= 18The Boolean test applied to each row.

These are identical because the temporary name can be chosen freely:

members.Count(x => x.age >= 18)
members.Count(member => member.age >= 18)
members.Count(person => person.age >= 18)

You must use the chosen name consistently. members.Count(person => x.age >= 18) fails because the expression declares person but then refers to an undeclared x.

Advance preview of rosters: for the next examples, assume members is an existing household roster with one row per person and questions named name, age, sex and relationship. Here you are learning how to read a collection expression. Module 6 teaches the functions in depth; Module 7 shows how the roster and its rows are actually created.

10. Choose the collection operation from the question you are asking

Survey questionOperationResult typeExample
Does at least one row qualify?AnyBooleanmembers.Any(p => p.age < 5)
Does every row qualify?AllBooleanmembers.All(p => IsAnswered(p.age))
How many rows qualify?CountLong Integermembers.Count(p => p.sex == 2)
Keep only qualifying rowsWhereA filtered collectionmembers.Where(p => p.age >= 18)
Take one field from each rowSelectA value collectionmembers.Select(p => p.name)

Official LINQ and lambda guide β†—

11. Translate in small steps

Rule: enable the remittance module if the respondent is at least 18 and selected Bank transfer.

  1. Identify types: resp_age is numeric; remit_channels is multi-select; Bank transfer code is 1.
  2. Translate each test: resp_age >= 18; remit_channels.Contains(1).
  3. Join both required tests with &&.
  4. Confirm the final expression returns Boolean.
resp_age >= 18 && remit_channels.Contains(1)

Syntax drill

3 attempts Β· answer reveals after attempt 3

Task: Write the Survey Solutions enabling-condition expression for a respondent who is at least 18 years old and selected Bank transfer.

Use exactly these variables/codes:
MeaningVariable / code
Respondent ageresp_age
Channels selected (multi-select)remit_channels
Bank transfer answer code1
Attempts remaining: 3

Expression troubleshooting checklist

  1. Location: must the expression return Boolean, text, a number, or a date?
  2. Name: does every reference exactly match a Designer variable name?
  3. Type: is the value scalar, nullable, multi-select, text, or a roster collection?
  4. Scope: are you in one roster row or querying the full roster?
  5. Missingness: what should happen while a dependency is unanswered?
  6. Grouping: do parentheses preserve the intended AND/OR logic?
  7. Behavior: after it compiles, did you test true, false, unanswered, and changed-answer cases?

Compilation proves that Designer can interpret the expression. It does not prove that the expression implements the intended survey rule. Syntax overview β†—

Apply it to the practice questionnaire. Add the consent enablement rule consent_q_01 == 1 to the main section. Add one validation from the practice reference, compile, and test a Yes and No consent path.
Module 6

Calculated variables and reusable questionnaire logic

Module 5 taught how to read an expression. This module shows when to save an expression as a calculated variable, how to choose its type, how it reevaluates, and how to connect the result to questionnaire flow and quality checks.

SurveyCTO β†’ Survey Solutions: a SurveyCTO calculate field maps most closely to a Survey Solutions calculated variable. In Survey Solutions, you separately define the variable name, output type, expression, label, and export behavior.

1. Decide whether you need a calculated variable

Keep a condition inline when
  • it is short;
  • it is used once;
  • its meaning is immediately clear.
consent == 1
Create a variable when
  • the result is reused;
  • it needs a meaningful name;
  • it will be exported or piped into text;
  • the underlying expression is complex.
is_eligible

For example, instead of repeating members.Any(person => person.age < 18) in four sections, create Boolean variable has_child and reference that name.

2. Build the variable in five deliberate steps

  1. State the rule in ordinary language. Example: β€œCount household members aged 18 or older.”
  2. Choose the output type. A count is a Long Integer.
  3. Choose a meaningful name. Example: adult_count.
  4. Write only the expression. Do not put an XLSForm field declaration or assignment statement in the expression box.
  5. Test dependency changes. Add an adult, change the adult to age 17, remove the row, save and reopen.
SurveyCTO
type: calculate
name: adult_count
calculation:
count-if(...)
Survey Solutions
Type: Long Integer
Name: adult_count
Expression:
members.Count(person =>
    person.age >= 18)

3. Choose the output type from the meaning

TypeUse it forExample expression
BooleanEligibility, flags, yes/no logical resultsresp_age >= 18 && recent_remit == 1
Long IntegerCounts, whole-number scores, completed agemembers.Count(person => person.age >= 18)
DoubleAmounts, rates, means, measurements with decimalsamount_sent + transfer_fee
StringClassifications, constructed text, display summariesresp_age >= 18 ? "Adult" : "Minor"
Date/TimeDerived dates and timestampsstart_date.HasValue ? start_date.Value.AddDays(14) : start_date
The type is a contract. If the variable is configured as Long Integer, the expression must yield a whole-number result. A Boolean expression cannot be stored in it merely because true feels like β€œ1.”

4. Start with scalar calculations

MeaningVariable
Transfer amountamount_sent
Transfer feetransfer_fee

Double variable total_sender_cost:

amount_sent + transfer_fee

Because the questions are nullable, the result is not a meaningful total until its dependencies are answered. Do not silently turn missing into zero unless that is the questionnaire's defined rule. If missing really means zero for both components, state that explicitly:

(amount_sent ?? 0) + (transfer_fee ?? 0)

5. Build a Boolean flag, then use it

MeaningVariable / code
Respondent ageresp_age
Recent remittancerecent_remit; Yes = 1

Boolean variable is_eligible:

IsAnswered(resp_age) &&
resp_age >= 18 &&
recent_remit == 1

A later section can now use the readable enabling condition is_eligible. The variable centralizes the definition, but changing it also changes every dependent section, validation, filter or text substitution. Test all consumers after editing it.

6. Working household-roster model

The rest of this module uses one consistent example. Assume the questionnaire already contains a roster called members. Module 7 will show how a numeric, list, multi-select or fixed source creates its rows.

VariableType and codesMeaning
member_nameTextName or roster title.
ageInteger, nullableCompleted age in years.
sexSingle-select: Male = 1, Female = 2Recorded sex category.
relationshipSingle-select: Head = 1, Spouse = 2, Child = 3Relationship to household head.
school_attendSingle-select: Yes = 1, No = 2Current school attendance.
monthly_incomeDouble, nullableMonthly individual income.

The sample household is Amina, age 42, female and head; Bilal, age 18, male and child; and Sana, age 11, female and child. Their monthly incomes are 20,000, 5,000 and unanswered respectively. Keeping this model fixed makes each function's effect easier to see.

7. Collection-function map

Read a collection expression from left to right: start with a collection β†’ optionally filter rows β†’ choose or aggregate values β†’ return the required type.

FunctionQuestion it answersReturnsTypical household use
ContainsDoes this array or text contain a value?BooleanWhether asset code 3 was selected.
AnyDoes at least one row satisfy the rule?BooleanWhether any child under five lives in the household.
AllDoes every row satisfy the rule?BooleanWhether every member's age is answered.
CountHow many rows exist or qualify?Whole numberNumber of members, children or eligible women.
WhereWhich rows qualify?Filtered collectionKeep members aged 5–17 before another operation.
SelectWhich field should be taken from each row?Value collectionTake names from eligible member rows.
SumWhat is the total?NumberTotal household income or education spending.
Min / MaxWhat is the smallest/largest answered value?NumberYoungest/oldest age or earliest/latest event value.
FirstOrDefaultWhat is the first match, if one exists?One row/value or a defaultRetrieve a known unique head value after validating uniqueness.
String.JoinHow can values be combined into display text?StringShow the names of eligible members in an instruction.
Where and Select do not finish most calculations. They return collections. Follow them with an operation such as Count, Sum, Min, Max, FirstOrDefault or String.Join when a calculated variable needs one scalar result.

8. Contains, InList and Length: selection and membership

Contains becomes handy whenever one answer can contain several values. The object before the dot determines what β€œcontains” means.

Ordinary multi-select
assets.Contains(3)

Returns true when asset code 3 is selected. Use assets.Length to count selected codes.

Text
caseid.Contains("HH-")

Checks whether the text contains that character sequence. For a required prefix, caseid.StartsWith("HH-") is more precise.

A single-select has only one code, so compare it directly or use InList for several accepted codes:

relationship == 1

relationship.InList(1, 2)

The second expression means β€œhead or spouse.” Do not use Contains on a normal single-select answer.

9. Any: does at least one member qualify?

members.Any(person =>
    person.age < 5)

This returns Boolean. It is useful for enabling a child-health section, setting an eligibility flag, or warning that an age-specific module is expected. In the sample household it returns false.

Any() without a condition asks whether the roster has at least one row:

members.Any()

Use Any when you need yes/no existence. Do not calculate Count(...) > 0 unless you also need the count.

10. All: does every existing row qualify?

members.All(person =>
    IsAnswered(person.age))

This is useful for a completeness flag before enabling a household summary. Be aware of the empty-collection rule: All(...) is true when there are no rows because no row violates the condition. If the household must contain at least one member, write both requirements:

members.Any() &&
members.All(person =>
    IsAnswered(person.age))

11. Count: total rows, qualifying rows and answered rows

ExpressionMeaningSample result
members.Count()All existing roster rows.3
members.Count(p => p.age < 18)Members classified as children.1
members.Count(p => p.age >= 18 && p.sex == 2)Adult women.1
members.Count(p => IsAnswered(p.monthly_income))Members with income answered.2

For an ordinary multi-select question, use assets.Length rather than treating it like a roster. For a yes/no-mode multi-select, assets.Yes.Length, assets.No.Length and assets.Missing.Length count each response state.

12. Where: keep qualifying rows for the next operation

members.Where(person =>
    person.age >= 5 &&
    person.age <= 17)

Where produces a filtered sequence of school-age member rows. By itself it is not a number or Boolean. Chain another function according to the question:

// How many school-age members?
members
  .Where(p => p.age >= 5 && p.age <= 17)
  .Count()

// Do any school-age members not attend school?
members
  .Where(p => p.age >= 5 && p.age <= 17)
  .Any(p => p.school_attend == 2)

You can often express the second rule more simply as one Any predicate. Use Where when the filtered collection feeds a later projection or aggregation and improves readability.

13. Sum: household totals and conditional totals

Double variable household_income:

members.Sum(person =>
    person.monthly_income)

Survey Solutions' migration guidance states that unanswered numeric values are ignored by Sum. In the sample household the result is 25,000. If β€œnobody answered” must remain different from a genuine total of zero, guard that state explicitly:

members.Any(p => IsAnswered(p.monthly_income))
  ? members.Sum(p => p.monthly_income)
  : (double?)null

A conditional household total filters first:

members
  .Where(p =>
      p.age >= 18 &&
      IsAnswered(p.monthly_income))
  .Sum(p => p.monthly_income)
Never assume missing means zero merely because Sum can ignore it. Decide whether the indicator is valid when some members have missing income. You may need a completeness rule alongside the total.

14. Min and Max: youngest, oldest and range checks

Long Integer variables for youngest and oldest answered age:

Youngest answered age
members.Any(p => IsAnswered(p.age))
  ? members.Min(p => p.age)
  : (long?)null
Oldest answered age
members.Any(p => IsAnswered(p.age))
  ? members.Max(p => p.age)
  : (long?)null

Unanswered values are ignored by these aggregates, but an empty set has no meaningful minimum or maximum. The Any guard makes that state explicit. These functions are useful for household eligibility, age-range QA, finding the latest event number, or checking the largest plot size.

15. Select: project one field from each qualifying row

Select changes a collection of member rows into a collection of values:

members.Select(person =>
    person.member_name)

This does not yet produce a String variable. It produces a sequence of names that another operation can consume. A conditional projection combines Where and Select:

members
  .Where(p => p.age >= 18)
  .Select(p => p.member_name)

Use Select when the next step needs values rather than entire roster rows.

16. String.Join: turn projected values into readable text

String variable adult_names:

String.Join(", ",
  members
    .Where(p =>
        p.age >= 18 &&
        IsAnswered(p.member_name))
    .Select(p => p.member_name))

In the sample household the result is Amina, Bilal. It can be piped into an instruction such as β€œThe adults listed are %adult_names%.” This joins stored answer values; it is not a general way to retrieve localized categorical answer labels.

17. FirstOrDefault: retrieve one match carefully

If the design guarantees one household head, this expression obtains the head's age as a nullable value:

members
  .Where(p => p.relationship == 1)
  .Select(p => p.age)
  .FirstOrDefault()

FirstOrDefault is order-dependent and returns a default when no match exists. It can conceal zero matches or multiple heads, so pair it with a separate validation:

members.Count(p =>
    p.relationship == 1) == 1
Do not use β€œfirst matching person” when the interviewer must deliberately identify someone. Use a linked question for respondent, mother, beneficiary or decision-maker selection. Module 8 covers linked questions.

18. Read and build a chain in a fixed order

String.Join(", ",
  members
    .Where(p =>
        p.sex == 2 &&
        p.age >= 15 &&
        p.age <= 49)
    .Select(p => p.member_name))
  1. members: start with all household-member rows.
  2. Where: retain women aged 15–49.
  3. Select: take the name from each retained row.
  4. String.Join: combine those names into one display string.

Change the final operation to change the question being answered: use Count() for how many, Any() for whether at least one exists, Sum(...) for a total, or String.Join(...) for a display list.

19. Function selection practice

Survey requirementBest starting pattern
Enable immunization if any child is under five.members.Any(p => p.age < 5)
Validate exactly one household head.members.Count(p => p.relationship == 1) == 1
Calculate total income reported by adult women.Where(...).Sum(...)
Display the names of children currently attending school.String.Join(... Where(...).Select(...))
Find the oldest answered age.Any(answered) ? Max(...) : null
Check whether crop code 4 was selected.crops.Contains(4)

20. Construct scalar text and classifications

Conditional classification
resp_age >= 18
  ? "Adult"
  : "Minor"

Configured as a String variable.

Concatenate scalar text
first_name + " " + last_name

Useful when the two names are outside a roster or in the same current row.

Insert a calculated value into a question or static text with Survey Solutions text substitution, for example %adult_count%. Calculated results are otherwise not interviewer-editable answers.

21. Understand reevaluation and failure behavior

  • Calculated variables reevaluate automatically when a referenced dependency changes.
  • Interviewers cannot override the calculated result.
  • In logical expressions, an exception is treated as false; in calculations, an exception produces null.
  • A variable used only as a helper can be marked Do not export; an analytic indicator can be retained with a clear variable label.
  • Cycles are invalid: variable A cannot depend on B while B also depends on A.
Do not use exceptions as logic. Guard division by zero, nullable dates and absent selections intentionally. A calculation that β€œbecomes null” may hide a programming mistake as easily as it represents a legitimate unanswered state.

22. Connect calculations to questionnaire behavior

Enabling
is_eligible

Controls whether a question, subsection, section or roster is active.

Validation
self <= total_income

Checks whether an entered answer is acceptable. It does not make an unanswered question mandatory.

Critical rule
consent == 2 ||
IsAnswered(end_time)

Supports submission policy when Headquarters configures critical-rule handling.

23. Test the dependency graph, not just the formula once

  1. Open the interview with all dependencies unanswered.
  2. Enter values that make the result true or non-zero.
  3. Change one dependency so the result becomes false, zero or a different classification.
  4. Clear a dependency and observe the intended missing-value behavior.
  5. For rosters, add, edit and delete a row.
  6. Save, reopen and synchronize where applicable.
  7. Confirm every dependent section, validation, filter and substituted text updates correctly.

24. SurveyCTO translation reference

SurveyCTO ideaSurvey Solutions pattern
if(condition, A, B)condition ? A : B
count-if(...)roster.Count(item => condition)
Does any row qualify?roster.Any(item => condition)
Do all rows qualify?roster.All(item => condition)
Conditional sumroster.Where(item => condition).Sum(item => item.amount)
join(...)String.Join(", ", roster.Select(item => item.name))
join-if(...)String.Join(", ", roster.Where(item => condition).Select(item => item.name))

Expression evaluation β†— Β· LINQ in rosters β†—

Design habit: give reusable logic a name that expresses its survey meaning, select a type that matches its result, document how missing dependencies behave, and test every transition that can change the result.
ICM applied workshop Β· Module 6

Read familiar SurveyCTO operations as Survey Solutions design decisions

Examples below are traced to the supplied ICM Integrated Survey workbook. Source excerpts are exact cell contents, not endorsements of their logic. Target examples are teaching designs that require Designer compilation and Android/web testing; no full instrument conversion or runtime equivalence is claimed.

Conditional flags, typed results, and special response codes

SurveyCTO source cells and expressions
survey!V634 Β· r_age_calc (calculate)
if(${r_age_unit}=3, ${r_age}, 0)
survey!V635 Β· r_age_calc_month (calculate)
if(${r_age_unit}=2, ${r_age}, 
if(${r_age_unit}=1, 0,'null'))
survey!V636 Β· r_age_calc_days (calculate)
if(${r_age_unit}=3, 'null', 
if(${r_age_unit}=2, ${r_age}*30, 
if(${r_age_unit}=1, ${r_age},'null')))

The age helpers combine numbers with the literal string β€œnull”. Quoted null is text, not an unanswered numeric value. Decide whether an age in days or months should count as zero completed years, and keep that decision separate from an unknown age.

Proposed Long integer r_age_calc, with Numeric Integer source questions:
(r_age_unit == 3 && r_age.HasValue) ? (long?)r_age.Value :
((r_age_unit == 1 || r_age_unit == 2) && r_age.HasValue) ? (long?)0 : null
Test the intent: Test an infant, a completed-year age, and an unanswered unit. Do not replace all missing ages with zero; that would classify unknown ages as infants.

Conditional counts and sums express different intentions

SurveyCTO source cells and expressions
survey!V649 Β· spouse_count (calculate)
count-if(${new_hhmem},${r_relation}=2)
survey!V650 Β· head_count (calculate)
count-if(${new_hhmem},${r_relation}=1)
survey!V1230 Β· no_unearned_income (calculate)
sum-if(${inc_any_unearn_ltm}, ${inc_any_unearn_ltm} = 1)

The form counts heads and spouses, and sums a yes/no income flag only when it equals 1. Summing a flag is a count only because its included value is exactly 1. Identify the roster owning the field before writing the aggregate.

Outside new_hhmem, Long integer:
new_hhmem.Count(p => p.r_relation == 1)

Validation of the intended single-head rule:
new_hhmem.Count(p => p.r_relation == 1) == 1
Test the intent: Try zero, one, and two heads. Apply the rule only when the relevant household-listing stage is complete; decide how unanswered relationships are handled.

Money, month counts, and sentinel precedence

SurveyCTO source cells and expressions
survey!V2187 Β· temp_icm_sale (calculate)
if(${icm_op} >= 0 and ${icm_no_sale} >= 0, ${icm_op} - ${icm_no_sale}, 
   if(${icm_op} = -999 or ${icm_no_sale} = -999, -999, 
      if(${icm_op} = -888 or ${icm_no_sale} = -888, -888, '')))
survey!V2190 Β· temp_icm_sale_high (calculate)
if(${icm_op} >= 0 and ${icm_prof_avg_n} >= 0, ${icm_op} - ${icm_no_sale} - ${icm_prof_avg_n}, 
   if(${icm_op} = -999 or ${icm_no_sale} = -999 or ${icm_prof_avg_n} = -999, -999, 
      if(${icm_op} = -888 or ${icm_no_sale} = -888 or ${icm_prof_avg_n} = -888, -888, '')))
survey!V2195 Β· add_months_to_twelve (calculate)
if(${icm_no_sale} != null and ${icm_prof_avg_n} != null and ${icm_prof_high_n} != null and ${icm_prof_low_n} != null, 
   if(${icm_no_sale} = -999 or ${icm_prof_avg_n} = -999 or ${icm_prof_high_n} = -999 or ${icm_prof_low_n} = -999, -999, 
      if(${icm_no_sale} = -888 or ${icm_prof_avg_n} = -888 or ${icm_prof_high_n} = -888 or ${icm_prof_low_n} = -888, -888, 
         ${icm_no_sale} + ${icm_prof_avg_n} + ${icm_prof_high_n} + ${icm_prof_low_n})), 
   0)

Nested if expressions propagate -999 and -888 and otherwise subtract month counts. These are stored answers, not null. Preserve which special code takes precedence. The row 2190 first branch subtracts icm_no_sale without testing it in that branch; review this dependency rather than silently copying it. Row 2195 uses an unquoted null token whose source meaning should be verified.

Proposed Double temp_icm_sale (same sentinel priority):
(icm_op >= 0 && icm_no_sale >= 0) ? (double?)(icm_op - icm_no_sale) :
(icm_op == -999 || icm_no_sale == -999) ? (double?)-999 :
(icm_op == -888 || icm_no_sale == -888) ? (double?)-888 : null
Test the intent: Test 12 minus 3, a missing operand, each special code, and one operand -999 with the other -888. Check totals exclude special codes instead of treating them as negative quantities.

Capping follow-up work with min and max

SurveyCTO source cells and expressions
survey!V863 Β· count_confirmed_currenthh (calculate)
sum(${ph1_confirmed_final})
survey!V864 Β· need_new_phones (calculate)
max(0, 3 - ${count_confirmed_currenthh})
survey!V865 Β· repeat_count_new_phn (calculate)
min(${need_new_phones}, ${temp_r_phone_calc})

The phone workflow counts confirmations, computes the remaining slots to reach three, then caps new follow-ups by eligible candidates. Learn the dependency chain before translating its arithmetic. Math.Max and Math.Min require deliberate handling of nullable calculated values.

Long integer confirmed_count:
repeat_hh_phone.Count(p => p.ph1_confirmed_final == 1)

Long integer slots_needed:
confirmed_count.HasValue ? (long?)Math.Max(0L, 3L - confirmed_count.Value) : null
Test the intent: Expect 3, 2, and 0 slots for 0, 1, and 4 confirmations. Define eligible_count separately and cap by it only after both counts are known. A calculated count is not automatically a valid numeric roster trigger.

Dates: calendar months are not elapsed days

SurveyCTO source cells and expressions
survey!V1602 Β· start_month (calculate)
int(substr(indexed-repeat(${nonagown_start}, ${repeat_nonagown2}, ${index_nonagownb3}), 0, 2))
survey!V1603 Β· start_year (calculate)
int(substr(indexed-repeat(${nonagown_start}, ${repeat_nonagown2}, ${index_nonagownb3}), 3, 7))
survey!V1605 Β· nonagown_mth_grant (calculate)
((int(format-date(today(), '%Y')) - 2025) * 12) + (int(format-date(today(), '%m')) - 6)
survey!V1606 Β· nonagown_mth (calculate)
((int(format-date(today(), '%Y')) - int(substr(indexed-repeat(${nonagown_start}, ${repeat_nonagown2}, ${index_nonagownb3}), 3, 7))) * 12) + (int(format-date(today(), '%m')) - int(substr(indexed-repeat(${nonagown_start}, ${repeat_nonagown2}, ${index_nonagownb3}), 0, 2)))
survey!V1607 Β· nonagown_possible_op (calculate)
min(${nonagown_mth}, ${nonagown_mth_grant})

The business logic extracts month and year from a month-year string, then calculates calendar-month distance. SurveyCTO substr uses start/end positions; C# Substring uses start/length. Prefer validated date or year/month inputs to repeated string parsing. Choose a fixed interview reference date explicitly instead of assuming a continuously changing clock is equivalent.

Proposed Long integer months_apart, with Date/Time questions start_date and reference_date:
(start_date.HasValue && reference_date.HasValue) ?
(long?)((reference_date.Value.Year - start_date.Value.Year) * 12
+ reference_date.Value.Month - start_date.Value.Month) : null
Test the intent: January 31 to February 1 is one calendar-month boundary, not one completed month. Test the year boundary, future dates, missing dates, and reopening later. Confirm the reporting period before changing hard-coded 2024/2025 dates.

Section timers are event-dependent calculations

SurveyCTO source cells and expressions
survey!V611 Β· start_res_member_info (calculate_here)
once(duration())
survey!V2321 Β· end_icm_qx (calculate)
once(duration())
survey!V2322 Β· dure_icm_qx (calculate)
( ${end_icm_qx}-${start_icm_qx} ) div 60

ICM captures once(duration()) at section boundaries and divides a difference by 60. The calculate_here type matters: it evaluates when that place is reached. A normal Survey Solutions variable recalculates and is not a drop-in first-visit timer.

Design decision, not a C# substitution:
Use paradata for a documented section-duration measure,
or explicit timestamp questions for a clearly defined operational event.
For a verified seconds difference, divide by 60.0 to retain fractional minutes.
Test the intent: Define whether pauses, revisits and corrections count. Compare first entry, return visit, save/reopen and offline operation. Do not label end-minus-start as active interviewing time without checking its meaning.

Reference guides: SurveyCTO expressions; calculation timing; Survey Solutions rosters; numeric lookup tables.

Apply it to the practice questionnaire. Create one calculated String variable from the practice names, such as suffix_text, and test both a filled and blank suffix. The full roster-name expression is developed in Module 14.
Module 7

Rosters: SurveyCTO repeats, but with a source that defines the rows

Modules 5 and 6 treated members as an existing collection so you could learn how to query it. This module now explains where those rows come from, how their identities behave, and what happens when the source changes.

SurveyCTO translation: begin repeat β‰ˆ Roster. But instead of writing a repeat_count expression in a column, Survey Solutions asks you to choose what creates the roster rows.
Keep structure and query logic separate. The roster source determines which rows exist. Functions such as Any, Count, Where, Sum, Min and Max inspect the rows that already exist; they do not create or preserve roster rows.

The four roster patterns

Roster typeWhat creates rows?Typical useSurveyCTO intuition
Numeric rosterA numeric questionβ€œHow many household members?”repeat_count = ${hhsize}
List rosterItems typed into a list questionNames of people, plots, firmsDynamic repeat from a list
Multi-select rosterSelected answer optionsOwned assets, cultivated cropsRepeat over selected choices
Fixed rosterItems defined in Designer7 days, expenditure categoriesFixed repeat structure

Example 1 β€” dynamic repeat count from a numeric question

SurveyCTO
integer hhsize
begin repeat members
  repeat_count = ${hhsize}

  text name
  integer age
  select_one sex sex
end repeat
Survey Solutions
Numeric question:
hhsize

Roster:
members
Source type: Numeric
Source question: hhsize

Inside roster:
name
age
sex
Illustrated field view β€” numeric roster
How many people live in this household?3
↓
1
Name: Amina
Age: 42
2
Name: Bilal
Age: 18
3
Name: Sana
Age: 11

If hhsize = 3, Survey Solutions creates three members rows. This is the closest equivalent of a SurveyCTO dynamic repeat_count. The official roster documentation defines numeric rosters exactly this way.

Example 2 β€” SurveyCTO count-selected() intuition

Suppose respondents select every crop they cultivate:

VariableTypeCodes
cropsMulti-select1 = Maize, 2 = Rice, 3 = Groundnut, 4 = Cassava
SurveyCTO way of thinking
count-selected(${crops})

You may use the number selected to determine how many repeat instances you need.

Survey Solutions way

You normally do not need to calculate the count first. Make crops the roster source directly. Each selected crop becomes one roster row.

Roster: crop_details
Source type: Multi-select
Source question: crops
Illustrated field view β€” multi-select creates the rows
Which crops do you cultivate? βœ“ Maize Rice βœ“ Groundnut βœ“ Cassava
↓ 3 selected choices = 3 roster rows
1
Maize
Area cultivated: ___
3
Groundnut
Area cultivated: ___
4
Cassava
Area cultivated: ___
If you actually need the count for another calculation: for an ordinary multi-select question, crops.Length is the Survey Solutions equivalent of SurveyCTO count-selected(${crops}).

Example 3 β€” yes/no multi-select roster

A multi-select question in yes/no mode can also trigger a roster. Only items marked Yes create roster rows. If you need the number marked Yes in an expression, use:

assets.Yes.Length

For example, if the asset list contains Radio, TV, Refrigerator and Motorcycle, and Radio + Motorcycle are marked Yes, the asset-detail roster has two rows.

Example 4 β€” list question as the roster source

Instead of asking β€œHow many members?” and then collecting names inside the roster, you can first collect a list:

Illustrated field view β€” list roster
List all household membersAminaBilalSana
↓ each list item becomes a roster row
Amina
Age: 42
Sex: Female
Bilal
Age: 18
Sex: Male
Sana
Age: 11
Sex: Female

This pattern is often more intuitive for household listings because the person's name can serve as the visible roster title.

Scaling a roster down: what happens if the source gets smaller?

This is important if you are used to changing repeat_count in SurveyCTO. The roster follows its trigger question. If a numeric trigger drops from 4 to 2, the roster drops from four rows to two. If a selected crop is unselected, that crop's roster row disappears. If an item is deleted from a list source, its corresponding roster row disappears.

Illustrated example β€” reducing a numeric roster
Before
hhsize = 4
Amina β€” answered
Bilal β€” answered
Sana β€” answered
Omar β€” answered
After interviewer changes it to 2
hhsize = 2
Amina β€” remains
Bilal β€” remains
Sana β€” row removed
Omar β€” row removed
Treat reducing a roster source as destructive. Survey Solutions records automatic answer removal when a roster row is deleted. Do not design a workflow that casually reduces a trigger after detailed information has already been entered. If preloaded trigger values must not be reduced, Survey Solutions can protect preloaded numeric, list, or multi-select trigger answers from reduction while still allowing extension.

index() equivalent: use @rowindex, but learn @rowcode too

SurveyCTO/ODK's repeat-position idea maps most closely to Survey Solutions @rowindex. The official migration guide maps position(xpath) to @rowindex.

@rowindex

The current position of the roster item inside its parent roster.

It is zero-based:

first row  β†’ @rowindex = 0
second row β†’ @rowindex = 1
third row  β†’ @rowindex = 2

If rows are deleted, the remaining rows can be renumbered.

@rowcode

The identity/code of the current roster item.

Its meaning depends on the roster source:

  • numeric roster: generated row code;
  • multi-select roster: selected option code;
  • fixed roster: item code defined in Designer;
  • list roster: stable item code, which may have gaps after deletions.
Do not treat @rowcode as another spelling of index(). For position, think @rowindex. For identity, think @rowcode.

Pulling a name out of a roster: SurveyCTO indexed-repeat()

Your SurveyCTO pattern creates household-level calculations from individual repeat instances. For example, r_comp_name is the name field inside repeat new_hhmem:

new_calc_hh_mem_name1 = indexed-repeat(${r_comp_name}, ${new_hhmem}, 1)
new_calc_hh_mem_name2 = indexed-repeat(${r_comp_name}, ${new_hhmem}, 2)
new_calc_hh_mem_name7 = indexed-repeat(${r_comp_name}, ${new_hhmem}, 7)

The three arguments mean field to retrieve, containing repeat, one-based instance number. These expressions request the first, second, and seventh names. SurveyCTO also supports additional repeat/index pairs for nested repeats. Its documentation notes fallback to instance 1 for an invalid index, so do not assume an invalid request always returns blank. Test the source behavior before reproducing it. SurveyCTO indexed-repeat reference β†—

A positional retrieval pattern in Survey Solutions

For this exercise, create a roster named new_hhmem containing Text question r_comp_name. Inside that roster add a Long integer calculated variable named member_position, with expression @rowindex + 1. It exposes the current row's position as a one-based number for this comparison. Outside the roster, add these String calculated variables:

// new_calc_hh_mem_name1
new_hhmem.Where(p => p.member_position == 1)
  .Select(p => p.r_comp_name).FirstOrDefault() ?? ""

// new_calc_hh_mem_name2
new_hhmem.Where(p => p.member_position == 2)
  .Select(p => p.r_comp_name).FirstOrDefault() ?? ""

// new_calc_hh_mem_name7
new_hhmem.Where(p => p.member_position == 7)
  .Select(p => p.r_comp_name).FirstOrDefault() ?? ""

Enter each expression without its explanatory comment. Where finds the requested position; Select extracts its name; FirstOrDefault retrieves one value or null; ?? "" displays an empty string for null. The alias p means one roster row. Place static text outside the roster containing Second member: %new_calc_hh_mem_name2% to see the result.

This example deliberately returns blank when the requested position is absent. It is a design choice, not a promise of identical SurveyCTO fallback behavior. It also makes an absent row and an unanswered name look the same. If you must distinguish them, use a Boolean variable such as new_hhmem.Any(p => p.member_position == 7) to check row existence separately.

Do not mechanically translate the seventh instance into new_hhmem[7]. Roster addressing must respect row codes and the roster source; a position and a person's identity are different. The explicit position variable makes the intended rule visible. Compile these expressions in your Designer version and test the row changes below before using them in a project.

Choose the retrieval rule that matches the survey question

What you meanPattern
The current member's name, inside the same rosterRefer directly to r_comp_name; use %r_comp_name% in question text. No cross-roster retrieval is needed.
The second member in the current orderingUse the positional calculation above. Deleting an earlier row can change who is second.
The household headFilter by the relationship code, for example new_hhmem.Where(p => p.relationship == 1).Select(p => p.r_comp_name).FirstOrDefault() ?? "", only if 1 means head. Validate that exactly one row qualifies; the first row is not necessarily the head.
A respondent-selected memberUse a linked question and its row identity (Module 8), rather than storing the member's current position.
All entered names for a summaryString.Join(", ", new_hhmem.Where(p => !String.IsNullOrEmpty(p.r_comp_name)).Select(p => p.r_comp_name)). This produces display text, not a member identifier.

In your screenshot, count(${new_hhmem}) counts repeat instances. The corresponding roster count is new_hhmem.Count(); counting answered names instead requires a predicate. Do not use a count of distinct name strings as a count of unique people: two members may share a name, and spelling differences can make one name appear distinct.

Retrieval lab: Enter Amina, Ben, and Chao in three rows. Expect the first calculation to show Amina, the second Ben, and the seventh blank. Clear Ben's name and check row existence separately. Restore it, then delete the first row in a list-driven roster: inspect the recalculated positions and names. Add enough rows to reach seven, save/reopen the test interview, and verify the seventh value. Repeat on Android and web. For nested rosters, first identify the parent row and then its child; do not apply this one-level example across all households or families.
Read Excel formulas and questionnaire expressions separately. In the ICM workbook, survey!W970 contains the spreadsheet formula ="${"&B969&"}"; its cached result is ${fd_cons_prep_cnt}. The first builds text in Excel; the second instructs SurveyCTO how many repeat instances to create. In Designer, reconstruct the intended roster source rather than pasting an Excel formula. Cached workbook values can be stale, so verify the effective source definition.

Why the distinction matters after deleting list items

Illustrated example β€” row index can change while row code stays tied to identity
Original list
Name
@rowindex
@rowcode
Meaning
Amina
0
0
first entered item
Bilal
1
1
second entered item
Sana
2
2
third entered item
Delete Bilal
Name
@rowindex
@rowcode
Meaning
Amina
0
0
still first position
Sana
1
2
now second position, original identity retained

Using row identity in conditions

Suppose a fixed roster has codes 10 = Matches and 11 = Cigarettes. Inside that roster you can apply different rules by row:

@rowcode == 10 ? self <= 20 :
@rowcode == 11 ? self <= 100 :
true

The system-generated @rowcode exists specifically for referring to particular roster rows.

Dynamic roster titles

To make roster questions intuitive for interviewers, use the roster's title/name in question text. For a member roster named members, you can write wording such as:

How old is %rostertitle%?

If the current roster title is Amina, the interviewer sees How old is Amina? The roster identifier form, such as %members%, is also supported, but %rostertitle% makes the intent explicit while working inside the current roster. Neither form is valid outside its applicable roster scope.

Nested rosters

You can put one roster inside anotherβ€”for example:

Household
└─ members
   β”œβ”€ name
   β”œβ”€ age
   └─ jobs
      β”œβ”€ employer
      └─ earnings

Survey Solutions supports nested rosters, but current limits cap nesting depth and total roster instances. Complex nested designs should be tested with realistic maximum sizes rather than only with tiny test households.

Table presentation for web interviews

For CAWI/web interviewing, some simple rosters can be displayed as a table, with roster items as rows and questions as columns. This can be useful for compact modules such as prices or simple household grids. It is subject to design restrictions and falls back to the normal subsection-style roster on tablets.

Illustrated web table-roster view
MemberAgeYears schooling
Amina4212
Bilal1813
Sana115

Practical design rules

1

Choose the trigger deliberately.
Numeric when only quantity matters; list when names/items are entered; multi-select when rows come from known categories.

2

Set sensible maximums.
Multi-select and list trigger questions should have maximum sizes. Numeric triggers should be constrained with validation such as self <= 30.

3

Know identity vs position.
Use @rowindex for position and @rowcode for stable row identity/code.

Design exercise: You are building a remittance survey. Decide which roster source is best for each:
  1. Household members whose names must be visible later.
  2. Remittance channels selected from a fixed list.
  3. Exactly seven days of the week.
  4. A respondent reports they made n transfers and you need one row per transfer.
Show suggested answers

1. List roster, because names themselves are useful as roster titles and later linked choices. 2. Multi-select roster. 3. Fixed roster. 4. Numeric roster triggered by the number of transfersβ€”unless you first collect meaningful transfer labels in a list, in which case a list roster may be more intuitive.

From roster source to household indicator

This final sequence connects the three modules without mixing their responsibilities:

StepDesign decisionExampleTaught in
1Create stable person rows.List question member_names triggers roster members.Module 7
2Collect typed answers inside each row.age, sex, relationship, monthly_income.Modules 5 and 7
3State the household-level question.β€œHow many adult women are listed?”Module 5
4Choose the function by required result.A number is required, so use Count.Module 6
5Write and name the calculation.members.Count(p => p.age >= 18 && p.sex == 2)Module 6
6Test row lifecycle transitions.Add, edit and delete a member; confirm the count and dependent logic update.Modules 6 and 7
Diagnostic shortcut: if the wrong people exist, inspect the roster source and row lifecycle. If the correct people exist but the result is wrong, inspect the expression's scope, predicate, missing-value rule and aggregation function.
ICM applied workshop Β· Module 7

Read familiar SurveyCTO operations as Survey Solutions design decisions

Examples below are traced to the supplied ICM Integrated Survey workbook. Source excerpts are exact cell contents, not endorsements of their logic. Target examples are teaching designs that require Designer compilation and Android/web testing; no full instrument conversion or runtime equivalence is claimed.

Parallel repeats: join people by identity, not only position

SurveyCTO source cells and expressions
survey!W613 Β· repeat_res_member_info (begin repeat)
${hhmem_new_num_push}
survey!V614 Β· index_res_member_info (calculate)
index()
survey!V615 Β· index_res_kids_id (calculate)
${index_res_member_info} + 18
survey!V616 Β· temp_res_member_info_name (calculate)
indexed-repeat(${r_fname}, ${new_hhmem}, ${index_res_member_info})
survey!V619 Β· temp_res_member_info_rela (calculate)
indexed-repeat(${r_relation}, ${new_hhmem}, ${index_res_member_info})

The demographics repeat has the same count as new_hhmem and pulls names and relationships with indexed-repeat. The +18 offset reserves codes after baseline members. This is an instrument-specific ID scheme, not a Survey Solutions roster rule.

Proposed design:
One members roster holds name, stable source_member_id, relationship,
residency status, age and education subsections.
Use a separate source_member_id field to preserve old identifiers.
Do not assign @rowcode = index + 18 or equate @rowindex with a persistent ID.
Test the intent: Delete a middle member, add someone, change residency, then check that age, education and phone ownership still belong to the same person. Preserve baseline IDs across waves; repeated names are not unique keys.

Filtered names, ages, and IDs: keep rows together

SurveyCTO source cells and expressions
survey!V746 Β· join_hh_mem_older_six2 (calculate)
join-if(',', ${info_current_all_index}, ${age_hhmem} >= 6)
survey!V748 Β· count_hh_mem_older_six (calculate)
count-if(${info_current_all}, ${age_hhmem} >= 6)
survey!V749 Β· join_hh_mem_older_thirteen2 (calculate)
join-if(',', ${info_current_all_index}, ${age_hhmem} >= 13)
survey!V750 Β· join_hh_mem_13_older (calculate)
join-if(',', ${age_hhmem}, ${age_hhmem} >= 13)
survey!V751 Β· join_hh_mem_older_13 (calculate)
join-if(',', ${info_current_all_name}, ${age_hhmem} >= 13)
survey!V752 Β· count_hh_mem_older_13 (calculate)
count-if(${info_current_all}, ${age_hhmem} >= 13)
survey!W1038 Β· repeat_earned_inc (begin repeat)
${count_hh_mem_older_13}
survey!V1040 Β· any_work_screen_name (calculate)
item-at(',', ${join_hh_mem_older_13}, (${index_any_work_screen} - 1))
survey!V1041 Β· any_work_screen_memid (calculate)
item-at(',', ${join_hh_mem_older_thirteen2}, (${index_any_work_screen} - 1))
survey!V1042 Β· any_work_screen_age (calculate)
item-at(',', ${join_hh_mem_13_older}, (${index_any_work_screen} - 1))

ICM constructs parallel comma-separated lists and then reads the same position from each. A missing value, delimiter in a name, or inconsistent filter can break alignment. In Survey Solutions, keep values in the member row and enable an age-eligible subsection, or use a linked selection for eligible people.

Assuming age_hhmem is numeric inside info_current_all:

Household-level Long integer:
info_current_all.Count(p => p.age_hhmem >= 13)

Household-level String display summary:
String.Join(", ", info_current_all.Where(p => p.age_hhmem >= 13)
.Select(p => p.info_current_all_name))

Within that roster, income subsection enabling:
age_hhmem >= 13
Test the intent: Test ages 12 and 13, an unanswered age, two people with the same name, and a name containing a comma. The joined text is for display; use the roster identity for later references.

Selected-item repeats become categorical rosters

SurveyCTO source cells and expressions
survey!W884 Β· repeat_fd_cat (begin repeat)
9.0
survey!V891 Β· fd_cons_cnt (calculate)
count-selected(${fd_cons_1})
survey!W892 Β· repeat_fd_cons (begin repeat)
${fd_cons_cnt}
survey!V894 Β· fd_cons_val (calculate)
selected-at(${fd_cons_1},index()-1)
survey!V895 Β· fd_cons (calculate)
pulldata('preload_fd_cat', 'fd_cons_name', 'cons',${fd_cons_val})

The food section has nine outer categories, then one repeat per selected food item. selected-at retrieves a choice code by zero-based selection position. A fixed outer roster plus an ordinary multi-select-triggered inner roster can retain the hierarchy without a calculated repeat count and selection-position helper.

Proposed design:
Fixed food-category roster β†’ multi-select fd_cons_1 β†’ item roster.
Inside the item roster, @rowcode identifies the selected category code;
use the chosen item labels for roster titles.
Preserve multilingual labels through translations, not numeric lookup columns.
Test the intent: Select two foods, deselect one, reselect it, and change the outer category. Check which answers survive. Decide explicitly how 0, -888 and -999 options affect rows; a triggered roster may otherwise create rows for special choices too.

Nested child counts: validate both identity and age bands

SurveyCTO source cells and expressions
survey!W702 Β· r_kids (select_multiple old_new_hh_members)

survey!W703 Β· repeat_kids (begin repeat)
count-selected(${r_kids})
survey!V704 Β· kid_age (calculate)
selected-at(${join_age_kids_15}, index() - 1)
survey!V705 Β· kid_age3 (calculate)
if(${kid_age} <0 and ${kid_age} <=2, 1, 0)
survey!V706 Β· kid_age3to8 (calculate)
if(${kid_age} >2 and ${kid_age} <=8, 1, 0)
survey!V707 Β· kid_age9to15 (calculate)
if(${kid_age} >8 and ${kid_age} <=15, 1, 0)
survey!V709 Β· r_kids_baby (calculate)
sum(${kid_age3})

A parent selects children, but kid_age reads the position in join_age_kids_15. Verify that this list contains exactly those selected children in matching order. Also, kid_age3 currently tests age < 0 AND age <= 2: it will not flag any nonnegative infant age. That is observed source logic, not an approved age-band definition.

Proposed rule after confirming ages and child identities:
children.Count(c => c.age >= 0 && c.age <= 2)
children.Count(c => c.age > 2 && c.age <= 8)
children.Count(c => c.age > 8 && c.age <= 15)

Here children is a proposed child roster and age is numeric;
it is not a literal rename of the ICM repeat.
Test the intent: Use ages 0, 2, 3, 8, 9, 15, unknown and -999. Select only the last two of four children and verify their ages, not the first two ages in a household list. Confirm the intended boundary with the survey owner before correcting the source rule.

Deduplicating lists and combining old/new records

SurveyCTO source cells and expressions
survey!V312 Β· count_join_nonagwork_name (calculate)
count-items(" ", de-duplicate(" ", ${join_nonagwork_num}))
survey!V1052 Β· join_ag_index (calculate)
de-duplicate(' ',  join(' ', ${ag_index}))

survey!V1053 Β· join_nonag_index (calculate)
de-duplicate(' ',  join(' ', ${nonag_index}))

survey!W1595 Β· repeat_nonagown3 (begin repeat)
${count_nonagown_list}+${nonagown_n_calc}

de-duplicate and count-items convert strings into sets; the business count adds old and new records. Translate the underlying entities, not merely the delimiters. Deduplicate by a documented stable ID; equal labels do not establish equal people or businesses.

Proposed Long integer, assuming a redesigned members roster and String member_id:
members.Where(p => !String.IsNullOrEmpty(p.member_id))
.Select(p => p.member_id).Distinct().Count()
Test the intent: Test duplicate IDs with different labels, duplicate labels with different IDs, blank IDs, and a baseline record also entered as new. Resolve conflicts before discarding duplicates. A sum of two counts does not prove the record sets are disjoint.

Reference guides: SurveyCTO expressions; calculation timing; Survey Solutions rosters; numeric lookup tables.

Apply it to the practice questionnaire. Create the member_fnames List question and use it to generate the members roster. Enter two synthetic names, then verify that the roster has exactly two rows.
Module 8

Linked questions: turning earlier answers into live answer options

This is one of Survey Solutions' most powerful features for complex household, labor, agriculture, education, health, and establishment surveys. A linked categorical question can take values collected earlierβ€”often people or roster rowsβ€”and turn them into the answer options of a later question.

SurveyCTO intuition: think of this as a built-in combination of dynamic choice lists + repeat-aware references + choice filtering. Instead of manually constructing a choice list, Survey Solutions lets a single- or multi-select categorical question bind directly to an earlier question or roster.

Simple example: choose the household head

You first collect household members:

Earlier roster: members
0
Amina
42 Β· Female
1
Bilal
18 Β· Male
2
Sana
11 Β· Female
Source
member_name inside roster members
β†’
Linked single-select
hh_head
Who is the household head?
What the interviewer sees
β—‹ Aminaroster row 0
β—‹ Bilalroster row 1
β—‹ Sanaroster row 2

Survey Solutions' official categorical-question documentation describes linked questions exactly this way: their categories are generated during the interview from answers to a previous text, numeric, date question, or roster source. Categorical questions β†—

How you configure one in Designer

Linked describes the source of the answers. In the Designer interface shown by the trainee, use Source of categories; older documentation instead shows an Is linked checkbox.

Source of categoriesMeaningExample
User defined categoriesOptions entered for this question.Yes / No.
Reusable categoriesA named category set in the questionnaire.The synthetic grade_codes list you create.
List question or question from roster groupChoices obtained from the selected interview source.Household members entered earlier.
β€œList question” is a specific Survey Solutions question type. It means an open-ended list whose items are entered during the interviewβ€”for example, β€œList all household members.” It does not mean any categorical question that displays a list of options. A categorical multi-select called list_fruits is still a multi-select question, so it will not appear as a direct List-question source in this binding selector.

If the binding dropdown is empty

  1. Confirm that the intended source is located before the linked question and has been saved.
  2. For a direct source, use an actual List question, or a supported Text, Numeric, or Date question located inside a roster.
  3. If the aim is to show a subset of the same fixed categories selected in an earlier categorical multi-select, leave the linked-source selector. Give both questions the same category codes and use an @optioncode filter, as shown below.
  4. Create a roster from the multi-select only when the questionnaire needs one rowβ€”or several follow-up questionsβ€”for every selected category.
Favorite fruit: filter the choices; do not create a roster

Suppose list_fruits is a categorical multi-select with codes 1 Mango, 2 Oranges, 3 Pineapples, and 4 Kiwi. The next question asks, β€œOf the fruits you selected, which is your favorite?” This is one answer chosen from a filtered fixed list, so it does not require a roster.

  1. Create favorite_fruit as a Categorical: Single-select question.
  2. Use the same four category codes and titles. Prefer one Reusable categories set bound to both questions; separately entered categories also work only if their codes remain identical.
  3. Keep Source of categories as User defined categories or Reusable categories. Do not choose List question or question from roster group.
  4. Add this option filter: list_fruits != null && list_fruits.Contains(@optioncode). If the symbol is unfamiliar, review Module 5: What @optioncode means.
  5. Add this enabling condition so the question appears only after at least one fruit is selected: list_fruits != null && list_fruits.Length > 0.

Designer evaluates the filter once for every candidate category. Here @optioncode is the code of the candidate currently being tested. If the respondent selected Mango (1) and Kiwi (4), only Mango and Kiwi appear in favorite_fruit.

Follow the behavior from Designer to the interview

The screenshots below show the two expressions doing different jobs. The enabling condition controls whether the whole favorite-fruit question is active. The option filter controls which fruit choices appear after the question becomes active.

Complete the reusable-category binding. In the Designer screenshot, Source of categories is set to Reusable categories, but Bind to reusable categories appears empty. Select the reusable fruit setβ€”for example fruit_codesβ€”before saving and compiling. Ideally bind both list_fruits and fav_fruits to that same set. Otherwise the filter can compile against codes whose labels or meanings do not match the source question.
Interview stateEnabling conditionOption filter resultWhat the interviewer sees
No fruits selectedfalseNot operationally relevant while the question is disabledThe favorite question is shaded and unavailable.
Mango and Pineapples selectedtrueTrue for codes 1 and 3; false for 2 and 4Only Mango and Pineapples appear as radio-button choices.
Selection later changesReevaluatedReevaluated for every candidate codeThe available favorite choices change; verify any previously selected favorite during testing.
Behavior test: Select Mango and Pineapples, choose Mango as the favorite, then return to list_fruits and deselect Mango. Confirm how Web Tester handles the now-ineligible favorite. Repeat in the Interviewer application. Record whether the answer is cleared, flagged, or otherwise requires correction; this dependency-changing case matters more than testing only the happy path.
Questionnaire intentionSurvey Solutions design
Choose one favorite from previously selected fixed fruitsUse the same categories plus list_fruits.Contains(@optioncode). No roster.
Ask quantity, price, frequency, or another set of questions for every selected fruitCreate a roster sourced by list_fruits and place the repeated questions inside it.
Let respondents type arbitrary fruit names, then choose one of those entered namesUse a true List question, then bind the later linked categorical question to that List.
  1. Create a categorical single-select or categorical multi-select question.
  2. Open Source of categories and choose List question or question from roster group.
  3. In the binding/source selector that appears, choose the appropriate list question, supported question inside a roster, or roster where offered. The binding caption can vary by Designer version.
  4. If needed, add a Filter expression so only eligible source items appear. The roster filter examples below assume a question inside a roster; do not assume a direct list source exposes the same candidate fields.
  5. Save, Compile, and Test. Enter source names first, confirm the linked choices appear, then change or remove a source item and check the selection.
You can link to a roster itself. If the roster has meaningful row titlesβ€”for example member names supplied by a list rosterβ€”you can bind to the roster and use those row titles as the linked options. Survey Solutions added this specifically for rosters that may not contain a separate suitable text question. Linked categories β†—

Single-select vs multi-select linked questions

Linked single-select

Use when exactly one earlier item should be chosen.

  • Who is the household head?
  • Who is this person's spouse?
  • Who owns the dwelling?
  • Which plot is the largest?
Linked multi-select

Use when several earlier items may be chosen.

  • Which members visited a doctor?
  • Who received remittances?
  • Which plots used fertilizer?
  • Which household members are employed?

Filtering linked options: where this becomes powerful

The Filter field is evaluated once for every potential option. If the expression is true, that option is shown; if falseβ€”or if evaluation throws an exceptionβ€”the option is hidden. Conceptually, it is an enabling condition applied separately to each possible answer option. Filtered options β†—

Example 1 β€” only adults can be household head

Source rosterVariables
membersmember_name, age, sex

Filter:

age >= 18
Linked options after applying age >= 18
β—‹ Amina β€” 42shown
β—‹ Bilal β€” 18shown
Sana β€” 11filtered out

Example 2 β€” women of reproductive age

If sex == 2 means female:

sex == 2 && age >= 15 && age <= 49

Survey Solutions' current syntax overview uses this exact kind of linked-roster filtering example. Filter syntax β†—

The important keyword: @current

This is where linked questions become especially useful inside a roster. When you are asking a question about the current roster row but filtering potential choices from the whole roster, you need a way to distinguish:

Potential option

Variables written normallyβ€”such as age, sex, or @rowcodeβ€”refer to the roster row currently being tested as a possible answer option.

Current interview row

Prefix with @current. to refer to the person/item whose roster row you are currently interviewing, e.g. @current.age.

Think of the filter as asking: β€œFor this candidate option, should I show them to the current person?” Survey Solutions evaluates that question separately for every candidate row. Filter context β†—

Example 3 β€” choose the mother of each household member

Suppose the linked question mother sits inside the members roster. You want to show only candidates who are:

  • female;
  • not the current person;
  • at least 10 years older than the current person.
sex == 2
&& @rowcode != @current.@rowcode
&& age >= (@current.age + 10)
Current row: Sana, age 11
β—‹ Amina β€” Female, 42eligible
Bilal β€” Male, 18wrong sex / too young
Sana β€” Female, 11cannot select herself

This is essentially the pattern used in Survey Solutions' official filtered-answer-options documentation. Filtered options β†—

Example 4 β€” spouse selection

Assume sex is coded 1 = male, 2 = female. A simplified opposite-sex spouse filter could be:

@rowcode != @current.@rowcode
&& sex != @current.sex
&& age >= 15

For a real survey you would normally add your survey's own age, marital-status, relationship, and plausibility rules rather than relying on sex alone.

Example 5 β€” double screening without creating another roster

Imagine you first ask a linked multi-select:

employed_members
"Which household members are employed?"

Then you ask:

multiple_jobs
"Which employed household members have more than one job?"

The second linked question can use the earlier dynamic list and filter it again. Survey Solutions explicitly added filtering of questions linked to a list to support this type of double screening. Linked-list filtering β†—

Example 6 β€” link to non-person rosters

Linked questions are not just for household members. Suppose you collect plots:

Plot roster
Plot A Β· 0.7 ha
Plot B Β· 2.1 ha
Plot C Β· 1.3 ha
β†’
Later question
Which plot received fertilizer?

You can link to the plot-name question or to meaningful roster titles, then filter by crop, size, ownership, irrigation status, or any other relevant variables.

Linking to text, numeric, date, and roster titles

SourceWhat becomes the linked options?Example
Text question in rosterEntered text valuesMember names
Numeric question in rosterEntered numeric valuesPlot IDs
Date question in rosterEntered datesEpisode dates
Roster itselfRoster row titlesNames from a list-triggered roster

The categorical multi-select documentation explicitly lists text, numeric, date, and roster-linked sources. Categorical questions β†—

Linked question vs ordinary filtered categorical question

Linked

The options themselves are generated from earlier interview data.

Who is the head?
[Amina]
[Bilal]
[Sana]
Ordinary categorical + filter

The option list is predefined in Designer; the filter merely hides some codes.

Payment method
[Bank]
[Cash]
[Wallet]

In an ordinary categorical filter, use @optioncode to refer to the option currently being evaluated. Filtered options β†—

What gets stored? The label is not the data key

Do not think β€œAmina” is what identifies the answer in the data. Linked options are tied to the underlying source item's generated code/row identity. This is especially important if names are duplicated or a list item is later deleted. Survey Solutions' export documentation shows linked roster options exported using their generated codes. Roster export β†—

For example, if the roster contains:

row code 0 β†’ Amina
row code 1 β†’ Bilal
row code 2 β†’ Sana

and a linked question selects Sana, the stored/exported answer is associated with code 2, while the interviewer sees the human-readable label Sana.

Multi-select linked questions can make wide exports

A linked multi-select may create many export columns because Survey Solutions has to allow for the potential maximum number of source records. This can surprise you if you come from SurveyCTO and expect a compact space-separated answer string. Survey Solutions' categorical multi-select documentation describes how linked multi-select exports depend on the source roster's potential size. Multi-select export β†—

Analysis implication: decide early whether a downstream Stata/R pipeline will decode linked-question codes back to roster identities. For complex surveys, document these relationships in your codebook rather than waiting until final analysis.

Common mistakes

MistakeWhy it happensBetter approach
Linking to the wrong source questionSeveral roster questions have similar titlesPay attention to variable names in Designer's source selector.
Forgetting to exclude the current personFilter looks only at sex/ageAdd @rowcode != @current.@rowcode when self-selection is impossible.
Using @current when the question is not in a roster contextConfusing candidate and current-row logicUse @current only when you truly need the current roster occurrence.
Assuming hidden option = impossible foreverFilters are evaluated dynamicallyRemember options can change when earlier answers change.
Using names as if they were unique IDsWhat the interviewer sees feels like the stored valueDesign around roster identity/codes; duplicated labels are possible.

When I would reach for linked questions immediately

Relationships

Mother, father, spouse, household head, respondent, caregiver, decision-maker.

Eligibility subsets

Women 15–49, working-age adults, school-age children, members with disability, migrants.

Cross-module references

Plots, crops, loans, firms, transfers, assets, health episodes, jobs, schools.

Design exercise: You have roster members with member_name, age, sex, relationship, and employed.
  1. Create hh_head: one adult household member.
  2. Create working_members: select all employed adults.
  3. Inside each member row, create mother: another member who is female and at least 10 years older.
  4. Inside each member row, create spouse: another member, never the current member.
Show example filters

1. age >= 18
2. age >= 18 && employed == 1
3. sex == 2 && @rowcode != @current.@rowcode && age >= (@current.age + 10)
4. At minimum: @rowcode != @current.@rowcode, then add your survey's marital-status, age, sex, and relationship rules as appropriate.

ICM applied workshop Β· Module 8

Read familiar SurveyCTO operations as Survey Solutions design decisions

Examples below are traced to the supplied ICM Integrated Survey workbook. Source excerpts are exact cell contents, not endorsements of their logic. Target examples are teaching designs that require Designer compilation and Android/web testing; no full instrument conversion or runtime equivalence is claimed.

Selected codes, display labels, and linked member identities

SurveyCTO source cells and expressions
survey!V689 Β· info_current_all_index (calculate)
selected-at(${joined_old_new_hhmems}, index()- 1)
survey!V690 Β· info_current_all_name (calculate)
choice-label(${hh_resp}, ${info_current_all_index})
survey!V2229 Β· mem_old_rel_index (calculate)
if(selected(${icm_grp_mem_name_old}, 0), selected-at(${icm_grp_mem_name_old}, index()), selected-at(${icm_grp_mem_name_old}, index()-1))
survey!V2230 Β· mem_old_rel_lbl (calculate)
pulldata('icm_part_preloads', 'icm_part_og_name1', 'id', ${mem_old_rel_index})

choice-label returns presentation text; selected-at returns a selected token. A linked Survey Solutions answer instead identifies a roster member. Preserve that identity for relationships; do not convert it into a label and later try to recover the member by name.

Design route:
Keep respondent/member selection linked to the member roster.
Use question text substitution or supported label-display behavior for wording.
Retrieve a member attribute through the linked identity only after checking
that the selection still exists; see the linked-question examples in this module.
Test the intent: Change the name or language and verify identity remains the same. Remove or make the selected member ineligible and inspect the linked answer. Do not cast a linked identity to an ordinary category integer.

Special options: count eligible choices explicitly

SurveyCTO source cells and expressions
survey!V2243 Β· cnt_icm_grp_mem_name_0 (calculate)
if(selected(${icm_grp_mem_name}, '0'), 0, count-selected(${icm_grp_mem_name}))
survey!V2244 Β· cnt_icm_grp_mem_name (calculate)
if(selected(${icm_grp_mem_name}, 0), (${cnt_icm_grp_mem_name_0}) - 1, count-selected(${icm_grp_mem_name}))
survey!W2258 Β· repeat_mem_oth_rel_ex (begin repeat)
${cnt_icm_grp_mem_name}
survey!V2259 Β· mem_rel_index_ex (calculate)
if(selected(${icm_grp_mem_name}, 0), selected-at(${icm_grp_mem_name}, index()), selected-at(${icm_grp_mem_name}, index()-1))

When code 0 is selected, row 2243 yields 0 and row 2244 subtracts 1, yielding -1. This feeds a repeat count. Treat this as a source-review issue, not a pattern to reproduce. The selected-at offset also assumes a particular position for the special code.

Illustrative Long integer for an ordinary categorical multi-select:
icm_grp_mem_name == null ? 0 : icm_grp_mem_name.Count(code => code != 0)

Use this only after confirming 0 means a non-member option and preserving
all other exclusions. This expression does not apply to a linked roster answer.
Test the intent: Test only 0, only a real member, multiple real members, and 0 together with a member. If 0 must be exclusive, enforce that separately. Never repair a negative count with Max(0, count) without finding its cause.

Reference guides: SurveyCTO expressions; calculation timing; Survey Solutions rosters; numeric lookup tables.

Apply it to the practice questionnaire. Create phone_owners as a linked multi-select sourced from members. In Tester, confirm the choices are the two names you entered, not a fixed reusable category list.
Module 9

Assignments, sample lists, and preloading household information

This is the Survey Solutions equivalent of one of the most common SurveyCTO case-management workflows: you already have a household sample with IDs, geography, household head, address, and sometimes earlier-wave information, and you want interviewers to receive the right case without asking all of that information again.

The most important mental shift: in SurveyCTO you may let the interviewer select a caseid and then pull information from the cases dataset. In Survey Solutions, the assignment itself is usually the case. Headquarters creates one assignment per sampled household and preloads the identifying information into it. The interviewer receives that household directly on their dashboard.

SurveyCTO case management vs Survey Solutions

SurveyCTO workflowSurvey Solutions equivalent
Cases dataset contains sampled householdsAssignments represent the sample/workload
Enumerator selects a caseEnumerator receives assigned household cards after synchronization
caseid identifies the caseYour household ID is typically an identifying question, while Survey Solutions also maintains its own internal interview/assignment IDs
Preload province, municipality, barangay, head, addressUpload those values into supported questions placed in Cover; their location makes them identifying automatically
Use case data to populate other fieldsUse advanced preloading for interviewer/hidden questions and roster data
Case can be reassignedAssignment can be reassigned to another supervisor/interviewer

Concrete example: a household sample from the Philippines

Suppose your sampling team gives you this household list:

hhidprovincemunicipalitybarangayhh_headaddress_responsible_quantity
PH-001-0001CaviteDasmariΓ±asPaliparan IIIMaria SantosBlk 4 Lot 12int_ana1
PH-001-0002CaviteDasmariΓ±asPaliparan IIIJose ReyesBlk 5 Lot 8int_ana1
PH-002-0001LagunaCalambaRealLiza CruzPurok 2int_miguel1

For a fixed sample where each row is one household, this is usually the closest Survey Solutions equivalent of a SurveyCTO cases dataset. Each row becomes one assignment. The official documentation supports batch creation of assignments from a tab-delimited file, with optional _responsible and _quantity columns.

Step 1 β€” put identifying questions on the Cover page

In Designer, open the built-in Cover section and add the sample variables that should identify the household. Modern Designer has no Identifying checkbox: supported questions become identifying automatically when placed in Cover.

Illustrated Designer view β€” Cover / identifying questions
Household ID Scope: Identifying
hhid
Province Scope: Identifying
province
Municipality / City Scope: Identifying
municipality
Barangay Scope: Identifying
barangay
Household head Scope: Identifying
hh_head
Address / landmark Scope: Identifying
address

Identifying questions live on the Cover page. If Headquarters preloads their values, those values appear on the interviewer's assignment/dashboard and are locked: the interviewer can see them but cannot change them.

This is a major design choice. If you preload hh_head = "Maria Santos" as an identifying answer, the interviewer cannot simply edit that identifying answer to β€œMaria Dela Cruz.” If you want field staff to verify and correct sample information, use a deliberate confirmation pattern rather than expecting them to edit the locked identifying field.

Step 2 β€” upload the sample as assignments

1. Designer
Place hhid, geography, head name, address, etc. in Cover. Their placement makes them identifying.
↓
2. Headquarters
Survey Setup β†’ Questionnaires β†’ hover over and click the imported questionnaire row β†’ Upload assignments.
↓
3. Download the .tab template
The first row contains the identifying variable names expected by that questionnaire.
↓
4. Fill one row per sampled household
Add _responsible if one file contains cases for multiple field staff; use _quantity = 1 for one interview per sampled household.
↓
5. Upload and validate
HQ checks variable names, formats, and values before creating assignments.
↓
6. Interviewer synchronizes
The assigned households appear as cards on the Interviewer dashboard.

What the interviewer sees

Illustrated Interviewer dashboard β€” assigned household
PH-001-0001 Β· Maria SantosAssigned
Province: Cavite Municipality: DasmariΓ±as Barangay: Paliparan III Address: Blk 4 Lot 12
START NEW INTERVIEWSHOW LOCATION

This is why, for a household sample, the interviewer normally does not need to type or select a case ID and then run a lookup. The assignment already carries the household's identifying information.

Pattern A β€” sample information is authoritative: show it, do not edit it

Use this when the household ID and geography are fixed by the sampling frame and should never be changed by field staff.

Cover / Identifying:
hhid          = PH-001-0001      πŸ”’
province      = Cavite           πŸ”’
municipality  = DasmariΓ±as       πŸ”’
barangay      = Paliparan III    πŸ”’
sample_head   = Maria Santos     πŸ”’
sample_addr   = Blk 4 Lot 12     πŸ”’

The interviewer uses these values to find the correct household. No repeated questions are needed.

Pattern B β€” show sample information and ask the interviewer to confirm it

This is often the best pattern for your example. Keep the original sample values locked, then ask separate interviewer questions to verify that they have reached the intended household.

Illustrated interview screen β€” confirming the assigned household
Sample household
HH ID: PH-001-0001
Head: Maria Santos
Barangay: Paliparan III
Address: Blk 4 Lot 12
Are you at the household described above?
● Yes β—‹ No
Is Maria Santos still the household head?
β—‹ Yes ● No
Current household head
Maria Dela Cruz

For example:

Identifying / locked:
sample_head

Interviewer questions:
correct_household
head_still_correct
current_head

Enable current_head when:
head_still_correct == 2

This preserves what the sample said and separately records what field staff found. That is usually analytically cleaner than overwriting the sample value.

Pattern C β€” let the interviewer correct the value directly

If a value truly should be editable, do not preload it as an identifying question. Instead, preload it into an interviewer-scope question using advanced preloading, or keep the original in a hidden field and collect an editable current value separately.

Preserve original + correction
hidden:
sample_phone

interviewer:
phone_confirmed
current_phone

Best when you need to know both the original frame value and the field correction.

Editable preloaded answer
interviewer question:
phone_number

preloaded:
0917...

The preloaded answer appears in the interview and can generally be edited, subject to question type and any special protection rules.

Identifying vs hidden vs interviewer scope

What you needBest Survey Solutions scopeBehavior
Show household/sample information on interviewer dashboardIdentifyingVisible on assignment cards; preloaded values are locked
Keep original frame value for logic but do not show itHiddenCan be preloaded; interviewer does not answer it
Show a preloaded value and allow field correctionInterviewerPreloaded in advanced mode; interviewer can interact with it unless protected by a specific rule
Ask supervisor-only verification after completionSupervisorHidden from interviewer/respondent; available during review

What file format does Survey Solutions actually accept?

For batch assignment uploads and preloading, Headquarters expects a tab-delimited text file: .tab. It does not directly ingest your working .xlsx, .csv, or .dta sample file for this workflow. The official documentation explicitly says that assignment uploads rely on an external tab-delimited .tab file. Batch assignments β†—
Your source fileCan HQ upload it directly as an assignment/preload file?What you do
.xlsx / ExcelNoSave/export the required sheet as tab-delimited text, then use the resulting .tab file.
.csvNo for this upload workflowConvert/export it to tab-delimited text.
.dta / StataNoUse Stata to export the sample in tab-delimited format.
.tabYesUpload directly after checking variable names and formats.
Survey Solutions does not do that conversion for you. The conversion happens in whatever software you already use to manage the sampleβ€”Excel, Stata, R, Python, etc. Survey Solutions then validates the uploaded .tab file against the questionnaire. The Survey Solutions team explicitly notes that Stata, SPSS, R, Excel and other packages can prepare assignment files as tab-delimited output. Assignment files β†—

So what should your workflow look like?

1. Keep your master sample in the format you normally use
master_sample.xlsx, sample.dta, database table, etc.
↓
2. Prepare only the fields Survey Solutions needs
For example hhid, province, municipality, barangay, sample head, address, _responsible, _quantity.
↓
3. Export as tab-delimited text
Create something like household_assignments.tab.
↓
4. Upload the .tab file in Headquarters
HQ validates column names and values against the questionnaire.
↓
5. Assignments are created
The identifying values are copied into each assignment; HQ is not maintaining a live connection to your original Excel/Stata file.

Excel example

Your working file might be:

master_sample.xlsx
Sheet: SurveySample

hhid | province | municipality | barangay | hh_head | address | interviewer

Before upload, create/export:

household_assignments.tab

hhid	province	municipality	barangay	hh_head	address	_responsible	_quantity
PH001	Cavite	Dasmarinas	Paliparan III	Maria Santos	Blk 4 Lot 12	int_ana	1

In Excel, the practical equivalent is saving a worksheet as Text (Tab delimited). If Excel gives the file a .txt extension, the content is still tab-delimited; for Survey Solutions workflows, teams commonly rename/use the expected .tab extension after confirming the content is tab-separated.

Stata example

If your sample is maintained as sample.dta, you can create the upload file from Stata rather than manually passing through Excel:

use "sample.dta", clear

keep hhid province municipality barangay hh_head address interviewer

rename interviewer _responsible
gen _quantity = 1

export delimited using "household_assignments.tab", ///
    delimiter(tab) replace

The important point is the output format: tab-delimited text. Survey Solutions' own guidance notes that statistical packages can generate these files directly. Assignment files β†—

CSV example

If the sampling team gives you sample.csv, you can open it in Excel/Stata/R/Python and export a tab-delimited version. Headquarters does not need to know that the source was CSVβ€”the only thing it sees is the final .tab upload.

Do not confuse import and export formats. Survey Solutions can export collected survey data in formats such as Stata and SPSS, and some reports can be exported to XLSX/CSV/TAB. That does not mean those same formats are accepted for assignment/preload uploads. For assignment/preload input, the documented format is tab-delimited text. Preload formats β†—

Why Survey Solutions uses .tab

The platform moved preloading, batch operations, and long lists to tab-delimited text specifically because it handles scripts and punctuation more reliably across languages. Preload files β†—

Basic sample preload: identifying data only

For a normal cross-sectional survey with a known sample, this may be all you need:

hhid	province	municipality	barangay	hh_head	address	_responsible	_quantity
PH-001-0001	Cavite	Dasmarinas	Paliparan III	Maria Santos	Blk 4 Lot 12	int_ana	1
PH-001-0002	Cavite	Dasmarinas	Paliparan III	Jose Reyes	Blk 5 Lot 8	int_ana	1
One row = one assignment. With _quantity = 1, each sampled household can generate one interview. If you assign the row to a supervisor instead of an interviewer, the supervisor can distribute it to someone on their team.

What if the sample is defined at EA/barangay level rather than household level?

Assignments can also represent a quota or area workload. For example:

province	Cavite
municipality	Dasmarinas
barangay	Paliparan III
_responsible	sup_rosa
_quantity	12

This means: collect up to 12 interviews under this assignment. Household-specific fields such as exact address and household head can be left blank and completed by interviewers. Survey Solutions explicitly supports this less-specific assignment model as well as one-assignment-per-household samples.

Advanced preloading: more than just the Cover page

Suppose this is a panel survey and you know much more from the previous wave:

  • household demographics;
  • member names, ages, and sex;
  • plots operated last wave;
  • previous phone numbers;
  • baseline treatment group;
  • previous-wave IDs.

Survey Solutions can preload data into questionnaire questions and rostersβ€”not only identifying questions. Headquarters provides an advanced preload template as a ZIP containing one tab-delimited file for each hierarchical level.

Illustrated advanced-preload file structure
preload.zip
β”œβ”€ Household.tab
β”‚    Id | hhid | province | sample_head | treatment | ...
β”‚
β”œβ”€ members.tab
β”‚    Id | ParentId | member_name | age | sex | ...
β”‚
└─ plots.tab
     Id | ParentId | plot_name | area | ...

Id and ParentId link lower-level records back to the household. Nested rosters can require additional parent identifiers. This is the mechanism you would use to preload last-wave household members or other repeated records.

Panel-survey example: preload last-wave members

IdParentIdmember_nameagesex
01Maria Santos462
11Paolo Santos221
21Ana Santos172

The current interview can start with these roster rows already populated. You can then ask whether each member is still in the household, update allowed information, and add new members.

Protecting preloaded roster triggers

For panel surveys, you may want to prevent an interviewer from deleting last-wave roster members while still allowing new members to be added. Survey Solutions provides protection for preloaded trigger questions of certain types:

  • numeric trigger: interviewer may increase but not reduce the value;
  • text-list trigger: interviewer may append items but not remove preloaded items;
  • multi-select trigger: interviewer may add selections but not remove protected preselected ones;
  • yes/no multi-select: preloaded Yes/No selections can be protected.
This protection is not a general β€œlock any question” setting. It applies specifically to supported trigger-question types and is configured through advanced preloading using a protected__variables.tab file.

Example protected__variables.tab

variable__name
hhmembers
n_plots
crops

Household location and GPS

If you already have target coordinates, they can be preloaded for navigation. Survey Solutions supports showing the assigned location to the interviewer from the assignment card so they can open it in an external navigation app. If coordinates are collected during the interview instead, those can be used later for verification.

Illustrated location workflow
PH-001-0001 Β· Maria SantosTarget location loaded
Barangay: Paliparan IIICoordinates: available
SHOW LOCATION
↓
Interviewer collects/validates current location if required by the questionnaire

Recommended pattern for your exact use case

If you receive a fixed household sample with province, municipality, barangay, household head, address and household ID, I would structure it like this:

  1. Make hhid, province, municipality, barangay, sample household head and sample address identifying questions.
  2. Batch upload one assignment per household with _quantity = 1.
  3. Use _responsible if the sample is already allocated to supervisors/interviewers.
  4. Do not ask the geography and sample ID again. Show them as locked reference information.
  5. Add an interviewer question such as correct_household: β€œAre you at the household described on the Cover page?”
  6. For fields that can legitimately changeβ€”household head, phone, address detailβ€”keep the sample value and separately ask whether it is still correct.
  7. Enable correction questions only when the interviewer says the preloaded value is no longer correct.
  8. If you have previous-wave roster data, use advanced ZIP preloading and consider protecting the roster trigger so prior members cannot simply disappear.

A full example questionnaire pattern

Cover (Identifying / locked if preloaded)
β”œβ”€ hhid
β”œβ”€ province
β”œβ”€ municipality
β”œβ”€ barangay
β”œβ”€ sample_head
└─ sample_address

Verification (Interviewer)
β”œβ”€ correct_household
β”œβ”€ head_still_correct
β”œβ”€ current_head
β”‚    enable when: head_still_correct == 2
β”œβ”€ address_still_correct
└─ corrected_address
     enable when: address_still_correct == 2

Household roster
└─ members
   β”œβ”€ name
   β”œβ”€ age
   β”œβ”€ sex
   └─ ...

Why this is better than asking everything again

Less respondent burden

You do not re-ask fixed sample-frame information merely to reconstruct what the project already knows.

Lower targeting error

The interviewer can see the intended household and verify they are at the right place before proceeding.

Cleaner audit trail

You retain the original sample-frame value separately from any corrected/current information collected in the field.

Common mistakes

MistakeWhy it causes troubleBetter approach
Make every preloaded value IdentifyingPreloaded identifying answers are lockedUse identifying only for stable case-reference fields; use separate confirmation/correction questions for changeable information
Ask interviewer to select a case ID from thousands of householdsCreates avoidable lookup and selection riskAssign each sampled household directly
Overwrite the frame value when correcting itYou lose the distinction between sample-frame and field-observed informationPreserve original + collect current/corrected value separately
Use one assignment per EA when the sample is already household-specificWeakens household-level control and case trackingUse one assignment per household with quantity 1
Preload a roster without considering deletionPrior-wave members can be removedUse trigger protection when appropriate and collect status/change information explicitly
Design exercise: You receive 2,500 sampled households with hhid, province, municipality, barangay, household head, phone number, address, GPS, and assigned interviewer.
  1. Which variables would you put on the Cover page as Identifying?
  2. Which values might you preserve as original sample values but verify separately?
  3. What should _quantity be if each household should be interviewed once?
  4. Would you make the enumerator choose hhid from a 2,500-item list?
Show suggested answer

1. At minimum hhid plus the stable geography/address information needed to find the household; household head can also be identifying if you want the sample value locked and visible. 2. Head name, phone, detailed address and possibly GPS are good candidates for β€œsample value + confirmation/current value” patterns because they may change or be imperfect. 3. 1. 4. Noβ€”the assignment already represents the sampled household and should be sent directly to the responsible fieldworker/team.

ICM applied workshop Β· Module 9

Read familiar SurveyCTO operations as Survey Solutions design decisions

Examples below are traced to the supplied ICM Integrated Survey workbook. Source excerpts are exact cell contents, not endorsements of their logic. Target examples are teaching designs that require Designer compilation and Android/web testing; no full instrument conversion or runtime equivalence is claimed.

Separate preloaded attributes from categorical labels

SurveyCTO source cells and expressions
survey!V29 Β· icm_part_name (calculate)
pulldata('icm_part_preloads', 'icm_part_og_name1', 'id',${caseid})
survey!V41 Β· pull_hh_head (calculate)
pulldata('cases_icm', 'hh_head', 'id',${caseid})
survey!V47 Β· calc_hh_mem_name1 (calculate)
pulldata('psps_baseline', 'calc_hh_mem_name1', 'caseid', ${caseid})
survey!V886 Β· fd_cat (calculate)
pulldata('preload_fd_cat', 'fd_cat_name', 'id_key', index())
survey!V895 Β· fd_cons (calculate)
pulldata('preload_fd_cat', 'fd_cons_name', 'cons',${fd_cons_val})

The same pulldata function is used for household attributes, baseline names, and food labels. These require different target designs. Questionnaire-local worksheets are evidence of reference data, but do not prove which attached CSV or server dataset was deployed.

Household attributes and baseline members β†’ assignment/roster preloading.
Static food codes and translated labels β†’ categories and translations.
Static numeric parameters β†’ numeric lookup table if representable.
Mutable cases/status publishing β†’ separate operational integration.
Test the intent: Check key uniqueness, leading zeros, missing matches and the exact deployed dataset version. A numeric lookup table cannot hold arbitrary text names and multilingual labels. Reconcile the source-member ID with target roster identity in the preload/export crosswalk.

Reference guides: SurveyCTO expressions; calculation timing; Survey Solutions rosters; numeric lookup tables.

Apply it to the practice questionnaire. Compare every header in the supplied assignment TAB with a saved Cover variable. Check that _responsible is a login and assigned_interviewer_name is the full display name before a synthetic upload.
Module 10

Field workflow: HQ β†’ Supervisor β†’ Interviewer

Survey Solutions treats supervision as a first-class workflow instead of an optional layer around submissions.

HQ creates/imports questionnaire
        ↓
HQ creates assignments
        ↓
Supervisor distributes work
        ↓
Interviewer synchronizes
        ↓
Interviewer conducts interview
        ↓
Complete
        ↓
Supervisor review
   ↙ reject      approve β†˜
Interviewer          HQ review / final workflow

Think in states, not files

A field problem may be caused by responsibility, status, assignment availability, device synchronization, or questionnaire versionβ€”not just β€œthe form is missing.”

The interview status lifecycle

The status tells you who must act next. Dashboards may display the labels with spaces while APIs and exports may use compact names.

Status
Who acts next?
Operational meaning
Supervisor Assigned
Supervisor
HQ has sent the assignment to a team; the supervisor allocates it to an interviewer.
Interviewer Assigned
Interviewer
The interviewer synchronizes, conducts or continues the interview, and eventually completes it.
Completed
Supervisor
The interviewer has submitted the interview for first-level review.
Rejected by Supervisor
Interviewer
The interview returns for correction or explanation, normally with comments identifying what must be addressed.
Approved by Supervisor
Headquarters
The supervisor accepts field-level quality and sends the interview to central review.
Rejected by Headquarters
Supervisor
HQ returns the interview to the supervisor, who resolves it or reallocates it to an interviewer.
Approved by Headquarters
No routine field action
The interview has passed final review. Any later reversal must follow authorized HQ controls and be auditable.

Official survey workflow β†—

How review should work in practice

Supervisor review
  • Filter for completed interviews.
  • Review invalid, unanswered, flagged and supervisor-scope questions.
  • Add question-level comments that state the issue and requested correction.
  • Reject when field follow-up is needed; approve when the interview is coherent and sufficiently documented.
Headquarters review
  • Review interviews approved by supervisors.
  • Apply central consistency, sample and protocol checks.
  • Reject to the supervisor rather than bypassing the field chain.
  • Approve only when the interview is ready for the project's accepted-data workflow.
A rejection is a controlled work queue, not a deletion. Comments should identify the exact question or issue, the expected action, and whether a respondent revisit is required. Supervisors must monitor the interview through correction, resubmission and review.

Reassignment and absence

Responsibility and interview status answer different questions. Responsibility says who currently owns the work; status says what stage it has reached. If an interviewer becomes unavailable, reassign only after checking whether the interview exists on a device, has synchronized changes, or is already awaiting review. Coordinate a final synchronization whenever possible before moving responsibility.

Safe reassignment checklist
  1. Confirm the assignment and interview IDs.
  2. Confirm the questionnaire version.
  3. Check current status and responsible user.
  4. Ask the original interviewer to synchronize if the device contains newer work.
  5. Reassign through the appropriate supervisor/HQ control.
  6. Have the new interviewer synchronize and verify the case before travel.
  7. Record the reason in the fieldwork issue log.
Troubleshooting drill: Known facts: household ID 102, assignment A-102, interviewer int_maria, questionnaire HH_v5. Your check order should explicitly cover: server/account β†’ questionnaire version β†’ assignment responsibility β†’ interview status β†’ last successful synchronization β†’ pending rejected work β†’ device state.
Module 11

Interviewer App vs Web Interviewer

This is the closest Survey Solutions analogue to the distinction between SurveyCTO Collect on Android and conducting interviews through a browser. The tablet workflow is built around synchronization; the web workflow works directly against the server.

First: interviewer accounts are not email logins

Yes, Survey Solutions accepts non-email usernames. In fact, the user login is normally a short account name such as enum001, ana_01, or supnorth. Email is optional profile information, not the login itself. Current batch-user documentation says login names must be 3–15 characters and can contain Latin letters, digits, and underscores.
Password requirements are not optional: use at least 10 characters, including at least one uppercase English letter, one lowercase English letter, and one digit. The password is case-sensitive. Apply the same rule whether the account is created manually or through UPLOAD USERS.
FieldExampleRequired?
Loginenum001Yes
PasswordFieldteam2026AYes
RoleInterviewerYes
Supervisorsup01Yes for interviewer account
Full nameAna ReyesOptional
Email[email protected]Optional
Phone639171234567Optional

The server can create users manually or in batch. Current Survey Solutions supports creating up to 10,000 users from one tab-delimited file containing login, password, role, andβ€”when relevantβ€”supervisor. Official batch-user documentation β†—

Lower part of the PDS create-user page showing optional full name, email, phone number and Create button.
Profile data are optional. Full name, email and phone help managers identify or contact the account holder, but they do not replace the login and are not automatically available as questionnaire substitutions.
PDS batch user upload page with workspace selector, template download and Upload tab file button.
Batch creation uses its own TAB template. Select the workspace, download the current user-upload template, populate it, and upload the tab-delimited file. This is different from the assignment-upload template.

How the tablet is configured

The Interviewer App needs three things the first time it connects:

  1. Server address β€” the Survey Solutions server URL / synchronization point.
  2. Interviewer login β€” e.g. enum001.
  3. Password β€” the password assigned to that interviewer account.
HQ server
https://survey.example.org

User: enum001
β†’
Android Interviewer App
Server URL
Username
Password
β†’
Synchronize
Receive questionnaire + assignments
Send completed interviews

Find the Interviewer App login QR code

The Users β†’ enum_01 β†’ Manage account page is the wrong screen for this QR code: it edits contact details, password and account settings. The login QR is on the workspace interviewer profile. In the PDS interface shown below:

  1. Enter Default Workspace and open Reports β†’ Devices/Interviewers.
  2. Find the sup_train team and click the interviewer login, such as enum_01.
  3. On the enum_01 interviewer profile, scan the QR code at the upper right from the Interviewer App’s first-login screen.
  4. The scan fills the server address and interviewer login. Enter the interviewer’s password separately, sign in, then synchronize.
Default Workspace Reports menu with Devices/Interviewers highlighted.
1. Open the workspace report. The route begins under Reports, not the administrator’s Users page.
Devices/Interviewers report listing the sup_train team and clickable enum_01 interviewer login.
2. Select the interviewer. Click enum_01 under the sup_train team to open the interviewer profile.
Interviewer profile for enum_01 with a login setup QR code at upper right.
3. Scan this account QR. It belongs to enum_01 and pre-fills the server and login. It does not contain a password.
Do not confuse the QR codes. The QR on the Get Interviewer App page is for the app/server address; the QR on an interviewer profile also supplies that interviewer’s login. A QR shown under Two factor authentication is for an authenticator app, not for Interviewer App login. The interviewer can also sign in to the server in a browser and view their own profile. Official QR setup guide β†— Server menu map β†—
SurveyCTO analogy: both platforms exchange server-side work with the interviewer device. In SurveyCTO Collect, Get Blank Form downloads new or updated form versions, case-management users refresh or automatically update their case list, and Send Finalized Form uploads completed submissions. Survey Solutions groups the comparable receive-and-send cycle under one explicit Synchronize action: it receives questionnaire versions and assignments and sends interview updates and completed interviews. The practical difference is mainly how the controls and workflow are packagedβ€”not that one platform synchronizes while the other does not.
Operational needSurveyCTO CollectSurvey Solutions Interviewer
Receive a new or updated questionnaireGet Blank Form, or configured automatic form updatesSynchronize
Receive updated assigned cases/workManage Cases β†’ Refresh, or configured automatic case updatesSynchronize receives assignments
Send completed data to the serverSend Finalized Form, or configured automatic sendingSynchronize sends completed interviews and other interview changes

The labels differ, but both require the device to contact the server. SurveyCTO exposes separate retrieve/send controls and can automate them; Survey Solutions presents one named synchronization cycle. SurveyCTO Collect workflow β†— SurveyCTO case refresh β†—

What synchronization actually does

Illustrated Interviewer dashboard after sync
AssignmentsSYNCHRONIZE
PH-001-0001 Β· Maria Santos New
Barangay: Paliparan IIIAssigned to: enum001
PH-001-0002 Β· Jose Reyes Started
Barangay: Paliparan IIIProgress: 44%
Last synchronization: 2 new assignments received Β· 1 completed interview sent

Official documentation describes synchronization as a two-way exchange: the interviewer receives assignments/instruments and sends completed work back. Internet is needed for synchronization, but not for conducting ordinary interviews once the data are on the tablet. Synchronization documentation β†—

Read the tablet dashboard as four work queues

The dashboard is the interviewer's operational inbox. Each tab answers a different question, and a case can only be acted on correctly when the interviewer understands which queue it is in.

Dashboard queueWhat it containsCorrect next action
Create NewAssignments for which another interview may be created. Cards show the assignment number, questionnaire version and identifying information.Confirm the case identifiers before selecting Start New Interview. Fill any identifying fields that HQ deliberately left open.
StartedInterviews created on this device but not yet marked complete.Select Open and resume. Previously recorded answers remain available.
CompletedInterviews marked complete on the device. They remain here until synchronization transfers them to the server.Reopen if a correction is needed before transmission; otherwise synchronize and confirm the upload result.
RejectedInterviews returned by the supervisor with issues, questions or requested corrections.Open the case, follow the question links and comments, make or explain each correction, complete again, then synchronize.
Completed is not the same as synchronized. Completing changes the interview's device-side workflow state. Synchronization is the separate exchange that sends it to the server and receives new, reassigned or rejected work.

Interviewer dashboard and workload β†—

A defensible interview close-out routine

Train interviewers to use the same sequence before every submission. This reduces avoidable rejections and makes genuine exceptions easier for supervisors to interpret.

1. Confirm the case
Check the assignment number and identifying information before final review.
↓
2. Open the completion screen
Review the counts and links for unanswered questions and answers with errors.
↓
3. Resolve each item
Correct mistakes. If an unusual answer is confirmed, retain it and leave a concise question-level comment explaining how it was verified.
↓
4. Review returned comments
For rejected interviews, address every supervisor or HQ issue instead of merely opening and completing the case again.
↓
5. Complete, synchronize and reconcile
After completion, synchronize and read the result: interviews uploaded, new assignments received, rejected interviews returned and work removed after reassignment.
A red validation message is a review prompt, not proof that the respondent is wrong. The interviewer should first check entry and probing. If the response remains correct, preserve it and explain the verified exception in a comment. Validation rules can flag an answer without blocking submission; critical rules and questions control whether completion is blocked or requires acknowledgement.

Actions that need extra care

Deleting a list item

If a list creates roster rows, removing a list item can remove the associated person's or item's roster data. Verify the intended row before deleting it.

Discarding an interview

Discard is destructive and the interview cannot be recovered from the device. It should follow an explicit project protocol rather than serve as routine troubleshooting.

Recording GPS again

A new GPS reading replaces the earlier reading. Re-record only when the first fix is inadequate and allow enough time for the accuracy to improve.

End-of-day security: after confirming the expected synchronization results, sign out whenever the device will be unattended. A screen lock complements the app login; it does not replace account discipline.

Question-level comments β†— Β· Question types and list behavior β†— Β· Validation and critical checks β†—

Typical field workflow

HQ creates user
enum001, role Interviewer, attached to sup01.
↓
HQ assigns cases
Households assigned to enum001 or to the supervisor/team.
↓
Enumerator signs into Android app
Server URL + login + password, or QR code + password.
↓
Synchronize
Questionnaires and assignments download to the tablet.
↓
Interview offline
Complete household visits without continuous internet.
↓
Synchronize again
Completed interviews go back to the server and new/rejected work comes down.

Password behavior worth knowing

If an interviewer’s password was assigned by someone else, current Survey Solutions can require the interviewer to change it on first login. Interviewers can change their own password, but the system does not reveal existing passwords in clear text later; forgotten passwords are reset. Password documentation β†—

Tablet Interviewer vs Web Interviewer

FeatureAndroid Interviewer AppWeb Interviewer
DeviceAndroid tablet/phoneAny supported browser
Internet needed during interview?No; mainly needed for synchronizationYes; continuous connection required
Where interview lives while being filled?Primarily on device until synchronizationOn server as work proceeds
Assignments/dashboardDownloaded during syncRead directly from server
Best fitFace-to-face CAPI, low-connectivity fieldworkCATI, office-based interviewing, connected devices
Question-type differencesSome web table/matrix presentation falls back to standard tablet presentationSome device-dependent question types have limitations
Web Interviewer is not the same thing as a public self-administered web survey. A Web Interviewer is still a logged-in interviewer account working from the server. Survey Solutions also has a separate CAWI/web-mode workflow for respondent self-completion. Web interviewing β†—

Web Interviewer workflow

HQ server
assignment β†’ enum001
β†’
Browser
open server URL
login as enum001
β†’
Interview live on server
no synchronization cycle needed

Which should you choose?

Household survey in the field

Default to Android Interviewer, especially where connectivity is intermittent.

Phone survey / call center

Web Interviewer is often convenient because interviews are immediately visible on the server.

Mixed operation

Survey Solutions can support both, but test question types and workflows in each mode before fieldwork.

Setup check: HQ creates interviewer ana_01. What does Ana need on a fresh Android tablet?
Show answer

The Interviewer App, the Survey Solutions server URL/synchronization point, her login ana_01, and her password. A QR code can prefill the server URL and login, but not the password. She then signs in and synchronizes to receive her questionnaire and assignments.

Apply it to the practice questionnaire. Sync one synthetic assignment to Interviewer or Web Interviewer. Verify the preloaded display name in consent, complete a practice case, and sync it back.
Module 12

Exports, roster files, paradata and QA

This is another major mental shift from SurveyCTO: Survey Solutions exports data by level of observation. The household-level file is separate from each roster-level file, which means roster data are naturally long rather than flattened across the household record.

Which export formats are available?

Main survey data can be exported in three core formats: Stata (.dta), SPSS (.sav), and tab-delimited text (.tab). Survey Solutions does not provide a native XLSX main-data export and does not use CSV as its primary main-survey export format. If you need CSV/XLSX, convert the tabular or statistical export afterward.
Illustrated Headquarters β€” Data Export
Household Survey Β· Version 3Status: Completed
ExportFormatAction
Main survey dataStata .dtaGENERATE
Main survey dataSPSS .savGENERATE
Main survey dataTabular .tabGENERATE
Binary dataimages/audio/filesGENERATE
Paradataparadata.tab + metadataGENERATE

Official Survey Solutions guidance explicitly recommends downloading all main-data formats when archiving a project. Data export documentation β†—

Wide vs long: Survey Solutions is hierarchical by default

Household level β€” one row per interview
household.dta

interview__id
hhid
province
hhsize
income
...
Roster level β€” one row per roster item
members.dta

interview__id
members__id
member_name
age
sex
...
So the answer to β€œdoes it export repeats wide like SurveyCTO?” is generally: no. Roster questions are exported to their own roster-level file, with one row per roster occurrence. Survey Solutions does not normally create age_1 age_2 age_3 ... for questions inside the roster. The source question that created the roster can still appear at household level in a wide form, depending on roster type.

Concrete example

Suppose your questionnaire contains:

Household
β”œβ”€ hhid
β”œβ”€ province
β”œβ”€ hhsize
└─ members
   β”œβ”€ member_name
   β”œβ”€ age
   └─ sex

With household PH001 containing three members, the export looks conceptually like:

Household file
interview__idhhidprovincehhsize
abc123...PH001Cavite3
members file
interview__idmembers__idmember_nameagesex
abc123...0Amina422
abc123...1Bilal181
abc123...2Sana112

The household identifier appears in every level needed for merging. Survey Solutions uses stable system identifiers such as interview__id and roster-specific identifiers based on the roster variable name, such as members__id. The roster variable-name limit historically reflects Stata's variable-name length constraints. Variable names β†—

What happens to the roster source question?

The source question that created the roster is exported at the parent level:

Roster sourceParent-level exportRoster-level export
Numeric rosterOne variable, e.g. hhsize = 3Three member rows
List rosterSource list can generate several columns up to its configured maximumOne row per list item
Multi-select rosterSource multi-select exports its answer-option columnsOne row per selected option
Fixed rosterNo separate source questionOne row per fixed roster item

This is why the export is not simply β€œall long” or β€œall wide.” The source question can have a wide representation at the parent level, while questions inside the roster are stored in a separate long roster file. Official roster-export anatomy β†—

SurveyCTO comparison

FeatureSurveyCTOSurvey Solutions
Repeat exportCan work with wide naming patterns such as repeated-instance columns depending on export/workflowRoster-level files are the native structure: one row per occurrence
Typical repeated variable namesMay appear as instance-specific names such as age_1, age_2, etc. in a wide representationage remains age in the roster file; row identity is carried by members__id
Main case identifierOften your key + SurveyCTO system identifiersinterview__id plus your own identifying variables
Merge key for rosterDepends on export designinterview__id + roster identifier(s)

Nested rosters

Nested rosters create additional levels/files. For example:

Household β”œβ”€ household.dta β”œβ”€ members.dta └─ jobs.dta each job belongs to a member each member belongs to an interview

Survey Solutions includes the identifiers required to link each level back to its parent. The general rule is: one export file per level of observation.

Questionnaire variable name controls the main filename

Designer distinguishes the human-readable questionnaire name from the questionnaire variable. That variable is used to name the main data file during export. For example:

Questionnaire name:
Philippines Household Remittance Survey 2026

Questionnaire variable:
ph_remit

Export:
ph_remit.dta
members.dta
transfers.dta
...

Survey Solutions introduced this so exported filenames remain stable and language-independent. Questionnaire variable β†—

System-generated QA files come with the export

Each export archive also contains system-generated data files in the same format as your main export. For a Stata export, for example, these QA files are also Stata files:

export archive β”œβ”€ ph_remit.dta β”œβ”€ members.dta β”œβ”€ transfers.dta β”œβ”€ assignment__actions.dta β”œβ”€ interview__actions.dta β”œβ”€ interview__comments.dta β”œβ”€ interview__diagnostics.dta β”œβ”€ interview__errors.dta └─ export__readme.txt

These files document operational events such as assignment creation/reassignment, interview status changes, comments, diagnostics, and validation errors. System-generated export files β†—

Paradata: a separate event-level dataset

Survey Solutions automatically produces paradata. Each row is an event rather than a respondent or roster record.

interview__idordereventresponsibletimestamp_utcparameters
abc123...1SupervisorAssignedsup0108:01:12...
abc123...2AnswerSetenum00108:13:44age||42||...
abc123...3Completedenum00109:04:10...

The paradata archive contains paradata.tab, a Stata import script paradata.do, a human-readable readme, and machine-readable JSON metadata. Survey Solutions also supports a reduced paradata export that omits some high-volume enable/disable/validation events. Paradata format β†—

What paradata can tell you

Who changed what?

Answer changes, reassignment, approvals, rejections, comments, recalculated variables.

When?

UTC timestamp plus time-zone offset, event order, completion and review timing.

Where in a roster?

Roster addresses contain row codes, so an event can be tied to a particular member/job/plot occurrence.

Useful QA patterns

  • Interview duration: derive active timing from paradata/diagnostics rather than only start/end timestamps.
  • Back-check suspicious edits: inspect repeated answer changes or late corrections.
  • Supervisor turnaround: use interview/assignment action files to measure time from completion β†’ review β†’ approval/rejection.
  • Enumerator monitoring: compare interview counts, duration distributions, error frequency, GPS or audit indicators where applicable.
  • Questionnaire debugging: use interview__errors and paradata events to locate recurring validation problems.

If you really need a wide household file

Survey Solutions does not natively flatten roster questions into age_1 age_2 age_3 .... If a downstream deliverable requires that structure, reshape it yourself after export.

Native roster file
hhid  members__id  age
PH1   0            42
PH1   1            18
PH1   2            11
After your own reshape
hhid  age_0  age_1  age_2
PH1   42     18     11
I would generally keep roster data long. Long roster files are safer for households with varying roster sizes and nested rosters. Wide reshaping is best treated as a downstream reporting requirement, not your primary storage structure.

Export workflow

1. Select questionnaire version
Exports are version-specific.
↓
2. Select interview status/range
For example Completed or Approved by Headquarters.
↓
3. Generate Stata / SPSS / Tabular archive
↓
4. Download ZIP
Main file + roster files + system-generated QA files + metadata.
↓
5. Export paradata separately
Full or reduced event set.
Data-structure check: Household PH1 has three members. Where would you expect hhid, age, and the member row identifier?
Show answer

hhid appears in the household-level file. age appears in the members roster-level file, one row per member. Each roster row carries a roster identifier such as members__id, and the roster file also carries the interview identifier needed to merge it back to the household.

Applied QA lab: build a daily fieldwork review

Use the main interview file, interview__actions, interview__errors, and paradata to produce one row per interview with:

  • responsible interviewer and supervisor;
  • first answer time, completion time, and an active-duration proxy;
  • number of validation errors and answer changes;
  • completion-to-supervisor-review time;
  • rejection count and latest status;
  • GPS or audit indicators required by the study protocol.
* Illustrative Stata workflow β€” adapt names to the exported metadata
use interview__actions.dta, clear
sort interview__id date
by interview__id: egen first_complete = min(cond(action=="Completed", date, .))
by interview__id: egen first_review = min(cond(inlist(action,"ApprovedBySupervisor","RejectedBySupervisor"), date, .))
gen review_hours = (first_review-first_complete)/(60*60*1000)

* Flag for review; do not automatically accuse an interviewer
gen rapid_review = review_hours < 0.25 if !missing(review_hours)
Interpret flags as prompts for investigation. Short duration, many edits, unusual GPS, or repeated rejection can have legitimate explanations. Combine indicators, inspect the interview, and document follow-up.
Apply it to the practice questionnaire. Export your synthetic case and identify the parent household record and its members roster rows. Check that the case ID links them.
Module 13

REST API: connecting Survey Solutions to other surveys and external data systems

Survey Solutions' REST API is the main bridge when you need automation that goes beyond the standard Designer β†’ Headquarters β†’ Interviewer workflow. This is where you can build cross-questionnaire pipelines, create assignments programmatically, automate exports, monitor interviews, and connect Survey Solutions to an external database or statistical system.

SurveyCTO translation: if SurveyCTO server datasets and publishing feel like β€œlive shared server-side data,” Survey Solutions takes a different approach. There is no built-in questionnaire-to-questionnaire pulldata() across live surveys. Instead, an external process can read data from Questionnaire A or another database, transform it, and use the Survey Solutions API to create/update work for Questionnaire B.

What the API is

An API, or application programming interface, is a controlled way for one computer program to ask another computer program for information or to perform an allowed action. When you use Headquarters yourself, you click buttons such as Create assignment or Export. With an API, a script sends the equivalent structured request to the server and receives a structured response that it can record and process automatically.

Think of a restaurant service counter. The kitchen is the Survey Solutions server and its database. The published menu is the API documentation: it lists what may be requested and what information each request needs. Your script is the customer placing an order. Authentication is the staff card proving who is allowed to order; the endpoint is the correct counter/address; the request is the order slip; and the response is the meal plus a receipt saying whether the request succeeded. You use the counter rather than walking into the kitchen and changing its shelves directlyβ€”just as an integration should use the API rather than editing Survey Solutions' internal database.

REST is one common style for that conversation. It combines a server address with an action word. A GET request normally asks for information, a POST request normally creates or starts something, and a PATCH request changes part of an existing resource. Data commonly travels as JSON, a structured text format made of named fields and values. The server returns a status code and usually a response body: for example, success with the new assignment ID, or an error explaining which required field was missing.

API termPlain-language meaningSurvey Solutions example
ClientThe software making the requestYour Python, R, PowerShell, or integration-service script
ServerThe system receiving and processing the requestYour Survey Solutions Headquarters server
EndpointThe published address for one kind of operation/api/v1/assignments
MethodThe requested actionGET, POST, or PATCH
Request bodyThe structured details sent to the serverQuestionnaire ID, responsible interviewer, quantity, and identifying answers
ResponseThe server's resultA status plus the created assignment ID or an error message
AuthenticationProof of which system account is callingDedicated API username/password or bearer token

You do not need to become a software engineer to follow this module. Read each example as a workflow: who is asking β†’ which operation is requested β†’ what data is sent β†’ what the server returns β†’ what should happen next. For a gentle introduction, see MDN's overview of HTTP β†— and MDN's REST glossary β†—. Then use the Survey Solutions API overview β†— and your own server's Swagger page for the exact operations and fields.

Every Survey Solutions server exposes API endpoints. Current Survey Solutions provides both REST and GraphQL interfaces. The REST API can perform many of the same operational actions available in the user interface, including creating assignments, listing interviews, approving/rejecting interviews, creating users, and generating exports.

Illustrated Swagger / REST API pageSurvey Solutions API v1
GET/api/v1/assignments
List assignments
POST/api/v1/assignments
Create new assignment
GET/api/v1/interviews/{id}
Get interview answers
POST/api/v2/export
Start an export job
GET/api/v2/export/{id}/file
Download completed export

Always use the API documentation from your own Survey Solutions server. Survey Solutions explicitly warns that API syntax can change between versions. On a server, the interactive REST reference is available under its API documentation pageβ€”for example the World Bank demo server exposes Swagger at /apidocs/index.html.

API account and authentication

For system-to-system integration, the server administrator creates an API User. That account is intended for scripts/services, rather than normal interactive fieldwork.

Basic authentication
API username
+
API password

Your HTTP client sends those credentials with each request.

Bearer token
Authorization:
Bearer YOUR_TOKEN

Survey Solutions also supports token/JWT authentication when the server administrator has enabled it.

Do not put API credentials inside the questionnaire or interviewer device. Run API integrations from a controlled computer/server/script. The questionnaire itself is not intended to call arbitrary external REST APIs while an interview is being conducted.

Usage 1 β€” dynamic data shared between questionnaires

Suppose you have two surveys:

  • Questionnaire A: Household listing collects household ID, household head, phone number, household size, and eligibility.
  • Questionnaire B: Main household interview should only be sent to eligible households and should already know the information collected in A.

In SurveyCTO you might solve this with publishing to a server dataset and then letting Form B read that dataset. Survey Solutions does not have that direct built-in cross-questionnaire lookup. The API gives you a different architecture:

Questionnaire A
Listing interviews completed
β†’
Your integration script / database
read β†’ filter β†’ transform
β†’
Questionnaire B assignments
created through REST API

Example: listing β†’ eligible main-survey assignment

Step 1 β€” collect Listing Survey A
listing_id, hhid, hh_head, phone, hhsize, eligible.
Step 2 β€” external script retrieves Survey A data
Your script can retrieve interviews through the interview endpoints or, for large recurring workflows, trigger a Survey Solutions export via the export API and read the resulting data file.
Step 3 β€” apply your business rule
Keep only households where eligible == 1. Optionally merge in other data from a CRM, administrative database, earlier wave, or sampling database.
Step 4 β€” create an assignment for Questionnaire B
Send the selected household data to POST /api/v1/assignments.
Step 5 β€” interviewer synchronizes
The new main-survey assignment appears automatically once it is assigned and the interviewer synchronizes.
Illustrated automation monitorListing β†’ Main Survey
HHIDListing statusEligibleMain assignment
PH001CompletedYesCreated #4521
PH002CompletedNoNot created
PH003CompletedYesCreated #4522

Illustrative Python workflow

The exact request/response schema should be copied from the Swagger documentation on your project server. The example below shows the overall structure of the workflow:

import requests

SERVER = "https://survey.example.org"
USER = "api_user"
PASSWORD = "********"

# Example: create one eligible household assignment
payload = {
    "Responsible": "enum001",
    "Quantity": 1,
    "QuestionnaireId": "QUESTIONNAIRE_GUID$VERSION",
    "IdentifyingData": [
        {"Variable": "hhid", "Answer": "PH001"},
        {"Variable": "hh_head", "Answer": "Maria Santos"},
        {"Variable": "phone", "Answer": "09171234567"},
        {"Variable": "hhsize", "Answer": "5"}
    ]
}

r = requests.post(
    f"{SERVER}/api/v1/assignments",
    auth=(USER, PASSWORD),
    json=payload,
    timeout=60
)

r.raise_for_status()
assignment = r.json()
print(assignment)
Version-specific schema: the endpoint POST /api/v1/assignments is current, but use your own server's Swagger page to confirm the exact body fields and formats for your installed version before writing a live integration.

Can Questionnaire B receive more than just identifying data?

Yes, Survey Solutions supports preloading beyond Cover/identifying questions, including hidden/interviewer questions and roster data. For complex panel/cross-questionnaire preloading, however, the structure is more involved because roster questions need question identities and roster vectors. In many projects the easiest reliable pattern is:

Simple API handoff

Use the assignment API for identifying/preloaded values needed to create the case and route it to a user.

Complex panel handoff

Generate the advanced preloading files from your external system, or build an API layer that understands the questionnaire document and roster identities.

The API can retrieve the questionnaire document through GET /api/v1/questionnaires/{id}/{version}/document, which is useful when an integration needs question identities and hierarchy rather than only variable names.

External dataset β†’ Survey Solutions

The same architecture works when the source is not another Survey Solutions questionnaire:

External source
SQL Β· CRM Β· MIS Β· Stata Β· administrative register
β†’
Integration script
select cases
map fields
validate
β†’
Survey Solutions
create assignments through REST API

The World Bank's own API documentation specifically describes this type of use: an external system can consume administrative databases, remote sensing, earlier surveys, or other sources and supply the selected data to Survey Solutions as assignments.

Usage 2 β€” sample/case data known before fieldwork: create assignments by API

Suppose your master sample is in a SQL database or a Stata/CSV pipeline rather than a .tab file. Instead of exporting a batch assignment file and uploading it manually, your system can create assignments directly.

Master sample
10,000 households
β†’
Assignment script
one API request per household
or controlled batches
β†’
HQ assignments
case information + responsible user

Example sample record

{
  "hhid": "PH-001-0001",
  "province": "Cavite",
  "municipality": "Dasmarinas",
  "barangay": "Paliparan III",
  "hh_head": "Maria Santos",
  "address": "Blk 4 Lot 12",
  "interviewer": "enum001"
}

Map the external fields to Survey Solutions variables

External database columnSurvey Solutions variableAssignment use
hhidhhidIdentifying
provinceprovinceIdentifying
municipalitymunicipalityIdentifying
barangaybarangayIdentifying
hh_headsample_headIdentifying / reference
addresssample_addressIdentifying / reference
interviewerResponsible loginRouting, not questionnaire data

Illustrative batch loop

for case in sample_records:

    payload = {
        "Responsible": case["interviewer"],
        "Quantity": 1,
        "QuestionnaireId": QUESTIONNAIRE_ID,
        "IdentifyingData": [
            {"Variable": "hhid", "Answer": case["hhid"]},
            {"Variable": "province", "Answer": case["province"]},
            {"Variable": "municipality", "Answer": case["municipality"]},
            {"Variable": "barangay", "Answer": case["barangay"]},
            {"Variable": "sample_head", "Answer": case["hh_head"]},
            {"Variable": "sample_address", "Answer": case["address"]}
        ]
    }

    response = requests.post(
        SERVER + "/api/v1/assignments",
        auth=(API_USER, API_PASSWORD),
        json=payload
    )

    # Save Survey Solutions assignment ID back to your master sample
    save_assignment_id(case["hhid"], response.json()["Id"])
That last step is important: maintain a crosswalk between your own case ID and the Survey Solutions assignment ID. Your hhid remains the authoritative sample key, while the Survey Solutions assignment ID is useful for API updates, reassignment, history checks, and audit.

A robust assignment automation table

In your external database, I would maintain fields like:

hhidsuso_assignment_idquestionnaire_versionresponsibleapi_statuslast_sync
PH00145213enum001created18 Sep 10:12
PH00245223enum001created18 Sep 10:12
PH003β€”3enum002error18 Sep 10:12

Why store the assignment ID?

Once an assignment exists, the API exposes endpoints such as:

Assignment lifecycle endpoints
GET/api/v1/assignments/{id} β€” assignment details
GET/api/v1/assignments/{id}/history β€” assignment history
PATCH/api/v1/assignments/{id}/assign β€” change responsible user
PATCH/api/v1/assignments/{id}/changeQuantity β€” change requested quantity
PATCH/api/v1/assignments/{id}/archive β€” archive assignment

Prevent duplicate assignment creation

Do not blindly rerun a script that creates assignments. Make the workflow idempotent:

  1. Before creating, check your external crosswalk for an existing Survey Solutions assignment ID.
  2. If needed, query GET /api/v1/assignments or an individual assignment endpoint to reconcile server state.
  3. Only create when no valid assignment already exists.
  4. Save the returned assignment ID immediately after successful creation.
  5. Log HTTP status, timestamp, and error message for failures.
At scale, reliability matters more than cleverness. For thousands of cases, add retries with backoff, logging, duplicate protection, and a reconciliation step. Never assume that β€œscript ran” means every assignment was created successfully.

Closed-loop example: Survey A β†’ external check β†’ Survey B

08:00 β€” Listing interviewer completes HH001
Survey A reaches HQ.
↓
08:05 β€” integration script sees completed interview
Reads/export data and checks eligible == 1.
↓
08:06 β€” merge external information
Add treatment group, administrative beneficiary status, or previous-wave ID from your database.
↓
08:07 β€” POST assignment for Questionnaire B
Assign to the appropriate interviewer/team.
↓
Next interviewer sync
HH001 appears as a new main-survey case.

What this does β€” and does not β€” mean for β€œdynamic data”

ScenarioSupported approach
Survey B needs values from completed Survey A before B startsYes: API/export A β†’ create/preload assignment B
Survey B needs values from an external administrative database before B startsYes: external DB β†’ API β†’ assignment B
Survey B must query a remote REST endpoint live every time the interviewer answers a questionNo built-in questionnaire mechanism. Survey Solutions questionnaires are designed to work offline in CAPI.
External system should react when Survey Solutions data changeYes: poll the API/export at an appropriate frequency or build a scheduled integration workflow
External checker should reject/approve interviews automaticallyYes: retrieve data β†’ run checks β†’ use interview approve/reject API endpoints

Automated export as part of the pipeline

The export API uses a job workflow:

POST
/api/v2/export
start export
β†’
GET
/api/v2/export/{id}
check job status
β†’
GET
/api/v2/export/{id}/file
download file

This is useful when Survey A has many completed cases and your integration wants a batch snapshot rather than making one interview request at a time.

REST vs GraphQL

REST

Best starting point for operational actions: assignments, interviews, users, exports, approvals/rejections. This module focuses on REST.

GraphQL

Useful for selected query/mutation workflows where the GraphQL schema provides the operation you need. Survey Solutions exposes its schema from the server.

Use an existing client when it fits your workflow

The Survey Solutions API overview lists community-maintained clients for Stata as well as packages or examples for R, Python, PowerShell and .NET. The R package wraps many REST calls for survey management, exports, paradata and Shiny workflows. Stata users should review the listed Stata clients before writing raw HTTP calls. A wrapper saves syntax, but it does not remove the need to understand assignment identities, server versions, retries and reconciliation.

What this course adds: the accompanying susocase package is a focused, Stata-first batch-release workflow. It does not claim to be the first or official Stata API client. It adds a durable case register, gradual sample release, one-household-to-many-questionnaire expansion, safe reruns, manual .tab delivery and API delivery under one workflow.

Guided practical β€” release a deployment batch with susocase

Assume your master register contains 3,200 households, but the field plan releases only batch 3 today. Each selected row has a string household ID, an interviewer username and one or more comma-separated form aliases. The project map connects each alias to the questionnaire identity and the identifying questions that receive preload values.

* Preserve leading zeros when the source is CSV
import delimited using "households.csv", clear stringcols(_all)
destring release_batch, replace

* Your familiar allocation logic remains in Stata
replace users = "enum_001" if barangay_code == "B000012"
replace users = "enum_002" if barangay_code == "B000097"

* Local preview only: no Headquarters change
susocase plan if release_batch == 3, id(id) responsible(users) ///
    forms(formids) batch("deployment_003")

* Choose one route after reviewing proposed_actions.csv and errors.csv
susocase apply, batch("deployment_003")
* OR: susocase export, batch("deployment_003")

susocase reconcile, batch("deployment_003")
susocase status, batch("deployment_003")
CommandWhat it doesServer effect
initCreates the project map and local register location.None
authStores API credentials in the operating-system credential store.None
doctorChecks Python, configuration, authentication and API reachability.Read only
planFreezes only the selected observations, validates them and classifies creates, unchanged cases, reassignments and blockers.None
exportCreates one Headquarters-compatible .tab per questionnaire version and reserves the cases as pending manual reconciliation.None until a person uploads
applyRechecks users and questionnaire versions, then creates or safely reassigns cases through REST.Creates/changes assignments
reconcileReads assignment evidence and completes the case-to-assignment crosswalk.Read only
statusExports register and operation receipts as CSV.None
Direct interviewer allocation β€” default

The responsible value is an individual interviewer username. This matches Survey Solutions' native accountability model and makes each assignment's owner explicit.

Team-to-supervisor allocation β€” optional

Use allocation(supervisor) with a team-to-supervisor map when operational plans release work to supervisors first. The supervisor then distributes assignments to interviewers in that team.

Reassignment has an offline boundary. susocase permits reassignment only when Headquarters reports zero interviews and the operator records that affected field devices synchronized. Headquarters cannot see work that remains only on an offline tablet. An assignment with an interview is flagged for review; active-interview transfer is outside the first package release.

What to inspect before delivery

  1. Confirm the selected batch count. Unselected households must produce no planned assignment.
  2. Inspect creates, unchanged cases, proposed reassignments, exclusions and blockers separately.
  3. Check that leading-zero IDs and raw category codes survived. A value already converted to a number cannot be reconstructed safely.
  4. Confirm every form alias maps to the exact questionnaire GUID and version imported into this Headquarters.
  5. For manual delivery, remember that the files are prepared, not uploaded. Supply the returned assignment IDs during reconciliation.
  6. For API delivery, treat a timed-out write as unresolved. Query and reconcile before another creation attempt.
Practical evidence: run the same batch plan twice and show that the second plan contains no fresh creates after successful reconciliation. Then change one unstarted case's responsible username and verify that the plan proposes a reassignment rather than a second assignment. Finally, test receipt on an Interviewer device, complete a synthetic interview, synchronize, and confirm that susocase status reflects server evidence. Assignment creation alone does not prove device receipt.

Practical architecture I recommend

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Master / external DB   β”‚
                    β”‚ hhid, sample, metadata  β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚ Integration layer       β”‚
                    β”‚ Stata / Python / R      β”‚
                    β”‚ validation + logs       β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚        β”‚
                 REST API   β”‚        β”‚ REST/export API
                            β”‚        β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”    β”Œβ”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚ Survey A / HQ β”‚    β”‚ Survey B / HQ    β”‚
              β”‚ listing       β”‚    β”‚ main interview   β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Why this architecture is strong: Survey Solutions remains responsible for data collection and field workflow, while your external system remains responsible for cross-survey logic, shared datasets, dynamic case generation, and persistent IDs. That separation is especially useful in large longitudinal, multi-stage, or high-frequency surveys.

Common API mistakes

MistakeBetter practice
Using an HQ user's password in scriptsCreate a dedicated API User with appropriate access.
Hard-coding API syntax from an old tutorialCheck Swagger on the exact server/version you are using.
Using Survey Solutions internal DB directlyUse supported REST/GraphQL/export interfaces; database internals can change.
Assuming questionnaire B can live-query questionnaire AUse an external integration process to transfer/preload values.
Creating thousands of assignments without a crosswalkStore hhid ↔ assignment_id and log every API result.
Using Survey Solutions roster IDs as your panel person IDFor panel work, preload your own stable person ID in a hidden/interviewer variable.
Design exercise: You have a listing survey and a main survey. Listing interviews are completed throughout the day. Only households with eligible == 1 should receive the main survey, and the main survey needs hhid, household head, phone, treatment group from an external database, and the interviewer login.
Show suggested architecture

Use an external integration script. Periodically retrieve completed listing data (interview API or export API), filter eligible == 1, merge treatment group and interviewer allocation from the external database, then create one main-survey assignment per eligible household using POST /api/v1/assignments. Save the returned Survey Solutions assignment ID against hhid so reruns do not create duplicates.

Operational API pattern: make retries safe

A field integration should be restartable. Keep a durable crosswalk and make each run reconcile desired work with actual server state before creating or changing anything.

for case in eligible_cases:
    existing = crosswalk.find(case.hhid, questionnaire_version)
    if existing:
        reconcile(existing, server_state)
        continue

    response = create_assignment(case)
    if response.success:
        crosswalk.save(case.hhid, response.assignment_id,
                       questionnaire_version, "created")
    elif response.is_transient:
        retry_with_backoff()
    else:
        log_for_review(response.status, response.body)
Retry

Use bounded exponential backoff for timeouts, connection failures and eligible 5xx responses. Respect server throttling and never retry an invalid request indefinitely.

Reconcile

After an uncertain response, query the server and crosswalk before creating again. This prevents duplicate assignments when the server succeeded but the client missed the response.

For exports, use the server's asynchronous job pattern: request an export, store the job identifier, poll at a reasonable interval, verify completion, download once, and record the file checksum. Confirm endpoints and payloads in Swagger on the exact server version.

Module 14 β€” Guided practice and capstone

Build, deploy and test a household roster and education survey

Build your first working Survey Solutions form in small, testable passes. Start with a household ID and consent, add one roster, then layer in age, phones and education before uploading assignments.

Your build route

  1. Start small: save caseid in Cover and compile once. This is your first working checkpoint.
  2. Control the interview: add coded Yes/No consent and test both consent and refusal.
  3. List people: create member_fnames and its members roster; test with two people before adding calculations.
  4. Complete member questions: add names, relationship, age (including special values), phone and education one block at a time.
  5. Deploy a practice batch: compile, import the questionnaire, upload the matching TAB, and synchronize an interviewer account.
  6. Prove it works: run the test scenarios, review an interview and inspect the parent–roster export.

Stay on this main route for a first build. The expandable troubleshooting notes explain real errors if you encounter them; they are not prerequisites.

Embedded practice resources

These files are stored inside this HTML. They remain available when the course is copied to another computer or opened without internet access.

The CSV preserves the supplied sample. The TAB is a derived practice upload with _quantity = 1, direct assignments split evenly between enum_01 and enum_02, and the matching full name in assigned_interviewer_name.

Preview the ten synthetic households
caseidprovincemunicipalitybarangaypuroktreatment_armhh_head_name
HH-2024-001IloiloPaviaMabiniPropertreatmentBruno Fernandez
HH-2024-002IloiloPaviaSalvacionSitio AcontrolHarry Maguire
HH-2024-003AntiqueSan JoseCatunganZone 2treatmentPatrick Dorgu
HH-2024-004CapizRoxas CityPoblacionBlock 3controlLeny Yoro
HH-2024-005AklanKaliboLinaoPurok 1treatmentLuke Shaw
HH-2024-006IloiloMiagaoBaybaySitio BtreatmentMason Mount
HH-2024-007AntiqueSibalomMapulang LupaZone 1controlManuel Ugarte
HH-2024-008CapizPontevedraCentroPropertreatmentGodwill Kukonki
HH-2024-009AklanBangaLumangbayanPurok 3controlKobbie Maino
HH-2024-010IloiloLeganesSanto NiΓ±oZone 3treatmentMatheus Cunha

Part A β€” Translate the specification into Survey Solutions

Do not reproduce every SurveyCTO mechanism literally. Preserve the survey's meaning while using Survey Solutions objects: identifying questions and assignments for sample data, a list-driven roster for household members, linked questions for selecting people, and C# conditions for relevance and validation.

1. Create the questionnaire shell and cover

Create a new Designer questionnaire named Household Roster and Education Practice with questionnaire variable hh_education_practice. Open the built-in Cover section and begin with the household identifier.

Modern Designer behavior: there is no Identifying checkbox to find. Since version 20.07, every supported question placed in the Cover section is automatically identifying. A question outside Cover is not identifying. Position is the setting. Question scope β†—

Checkpoint 1 β€” finish and save caseid

  1. Confirm that the left side of Designer says Cover. If caseid is shown in this section, it is already an identifying question.
  2. Set Question type to Text, Variable name to caseid, Variable label to Household ID, and Question text to Household ID. Cover questions require a variable label because that label is used on assignment and interview cards.
  3. Click the green SAVE button at the bottom of the question editor. Do not look for an Identifying control; none is needed.
Save verification: the edit is accepted and caseid appears as a saved question in the Cover list. If Designer keeps the editor open, the absence of an unsaved-change warning and the saved item in the list are the important checks.
Survey Solutions Designer with the caseid text question in the Cover section; the green Save button is at the bottom of the question editor and Compile is in the top toolbar.
Your starting screen. The three landmarks are Cover on the left, the green SAVE button at the bottom of the editor, and COMPILE beside the questionnaire title in the top toolbar. The screenshot correctly shows no Identifying checkbox.

Checkpoint 2 β€” compile the saved starting state

  1. Click COMPILE in the top toolbar.
  2. Read the result rather than closing it immediately. Confirm that Designer does not report a missing identifying question or a missing Cover variable label for caseid.
  3. If an error names caseid, return to the question, confirm that it is inside Cover and that its variable label is filled, save again, and recompile.
Compilation is a structural check, not the finish line. At this early checkpoint, you are proving that the first identifying question is saved correctly. Later additions can introduce new errors, so repeat Edit β†’ Save β†’ Compile throughout Part A. A successful compile also does not prove that the interview flow is logically correct; that requires testing.

Complete the rest of the Cover

Add the remaining sample-frame questions directly inside Cover, one at a time, so their variable names match the assignment file exactly. Each becomes identifying automatically. Save each question before adding the next.

VariableTypePurpose
caseidTextStable household ID. This is the first saved-and-compiled checkpoint above; validate that it is not blank.
province, municipality, barangay, purokTextLocation shown on the assignment and interviewer dashboard.
treatment_armTextFrozen practice allocation from the sample list.
hh_head_nameTextSample-frame name of the household head. Keep it separate from roster responses.
Cover completion check: you should have seven saved Text questions in Cover: caseid, province, municipality, barangay, purok, treatment_arm, and hh_head_name. Give every one a clear variable label, compile again, and resolve all errors before moving on.

Add interview_date as an interviewer Date question with timestamp recording at the start. Do not duplicate enumerator and supervisor names merely to recover operational metadata: Survey Solutions already tracks responsible accounts and exports workflow history. Add a questionnaire name field only when the interview text or protocol needs an explicit preloaded or independently confirmed value, and document whether that value remains valid after reassignment.

2. Consent and interview outcome

Configure consent as a single-select question so the answer can control the rest of the interview. This Designer screen shows the question type and the preloaded interviewer-name substitution in place.

Survey Solutions Designer showing consent_q_01 as a categorical single-select question with the assigned_interviewer_name field substituted into the consent wording.
Configured consent question. The question is now Categorical: Single-select, and %assigned_interviewer_name% supplies the preloaded full name. Check the Yes/No category codes below the visible part of the editor, and replace the remaining [organization] prompt with the real organization name before deployment.

Checkpoint 3 β€” make consent a coded Yes/No question

  • Confirm the question type is Single-select categorical and that its categories are Yes = 1, No = 2. The screenshot shows the type, but the category list is below the visible area.
  • Rename consent_q_01 to consent if you want to follow the expressions in this capstone exactly. If you keep consent_q_01, replace consent with that name in every later expression.
  • Place all substantive sections after consent under enabling condition consent == 1.
  • If consent is refused, enable an outcome question such as final_outcome and allow the interview to end correctly. Refusal is a valid outcome, not a validation failure.
  • Make the consent question critical if the project requires an explicit recorded answer before submission.

Do not rely on an interviewer-name system substitution

%interviewer% and @interviewer are not supported questionnaire shortcuts for the logged-in user's name. Current Survey Solutions documentation lists %rostertitle% and %self% as the system-defined text substitutions, and Designer's expression context does not expose the HQ account's display name. The optional full name stored in an HQ user profile is operational account metadata, not a questionnaire value that can be piped automatically into consent text. Text substitution β†—
If an older fo_name calculation fails with WB0027
Survey Solutions compilation window showing WB0027 syntax error for fo_name while its expression uses the unsupported at-user identifier.
This expression should be removed, not repaired with another account shortcut. The screenshot uses @user in a nested conditional expression. Survey Solutions does not expose @user or @interviewer as the logged-in username, so the variable cannot determine the current staff member this way.
  1. Create the replacement field first. In Cover, add a Text question named assigned_interviewer_name, give it a clear variable label, and save it.
  2. Change consent. Replace %fo_name% with %assigned_interviewer_name%, then save the consent question.
  3. Remove the broken variable. Delete fo_name after nothing refers to it. Do not replace @user with @interviewer; both approaches are unsupported.
  4. Compile again. For Designer testing, enter a sample name in the Cover question. For fieldwork, preload the full name through the assignment workflow explained below.
Temporary diagnostic only: if you want to prove that the String variable editor itself works before removing fo_name, replace its entire expression with "John Smith", save, and compile. That fixed string should compile, but it is not dynamic and must not be used in production.
Recommended for this practice
  1. Add an interviewer instruction: Introduce yourself by full name, show your ID if required, and name the organization before reading the consent statement.
  2. If you do not preload a name, use a stable opening such as: Good morning/afternoon. I am part of the survey team. If you do preload it, use %assigned_interviewer_name% as shown above.
  3. Replace [organization] in the screenshot’s wording with the actual organization name before deployment. Treat morning/afternoon as spoken guidance rather than expecting Designer to choose it automatically.
If the full name must appear on screen
  1. Create a Text question in Cover named assigned_interviewer_name with variable label β€œAssigned interviewer name.”
  2. Add a column named exactly assigned_interviewer_name to the assignment .tab file and preload the intended staff member's full display name for each case.
  3. Use %assigned_interviewer_name% in the consent text, then compile and test.
When you are ready to upload: how interviewer names and usernames work in the assignment TAB

_responsible versus assigned_interviewer_name

These two columns have different jobs and normally contain different forms of the same person's identity:

Assignment-file columnWhat it must containWhat Survey Solutions does with it
_responsibleThe existing Survey Solutions login username, for example jolex. Do not enter β€œJolex Banda” unless that is literally the account login.Routes the assignment to that HQ, supervisor or interviewer account. For direct interviewer assignment, use the interviewer's login.
assigned_interviewer_nameThe human-readable full name to show in consent, for example Jolex Banda.Preloads the ordinary questionnaire question with that exact text. It does not assign or route the case.
_quantityAn integer such as 1, or -1 for an unlimited assignment where appropriate.Controls how many interviews may be created from the assignment.

Because assigned_interviewer_name is placed in Cover in this exercise, it is an identifying question and should appear in the assignment template downloaded from Headquarters. Populate it in the same assignment .tab file alongside _responsible:

caseid	assigned_interviewer_name	_responsible	_quantity
HH-001	Jolex Banda	jolex	1
HH-002	Alice Smith	asmith	1

Read the first row as: create assignment HH-001, route it to the account whose login is jolex, allow one interview, and preload the display text β€œJolex Banda” into the questionnaire. The two name columns are deliberately not interchangeable. Assignment upload columns β†—

Spreadsheet assignment table containing responsible usernames, quantity values and assigned_interviewer_name full names for ten practice households.
The column structure is correct, with one exception in the captured rows. Rows assigned directly to enum_01 and enum_02 correctly pair the login with Mike Johnson and Abel Kayembe. Rows 9–10 use sup_train, so β€œSeliano Masautso” would describe the supervisorβ€”not a guaranteed interviewer. The revised downloadable TAB assigns those final two cases directly to the interviewers instead. Also save the file as UTF-8 so Santo NiΓ±o does not become Santo Niño.
Column order is flexible; column names are not. assigned_interviewer_name may appear before or after _responsible and _quantity. Keep the headers spelled exactly as the questionnaire and Headquarters expect.
If the column is missing from the downloaded template: save and compile the Cover question, import the updated questionnaire version into Headquarters, and download a fresh assignment .tab template for that version. Do not add the column to an older questionnaire version whose schema does not contain it.
Direct assignment versus supervisor assignment. If _responsible contains a supervisor's username, the assignment initially belongs to that supervisorβ€”not to the interviewer named in assigned_interviewer_name. The supervisor may later give it to a different interviewer. Survey Solutions will update operational responsibility, but it will not rewrite your questionnaire field.
Preloaded does not mean live. assigned_interviewer_name records what the assignment file said. It does not automatically change when Headquarters reassigns the case, and a preloaded Cover answer is locked. Use this design only when assignments are sent directly to known interviewers and reassignment is tightly controlled. If reassignment is common, prefer a spoken self-introduction or another field protocol that cannot display a stale name.
Why a Designer lookup table cannot map hundreds of login names to full names
Do not upload usernames and names as a Designer lookup table. Survey Solutions lookup tables require a unique integer rowcode, and every other data column must also be numeric. Renaming interviewer_login to rowcode, adding a numeric ID, using LINQ, or switching from GetRow() to another lookup syntax cannot make a text column valid. Lookup-table rules β†—
Survey Solutions error saying Mandatory rowcode column is missing.
Failure 1: missing key. A lookup table must have a column literally named rowcode.
Survey Solutions error saying tester1 cannot be parsed as a long integer in rowcode.
Failure 2: text key. Values such as tester1 cannot be used as rowcode; the key must be an integer.
Survey Solutions error saying tester1 cannot be parsed as a decimal number in interviewer_login.
Failure 3: text data. Adding rowcode still fails because interviewer_login and full names are strings, while lookup-table content must be numeric.

Recommended scalable workflow β€” preload the display name with the assignment

  1. Keep the questionnaire field simple. Use the Cover Text question assigned_interviewer_name described above. Do not create fo_name as a calculated variable for this workflow.
  2. Build assignments outside Designer. Join each sampled case to your staff directory in Excel, R, Stata, Python, or your case-management system. Put the full name in assigned_interviewer_name and the HQ login in the special _responsible column.
  3. Upload the tab-delimited assignment file in Headquarters. The column names for questionnaire data must match Designer exactly. One file can cover many interviewers when each row supplies _responsible.
  4. Pipe the preloaded value into consent. Use %assigned_interviewer_name%, save, compile, and test at least one assignment for each responsible account.
caseid	assigned_interviewer_name	_responsible	_quantity
DEMO-001	John Smith	tester1	1
DEMO-002	Mike Johnson	tester2	1

This embedded file contains one row per case, the matching Cover fields, _responsible, and _quantity. It is an assignment upload, not a Designer lookup table; adapt the sample only after your questionnaire fields match. Assignment upload guide β†—

Alternative β€” use a coded single-select question

If the project already manages a stable numeric staff code, create reusable categories such as 1 = John Smith and 2 = Mike Johnson. Bind them to a Cover single-select question named assigned_interviewer_code, preload the numeric code, and use %assigned_interviewer_code% in consent. Text substitution displays the selected option's label, not its numeric code. This avoids hundreds of C# branches, but the code-to-name category list still needs governance and the preloaded value can become stale after reassignment.

Why the proposed expression cannot work

Expression piecePlain-language intentWhy it fails here
enumeratorsUse the uploaded table named enumerators.The file contains string data, so Designer rejects it before an expression can query it.
GetRow(@interviewer)Find the row whose key matches the logged-in account.A lookup key must be an integer, and @interviewer is not a supported questionnaire system variable.
?.interviewer_fnIf a row was found, read its full-name field.A full name is text, but lookup-table data fields are numeric.
?? @interviewerIf no name was found, fall back to the login.The fallback operator is valid C# in the right context, but it cannot repair an invalid table or supply a nonexistent system value.
enumerators.GetRow(@interviewer)?.interviewer_fn ?? @interviewer
// Conceptually understandable, but not valid for this Survey Solutions use case.
What lookup tables are for. Use them for numeric reference data, for example a numeric staff code mapped to a numeric team or region code. The documented pattern is LookupTableName[code].ColumnName, optionally guarded with LookupTableName.ContainsKey(code). In plain language: choose the table, find the integer key in rowcode, then return a numeric value from that row.
Reassignment check: _responsible controls who receives the assignment; assigned_interviewer_name is ordinary preloaded questionnaire data. Reallocating an assignment does not prove that the displayed name was refreshed. Add a reconciliation step before fieldwork or use the spoken-introduction pattern when reassignment is common.
Consent verification: Save the categorical question, compile, and Test. Confirm that the opening contains no literal square-bracket placeholders, that Yes stores 1 and No stores 2, that the displayed staff nameβ€”if you chose to preload oneβ€”matches the assignment, and that refusal follows the intended end-of-interview route.

3. Build the household member roster

Roster source
  1. Add list question member_fnames: β€œList the first names of all current household members, starting with the household head.”
  2. Set a realistic maximum, for example 20 items.
  3. Create list roster members triggered by member_fnames.
  4. Use %rostertitle% in member-specific question text.
Why this is an adaptation

The source loop asks β€œIs there another member?” after every person. In Survey Solutions, the list question's Add Item interaction controls row creation. This is easier to review and gives the roster stable row identities without reproducing event-driven repeat behavior.

Inside members, add middle_name, last_name, has_suffix, suffix, has_nickname, nickname and relation_to_head. Use the PDF for exact labels and categories. The examples below use these same names throughout; variable names are case-sensitive.

suffix enabled when:       has_suffix == 1
nickname enabled when:     has_nickname == 1

Filter and validate relationship to the household head

Because the list question instructs the interviewer to enter the household head first, code 1 (Head) should be the only relationship available in row 0, and it should be unavailable in every later row. Put this expression in the Filter field of relation_to_head:

(@rowindex == 0 && @optioncode == 1) ||
(@rowindex > 0 && @optioncode != 1)

Designer evaluates the filter once for each candidate category. @optioncode is that candidate's code, while @rowindex identifies the current roster position. The result is: row 0 sees only Head; rows 1 onward see every permitted relationship except Head.

If you retain a validation condition on relation_to_head, it must accept both the valid first-row case and the valid later-row case:

(@rowindex == 0 && self == 1) ||
(@rowindex > 0 && self != 1)

Use an error message such as: The first listed member must be the household head, and later members cannot be coded as head. The earlier expression @rowindex == 0 && self == 1 by itself is incorrect because it evaluates to false for every row after the first. The matching validation above is a defensive check; the filter already prevents the interviewer from selecting the disallowed categories.

A validation does not make a blank answer mandatory. Validation rules evaluate submitted answers. If every member must have a relationship before completion, mark relation_to_head as a critical question; Survey Solutions then treats each roster instance separately at completion. Mandatory questions β†—
Survey Solutions Designer showing the relation_to_head categorical question with a row-sensitive option filter and a validation condition that currently covers only the first roster row.
Filter and validation serve different purposes. The filter shown is correct: it controls which relationship categories are offered by row. Replace the validation visible in the screenshot with the two-branch expression above so later rows are valid too.

Why there is no roster-level validation box

You are not overlooking a hidden control. Survey Solutions rosters support an enabling condition but do not have validation conditions. Validation rules can be attached to questions and static text, while a critical rule can be defined for the questionnaire as a whole. Roster properties β†—

Survey Solutions Designer roster properties for members showing the roster source, roster ID, display mode and enabling condition, with no validation-condition control.
The roster panel is complete. The absence of an Add validation condition control is expected. The source list already determines whether rows exist, so member_fnames.Length >= 1 is an optional display guard rather than a data-quality validation.

Put household-wide roster checks on members_confirm

Place the aggregate checks on a simple Yes/No confirmation question members_confirm after the roster, where the whole members collection is in scope. For a first pass, its text can simply ask the interviewer to confirm the listed members; add the generated full-name sentence in the optional second pass below. Add these as two separate validation conditions so each failure has a precise message:

Validation condition on members_confirmError message
members.Count(p => p.relation_to_head == 1) == 1There must be exactly one household head.
members.Count(p => p.relation_to_head == 2) <= 1There cannot be more than one spouse or partner of the household head.

These rules are evaluated when members_confirm has an answer. Keep that question outside the roster and after all relationship questions. If these conditions must prevent interview completionβ€”not merely flag invalid confirmationβ€”also create an equivalent questionnaire-level critical rule:

members.Count(p => p.relation_to_head == 1) == 1 &&
members.Count(p => p.relation_to_head == 2) <= 1
Why both row and household checks? The row filter guides data entry immediately. The row validation catches an inconsistent stored answer. The confirmation validations inspect the roster as a collection. A critical rule, when required by the protocol, governs whether an interview with the inconsistency may be completed. These are complementary controls, not interchangeable settings.

Once the list, roster and relationship checks work with two members, continue to age and education. The name calculations below are a useful second pass when you want to display polished full names in the interview.

Second pass: calculate the head's full name and join every roster member's name

Create one household-level head_name

Placement matters. In the supplied screen, head_name is indented inside the roster, so Survey Solutions would calculate one value for every roster row. Drag it out only if Designer shows an unambiguous parent-level drop target; otherwise select it and use MOVE TO to place it in the parent household section, immediately after the roster.

The list question stores objects with a numeric Value and text Text. The roster is an ordered collection. Therefore the first name entered in the list is member_fnames.First().Text, while the first roster record is members[0]. In your build, the first listed person is defined as the household head.

If the suffix is a Text question

Set head_name to type String and use this expression outside the roster:

members.Any()
? String.Join(" ", new[] {
    member_fnames.First().Text,
    members[0].middle_name,
    members[0].last_name,
    members[0].suffix_text
  }.Where(x => !String.IsNullOrWhiteSpace(x)))
: null

This means: if the roster has at least one person, take the first listed first name and the first roster row's middle name, last name, and suffix; discard blank pieces; then join the remaining pieces with one space. If the roster is empty, return no value.

If suffix is categorical, as in the supplied screen

A single-select answer is a nullable integer in expressions, not its displayed label. Create a String variable named suffix_text inside the roster and map your questionnaire's actual category codes to their text. The following is a templateβ€”replace the example codes and labels with the values shown in your suffix question:

suffix == 1 ? "Jr." :
suffix == 2 ? "Sr." :
suffix == 3 ? "II" :
suffix == 4 ? "III" :
suffix == -666 ? suffix_oth :
null

Then use the household-level head_name expression above. The practice questionnaire uses -666 for β€œOther”; if your own category code differs, change it. Do not concatenate members[0].suffix directly or the result will contain the numeric category code rather than β€œJr.” or β€œIII.”

Keep your names consistent. The expressions here use member_fnames for the source list and members for the roster, matching the supplied Designer screenshots. If your own questionnaire uses different variable names, substitute them everywhere before compiling. Survey Solutions data types β†—
Head-name test: compile, then test four cases: first + last only; first + middle + last; first + last + suffix; and all four parts. Confirm there are no doubled spaces and that changing the first list item or the first row's name fields immediately recalculates head_name. Also confirm that a second member does not replace the head.

Worked example: build and display a list of every member's full name

This task has two calculations at different levels. First, build one clean full_name inside every members roster row. Second, place full_names_joined outside the roster and join all those row-level values into one household-level sentence. Keeping those two jobs separate makes the logic easier to compile, test and reuse.

Step 1 β€” build one full_name inside the roster
  1. Right-click the members roster and choose Add variable, or use the section controls and then confirm that the new variable is indented inside the roster.
  2. Set Variable type to String and Variable name to full_name.
  3. Enter the expression below, save, and compile.
String.Join(" ", new[] {
    member_fnames
        .Where(x => x.Value == @rowcode)
        .Select(x => x.Text)
        .FirstOrDefault(),
    middle_name,
    last_name,
    suffix_text
}.Where(x => !String.IsNullOrWhiteSpace(x)))
Expression pieceMeaning in plain language
x.Value == @rowcodeFind the item in the source list whose identity matches the current roster row.
.Select(x => x.Text).FirstOrDefault()Take that list item's displayed first name. If no matching item exists, return no first name instead of crashing the calculation.
new[] { ... }Place the first, middle, last and suffix text into a temporary ordered set of name parts.
Where(...IsNullOrWhiteSpace...)Discard missing or blank parts, so an absent middle name or suffix does not create doubled spaces.
String.Join(" ", ...)Combine the remaining parts with one space between them.
Do not use @rowname. Modern Designer reports WB0276 because that variable is no longer supported. For a list-driven roster, match the supported current-row identifier @rowcode to the source list's numeric Value, then read its Text. Also use suffix_text, not the categorical suffix code, when the output should show β€œJr.” or β€œIII.” Compilation message WB0276 β†—
Survey Solutions Designer showing the String variable full_name inside the members roster and an expression that matches member_fnames Value to the current rowcode before joining first, middle, last and suffix text.
One result per roster row. The indentation on the left shows that full_name is inside members. The expression on the right removes blank name parts before joining them with spaces.
Step 2 β€” join all roster-row names outside the roster
  1. Add a String variable named full_names_joined in the parent household section, after the roster. It must not be indented as a child of members.
  2. Use the expression below. It reads the full_name calculated for each roster row, removes any empty result, and joins the names with a comma and a space.
String.Join(", ",
    members
        .Where(p => !String.IsNullOrWhiteSpace(p.full_name))
        .Select(p => p.full_name))
Survey Solutions Designer showing the household-level String variable full_names_joined and an expression that selects nonblank full_name values from the members roster and joins them with commas.
One result for the household. Here p means one roster row at a time. Where keeps rows with a usable name, Select takes each row's full_name, and String.Join produces one display string such as John Banda, Mary Phiri Jr., Peter Mwale.
Step 3 β€” substitute the joined result into the confirmation question

Create or edit the categorical confirmation question members_confirm outside the roster. Insert %full_names_joined% wherever the generated list should appear:

Thanks for providing me the names. Please confirm that the following
members live together, eat together, and are current residentsβ€”that is,
members who are here now or traveling for less than three months:
<b>%full_names_joined%</b>.
Survey Solutions Designer showing the categorical members_confirm question with full_names_joined substituted in bold in the confirmation text and a compiled status of zero errors.
Display the completed list. Percent signs perform text substitution; the HTML <b>...</b> makes the generated names bold. The captured build compiles with 0 errors. Its six warnings still need to be opened and reviewed separately; zero errors does not mean warnings can be ignored.
The same pattern works for other roster items. Replace p.full_name with the row value you want to displayβ€”for example p.school_name or p.crop_nameβ€”while keeping the sequence Where β†’ Select β†’ String.Join. Change the first argument from ", " to another tested separator such as "; " when semicolons are easier to read.
Roster-list test: test one member; several members; a blank middle name; a blank suffix; an β€œOther” suffix; and a corrected or deleted list item. Confirm that every name contains only the expected spaces, that names appear once and in roster order, that the confirmation text refreshes, and that compilation has no WB0276 or other errors.

4. Demographics and age

Add gender, dob_known, dob, age_value, age_unit, calculated variable age_years, marital_status, and first_union_age inside the roster. A single Date question cannot store partial dates and special numeric missing codes cleanly, so explicitly ask whether the full date is known and use the age fallback when it is not.

Offer β€œDon't Know” and β€œRefuse” as numeric special values

For age_value, choose Numeric, check Integer, and use ADD SPECIAL VALUE to add -999 = Don't Know and -888 = Refuse to answer. These appear as selectable answers alongside ordinary numeric entry; the interviewer does not have to type the codes. Leave Non-negative unchecked when using these negative codesβ€”otherwise Designer reports WB0324.

Survey Solutions numeric age question editor with Integer selected, Non-negative unchecked, and special values minus 999 Don't Know and minus 888 Refuse to answer.
Age question in Designer. The two special values are entered under Value and Title. Their negative codes are deliberate and distinct from valid ages.
A special value is an answer, not a blank. It is exported as its numeric code and counts as answered in expressions. Every age range, calculation and eligibility rule must therefore exclude -999 and -888 explicitly. Do not interpret those codes as actual negative ages. Official special-values guide β†—

Validate the numeric question age_value first. This accepts the two special codes or an ordinary age from 0 to 110:

self == -999 || self == -888 || self.InRange(0,110)

For ordinary ages, enable the categorical age_unit question. If it uses 1 = days, 2 = months and 3 = years, put this second validation on age_unit to apply the narrower day and month limits:

(self == 1 && age_value.InRange(0,29)) ||
(self == 2 && age_value.InRange(0,11)) ||
(self == 3 && age_value.InRange(0,110))

Make age_unit critical when enabled. Use an error message such as The age does not fit the selected unit. Change the unit codes and limits if your protocol differs. Keeping the first rule on age_value independent of age_unit avoids a circular dependency.

dob enabled when:              dob_known == 1
age_value enabled when:        dob_known != 1
age_unit enabled when:         dob_known != 1 && age_value >= 0
marital_status enabled when:   age_years > 13
first_union_age enabled when:  marital_status >= 1 && marital_status <= 5

Use a nullable Long Integer calculated variable for age_years. With interview_date captured earlier, calculate completed years rather than dividing days by 365.25. The fallback below returns no age in years for a special value or unanswered age, zero for a valid age given in days or months, and the entered value for years:

dob.HasValue && interview_date.HasValue
? (long?)(interview_date.Value.Year - dob.Value.Year
  - (interview_date.Value.Date < dob.Value.Date.AddYears(
       interview_date.Value.Year - dob.Value.Year) ? 1 : 0))
: (!age_value.HasValue || age_value == -999 || age_value == -888
   ? (long?)null
   : (age_unit == 3 ? age_value
      : (age_unit == 1 || age_unit == 2 ? (long?)0 : null)))

Compile this expression in the selected Designer version and test it on the day before, the day of and the day after a birthday, as well as on leap day and for infants. Also select each special value in the Tester and confirm that age_years stays blank and age-gated questions do not appear.

5. Phone selection and member-level phone details

Create phone_status first so None, Don't know and Refuse remain explicit outcomes. When a household has phone owners, add a linked multi-select phone_owners sourced from members and limit it to three selections.

Do not use phone_owners as a roster source. In Survey Solutions, a linked multi-select question cannot trigger a roster. Keep the phone-detail questions inside the existing members roster and enable a Phone details subsection only for a selected member. This retains one stable roster identity for each person.

Inside members, add phone number, network, phone type, another-SIM indicator and second number. A linked multi-select stores roster identities rather than ordinary category codes. In the member roster, test whether the current row code occurs in that identity vector:

phone_owners enabled when:       phone_status == 1
Phone details subsection when:  phone_owners.Any(p => p[0] == @rowcode)
second_phone enabled when:       has_second_phone == 1

phone text validation:
Regex.IsMatch(self, @"^(0[0-9]{10}|-999|-888)$")

The vector shape depends on roster depth. The expression above is for this one-level roster; compile it in Designer and confirm it for selected and unselected members before deployment.

6. Education inside the member roster

Create an Education subsection inside members and enable the whole subsection with age_years >= 5. Keeping education in the existing roster avoids asking the interviewer to recreate or manually select the same eligible members.

VariableConditionImplementation note
school_attendEducation subsection enabledYes = 1, No = 2.
current_grade, school_typeschool_attend == 1Use the supplied grade and school-type codes.
not_attend_reasonschool_attend == 2 && age_years <= 24Ask only for non-attenders aged 5–24.
highest_gradeAll members aged 5+Reuse the grade codes plus 00 and 60.
completed_levelhighest_grade != 0 && highest_grade != 99Yes = 1, No = 2.

Finish with open text end_comments, closing static text, variable labels, and at least one critical check for required operational fields. Compile until Designer reports zero errors, then test each branch before import.

Part B β€” Upload the sample and run the PDS test

Return to the expandable assignment-name note in Step 2 if you need to check the difference between _responsible (the account login) and assigned_interviewer_name (the full name displayed in consent).

  1. Import the compiled questionnaire to the PDS as Version 1 using the Designer credentials that own or can view it.
  2. Download the embedded Survey Solutions assignments TAB. Its headers match the cover variables and it contains _responsible and _quantity.
  3. In Headquarters, open Survey Setup β†’ Questionnaires. Move the mouse pointer over the imported questionnaire row so it highlights, then click the questionnaire title or row. This opens its action menu; choose Upload assignments.
  4. Do not proceed if verification reports a mismatch. Check variable spelling, question types, UTF-8 text and whether every uploaded field is an identifying question.
  5. After β€œVerification Complete,” create the assignments. The TAB sends five cases directly to enum_01 and five directly to enum_02; the matching display names are preloaded in the same rows.
  6. As sup_train, verify that both interviewers received the expected cases. Do not reassign these consent-name practice cases unless you also have an approved way to keep the preloaded display name consistent.
  7. On Android, configure the Interviewer App with the PDS URL and enum_01, then synchronize. In a browser, sign in as enum_02 and open Web Interviewer.
Easy-to-miss interface behavior: Upload assignments is not a permanently visible button on the Questionnaires page. The command appears in the questionnaire's action menu only after you point to and click the imported questionnaire row. Do not go to the general Assignments list and look for the batch-upload command there.
PDS general Assignments page showing a New Assignment button but no Upload assignments command.
Not here. The general Assignments page lists and filters existing assignments and exposes NEW ASSIGNMENT, but it does not show the questionnaire-specific batch-upload command.
PDS Questionnaires page with the imported questionnaire row selected and its action menu open, showing Upload assignments.
Use this hidden action menu. Return to Questionnaires, hover over the imported questionnaire row and click it. The menu opens above the row; choose Upload assignments.
PDS Creating multiple assignments page showing the questionnaire version and default responsible selector.
Confirm the target questionnaire and version. The default responsible is used only for rows whose batch file does not provide _responsible.
PDS assignment upload page comparing identifying-data TAB upload with identifying-and-collected-data ZIP upload.
Choose the correct upload mode. For this capstone's Cover and interview-level assignment file, use UPLOAD .TAB FILE under Identifying data only. The ZIP option is for multi-level preloading, including subordinate rosters.

When an assignment column cannot be matched: PL0003

The upload below stopped before creating any assignments. HQ reported PL0003: Column cannot be mapped to any question in the questionnaire and identified barangay as the unmatched header. This means the questionnaire version shown on the upload screen has no matching question at the level expected by this file. In this case, a Cover question's variable name was misspelled; the same error can occur when a spreadsheet contains a column that was never added to the questionnaire.

PDS batch assignment upload rejected with PL0003 because column barangay cannot be mapped to a question in questionnaire version 1; no assignments were created.
Read both the code and the named column. PL0003 points to the header barangay, not to a value in a particular household row. The message No assignments were created means the file can be corrected and submitted again without duplicating this failed batch.
  1. Compare the exact headers. Download the assignment .tab template for the same imported questionnaire version. Compare its first row with the first row of your upload, especially barangay. Check spelling, underscores, capitalization, and accidental spaces. Check the question's variable name in Designer, not its question text or variable label.
  2. If the Cover variable name is wrong: correct it in Designer, save and compile the questionnaire, then import the revised questionnaire into Headquarters. Download a fresh assignment template for that new version and upload to that version. Editing Designer alone does not change an already imported Version 1.
  3. If the TAB contains an unwanted extra column: remove that column from the upload file and save it as tab-delimited UTF-8 text. Keep the intended Cover fields and any valid special columns such as _responsible and _quantity.
  4. Retry verification. Confirm that Headquarters reports verification complete before creating the assignments. If another header is named, resolve that mismatch in the same way.
Version check: a correctly spelled barangay column still fails against an older imported version if that version contains the misspelling. Make the assignment file and the selected HQ questionnaire version agree; use the freshly downloaded template as the reference. Official PL0003 guidance β†—

Batch assignment upload β†—

Required test scenarios

ScenarioData to enterExpected evidence
Consent refusalconsent = 2Substantive sections remain disabled; outcome is recorded; interview can close under the chosen critical-rule policy.
Attending childHead, spouse and child aged 10; child attends Grade 4.Exactly one head; marital questions follow age; education appears for age 5+; current grade and school type appear.
Non-attending youthMember aged 17 with school_attend = 2.Reason for non-attendance appears; current grade and school type remain disabled.
Older non-attenderMember aged 30 with school_attend = 2.Non-attendance reason remains disabled because age is above 24; attainment questions still appear.
Unknown or refused ageChoose each age_value special value: -999, then -888.Each is a selectable, recorded answer; age_years remains blank and age-dependent questions do not appear.
Invalid numeric ageTry a negative ordinary age or a value beyond the selected unit's limit.The age validation flags it; only the defined negative special codes are accepted.
Phone validationEnter an invalid number, then a valid 11-digit number beginning with 0.Validation triggers, correction clears it, and phone details exist only for selected members.
Roster lifecycleEnter answers for three people, then remove the second list item using synthetic data.Observe which roster row and answers are removed; restore the case and document why list deletion requires care.

Required review cycle and evidence

Workflow evidence
  1. Complete and synchronize one Android interview.
  2. Complete one Web Interviewer case.
  3. As supervisor, leave a question-level comment and reject one interview.
  4. As interviewer, synchronize, correct, comment and complete again.
  5. Approve as supervisor, then approve as Headquarters.
Data evidence
  1. Export main and roster data in TAB or Stata format.
  2. Verify all ten caseid values remain strings.
  3. Join members to the household file using the exported parent identifier.
  4. Confirm treatment and location preloads match the embedded sample.
  5. Inspect paradata for answer changes, completion, synchronization and review actions.
Practice completion rule: retain screenshots or notes showing zero Designer errors, successful assignment verification, both client modes, one rejection/correction cycle, one approved interview, and a successful parent–roster export join. Download all evidence before the PDS reaches its deletion date.

Independent extension: household remittance survey

Build a household survey that identifies Gulf-linked households, lists members, identifies a remittance recipient, records recent transfers, and captures sender contact permission.

Extension A β€” Designer

  1. Cover: district, EA, household ID.
  2. Consent section.
  3. Household roster: name, age, sex, relationship.
  4. Migration/remittance screener.
  5. Linked question: main remittance recipient.
  6. Transfer roster: amount, method, fee, exchange rate, date.
  7. Sender contact and permission.
  8. At least 5 validations and 5 enabling conditions.

Extension B β€” Operations

  1. Import questionnaire to HQ.
  2. Create supervisor/interviewer accounts.
  3. Create sample assignments with identifying data.
  4. Sync an interviewer device.
  5. Complete, reject, correct and approve one interview.
  6. Export main + roster data.
  7. Merge roster records to household identifiers in Stata/R.
  8. Inspect paradata for the test interview.
  9. Optional advanced task: design how a follow-up questionnaire would receive fresh data from this surveyβ€”assignment preload vs lookup table vs external API/databaseβ€”and explain why.
Graduation test rubric (self-check): For a missing interview, your explanation must cover questionnaire version β†’ assignment β†’ responsible user β†’ synchronization β†’ interview status. For a missing/disabled question, it must cover variable scope β†’ enabling condition β†’ roster context β†’ validation/answer state.

Final mandatory quiz

Attempts remaining: 3

Answer all seven items. A perfect score unlocks course completion. After three complete attempts, the answer key is revealed and completion unlocks.

1. XLSForm relevance maps most closely to:
2. A repeated household-member block is primarily modeled as:
3. The syntax for equality is typically:
4. The object that defines responsibility for survey work is:
5. A linked question is especially useful for:
6. Which file should you upload for the ten practice assignments?
7. Which statement about a Personal Demo Server is correct?

Rapid-reference flashcards

Apply it to the practice questionnaire. Compare your form with the respondent-facing Practice PDF. Complete any unfinished fields and branches using the instructions in this course, compile with zero errors, review warnings, test special values and roster names, then inspect the assignment and export results.