# Qube Survey Script (QSS) Authoring Guide for LLMs Compact authoring rules for Large Language Models writing Qube Survey Script (`.qube`) questionnaires. The executable GOOD examples are parser- and semantic-checker-gated; broader language claims remain subordinate to the normative QSS proposal, grammar, standard library, and acceptance documents in this folder. --- ## 1. Instrument Mental Model QSS is an indentation-insensitive, line-oriented survey definition language with Markdown prose and a typed expression language. ### Core Invariants: 1. **One Survey Header:** A questionnaire must begin with `@survey @language @mode `. 2. **Terminal Outcome Required:** An interview must contain at least one terminal outcome `@outcome ` (e.g. `@outcome complete`). 3. **Required by Default:** All questions and struct fields are mandatory unless explicitly annotated with `@optional`. 4. **Questions are Entities:** Every question has a stable snake_case or alphanumeric identifier (`@question `). 5. **Answers are Values:** Selection questions own inline `@options` by default and reference named domains when options are genuinely shared; free text, numbers, and dates use typed contracts. Operations over answers propagate absence transparently. 6. **No Shared Intermediate Representation:** QSS and ODIN are independent engines converging on `QuestionPage` v2 and `qube-case` event logs. QSS emits `QuestionPage` v2 natively. --- ## 2. Compact Rules Directory ### [QSS-LLM-001] Declare the survey header and terminal outcome Every script must declare `@survey` with language and mode, and define at least one terminal `@outcome`. ```qss -- GOOD: @survey customer_feedback @language en @mode cawi @question q_intro display Welcome to our survey. @outcome complete Thank you for your participation. ``` ```qss -- WRONG: Missing @survey header or missing terminal outcome @question q_intro display Welcome to our survey. ``` --- ### [QSS-LLM-002] Selection questions require an `@options` domain Single-choice (`single`) and multiple-choice (`multi`) questions must bind to an `@options` domain. Define the domain inline when it is used by only one question. Give it a name and declare it separately only when it is genuinely reused or needs an independent shared identity. Option labels are unquoted Markdown text: quotation marks in a label are rendered literally and must appear only when the respondent should see them. ```qss -- GOOD: one-off options stay with their question @question q_sat single How satisfied are you with our service? @options 1: Very dissatisfied 2: Dissatisfied 3: Neutral 4: Satisfied 5: Very satisfied ``` ```qss -- WRONG: Selection question missing @options binding @question q_sat single How satisfied are you with our service? ``` --- ### [QSS-LLM-003] Multi-choice questions and `@exclusive` constraints In a `multi` question, exclusive options (such as "None of the above" or "Don't know") must carry `@exclusive`. An exclusive option must be the only member of its selection. The browser clears conflicting choices when the respondent clicks an option. Whole-cell proposals, direct runtime answers and trusted replay reject a mixed exclusive/ordinary selection before saving it; they cannot infer which choice was clicked last. ```qss -- GOOD: @options devices 1: Smartphone 2: Laptop 3: Tablet 99: None of these @exclusive @question q_devices multi Which devices do you own? @options devices ``` --- ### [QSS-LLM-004] Attached open verbatims with `@specify` To capture an open text explanation for a specific category (like "Other, please specify"), attach `@specify` to the option. ```qss -- GOOD: @options brands 1: Brand A 2: Brand B 98: Other @specify @question q_brand single Which brand do you prefer? @options brands ``` --- ### [QSS-LLM-005] Non-read / Spontaneous options with `@noread` For interviewer-administered modes (CATI/CAPI) where an option should not be read aloud to the respondent, mark the option with `@noread`. It remains selectable by the interviewer. ```qss -- GOOD: @options awareness 1: Yes 2: No 99: Don't know / Not sure @noread @exclusive @question q_aware single Have you heard of our new initiative? @options awareness ``` --- ### [QSS-LLM-006] Numeric and Text scalar contracts Use `integer` for whole numbers, `decimal` for continuous numbers, `text` for strings, `boolean` for yes/no, and `display` for informational screens. Use `@multiline` for long verbatim inputs. ```qss -- GOOD: @question q_age integer How old are you? @question q_feedback text @multiline @optional Please share any additional comments: ``` ```qss -- WRONG: Using legacy ODIN types like NUMBER or ALPHA instead of QSS contracts @question q_age NUMBER How old are you? ``` --- ### [QSS-LLM-007] Temporal contracts (`date`, `time`, `datetime`, `duration`) Use explicit temporal types for dates, time of day, timestamps, and durations. ```qss -- GOOD: @question q_dob date What is your date of birth? @question q_call_time time What time is best to call? ``` --- ### [QSS-LLM-008] Grid / Matrix questions with `@rows` and `@columns` To ask a question across a battery of items (a grid / matrix), attach `@rows ` and optionally `@columns ` to the question contract (`single`, `integer`, `decimal`, etc.). ```qss -- GOOD: Single-choice rating grid @options attributes 1: Speed 2: Reliability 3: Value for money @options ratings 1: Poor 2: Fair 3: Good 4: Excellent @question q_grid single Please rate our product on the following attributes: @rows attributes @options ratings ``` --- ### [QSS-LLM-009] Constant sum / Allocation questions (`allocation`, `@rows`, `@values`, `@total`) For numeric point-distribution tasks across items, use `allocation` with `@rows`, `@values `, and `@total `. The runtime enforces the exact sum. ```qss -- GOOD: @options features 1: Performance 2: Design 3: Battery life @question q_points allocation Please distribute 100 points across these features based on importance: @rows features @values integer @total 100 ``` --- ### [QSS-LLM-010] Composite data with `@struct` and `@field` Use `@struct` with one or more `@field` declarations to create composite multi-field forms on a single screen. Fields support constraints like `@range`, `@length`, `@pattern`, `@min`, and `@max`. ```qss -- GOOD: @struct person @field first_name text @length 60 First name @field age integer @range 1..120 Age @question q_person struct person Please enter your details: ``` --- ### [QSS-LLM-011] Multi-question screens with `@page` ... `@end_page` Group multiple questions to appear on the same screen with `@page `. Questions on a single page are answered and committed as a unit. **A question on a page may not condition on another question on the same page**, because at the moment the screen is displayed, neither has been answered. ```qss -- GOOD: @page contact_details Please provide your contact information: @question q_first_name text What is your first name? @question q_last_name text What is your last name? @end_page ``` --- ### [QSS-LLM-012] Iteration over dynamic selections with `@foreach` Use `@foreach in ` ... `@end_foreach` to loop over selected items. The source must be option-shaped (a domain, a multi-selection answer `answers.`, or `order(...)`). Inside the body, `{var.id}` is the option id, `{var.label}` is its label, and `{var.position}` is its 1-indexed position. Leaving a loop with `@route` or `@goto` abandons the remaining members. ```qss -- GOOD: @options brands 1: Brand A 2: Brand B 3: Brand C @options rating 1: Poor 2: Fair 3: Good 4: Excellent @question q_used multi Which brands have you used in the past month? @options brands @foreach brand in answers.q_used @question q_rating single How would you rate {brand.label}? @options rating @end_foreach ``` ```qss -- WRONG: Using legacy @repeat syntax instead of @foreach @repeat brand in answers.q_used @question q_rating single Rate brand @end_repeat ``` --- ### [QSS-LLM-013] Conditional applicability with `@if` Attach `@if ` directly to a question declaration (or `@field` or `@route` rule) to control whether it is asked. When an expression reads an unanswered question, absence propagates and the condition evaluates to not-true. ```qss -- GOOD: @question q_has_car boolean Do you own a car? @question q_car_age integer @if answers.q_has_car == true How old is your car in years? ``` ```qss -- WRONG: Using wrapping @if ... @end_if blocks around top-level questions @if answers.q_has_car == true @question q_car_age integer How old is your car? @end_if ``` --- ### [QSS-LLM-014] Hard validations and soft confirmations (`@validate`, `@severity`) Use `@validate ` to assert an invariant. The validation message is written as Markdown prose directly on the line below. `@severity error` (the default) blocks progress; `@severity warning` or `confirm` is a soft confirmation that can be acknowledged. Use `@focus ` to highlight the offending input. ```qss -- GOOD: @question q_age integer What is your age? @validate age_range answers.q_age >= 18 and answers.q_age <= 120 @severity error @focus q_age Age must be between 18 and 120. @validate age_unusual answers.q_age < 100 @severity confirm @focus q_age You entered an age over 100. Please confirm this is correct. ``` --- ### [QSS-LLM-015] Routing and branching with `@route` and `@goto` Use `@route ` with `if -> ` (evaluated first-match-wins) and optional `else -> ` for conditional branching. Use `@goto ` for unconditional jumps. Conditional `@goto @if ...` is forbidden (QUBE0601). Sections may explicitly opt into intermediate snapshots with `@section demographics @checkpoint`. The modifier is bare (no value), belongs only on the primary section declaration, and is absent by default. After an accepted forward response leaves that section, the host may upload a background snapshot; newer snapshots replace older ones. Ordinary pages, Back, restore and replay do not upload. Re-answering after Back may refresh the snapshot on leaving the section again. Skipped or empty sections do not trigger one. Terminal outcomes use the final submission instead. The runtime only reports the section reached by actual routing; it does not perform network operations. This is not verified answer export or a cross-device recovery declaration. An outer marked section may contain repeats. A marked section declared inside `@foreach` is currently rejected; checkpoint boundaries within loop bodies are deferred. ```qss -- GOOD: Conditional routing / screenout @question q_consent boolean Do you agree to participate in this study? @route consent_check if answers.q_consent != true -> screenout else -> next_step @section next_step Continuing survey... @outcome screenout Thank you for your time, but this study requires consent. @outcome complete Thank you for completing the survey. ``` ```qss -- WRONG: Conditional @goto is rejected; use @route instead @goto screenout @if answers.q_consent != true ``` --- ### [QSS-LLM-016] Dynamic text piping and interpolation with `{...}` Interpolate answer values, loop variables, and expressions inside question Markdown using `{...}`. References are statically type-checked at build time. ```qss -- GOOD: @question q_name text What is your name? @question q_welcome display Hello, {answers.q_name}! Welcome to the study. ``` --- ### [QSS-LLM-017] Option domain ordering and randomization with `@order` Use `@order order().()` on the question to control presentation order deterministically per case seed. Supported operations include `.shuffle()`, `.shuffle(group)`, `.shuffle_groups()`, `.reverse()`, `.reverse_if(cond)`, `.first(...)`, `.last(...)`, `.fixed(opt, pos)`, and `.stop_at(...)`. Pinning (`.first`, `.last`) always takes precedence. ```qss -- GOOD: @options fruit 1: Apple 2: Banana 3: Cherry 99: None of these @exclusive @question q_fav single Which fruit do you prefer? @options fruit @order order(fruit).shuffle().last(fruit.get(99)) ``` --- ### [QSS-LLM-018] Option groups with `@group` Organize options under visual headings using `@group : "Heading"`. Pinning an option that belongs to a group tears it out of that group and is rejected at projection; declare options you intend to pin outside any group. ```qss -- GOOD: @options vehicles @group cars: "Passenger Cars" 1: Sedan 2: Hatchback @group commercial: "Commercial Vehicles" 3: Van 4: Truck @question q_veh single Select your vehicle type: @options vehicles ``` --- ### [QSS-LLM-019] Static constants (`value`) vs Dynamic computations (`function`) Use `value = ` to declare static package-level constants (evaluated once; cannot depend on case state or `answers`). To declare dynamic calculations over answers, define a `function () returns answer() = ` (since operations over answers return lifted `answer(...)` values) or compute expressions directly inside text interpolation `{...}`. Statements (`let`, `if`, `for`) live only inside function bodies (`end`); questionnaire-level assignment does not exist (a bare `x = 1` outside a function is Markdown prose and would be displayed to the respondent). ```qss -- GOOD: Static constant value MAX_RECORDS = 50 -- GOOD: Dynamic calculation over answers @question q_income decimal What is your monthly income? function annual_income() returns answer(decimal) = answers.q_income * 12 @question q_show_annual display Your estimated annual income is {annual_income()}. ``` ```qss -- WRONG: Top-level `value` cannot read `answers` value annual_income = answers.q_income * 12 ``` --- ### [QSS-LLM-020] Check before claiming success Always verify that generated QSS code parses and type-checks cleanly through `qss-parser` / `qss.check` before presenting it as valid. ### [QSS-LLM-021] Read untrusted launch strings explicitly Use `launch.param("id")` and `launch.param("gkz")` for hosted launch text. Names must be literal and case-sensitive. Results are `optional(text)`: missing is `none`, blank is empty text, and leading zeros are preserved. Do not assume a fixed identifier length or convert identifiers to numbers. These values are untrusted data, never identity or access authority. Start freezes them; URL edits on resume are ignored. See the standard library for bounds and retention. ```qss -- GOOD: Read source-declared strings; preserve the intentional introduction. @question intro display Interview {launch.param("id")} in municipality {launch.param("gkz")}. @question missing_id display @if launch.param("id") == none The link did not include an interview identifier. ```