> ## Documentation Index
> Fetch the complete documentation index at: https://docs.expectedparrot.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Humanize schema

> Reference for the humanize schema: a dict that configures how survey questions are presented in web surveys with human respondents.

When creating a human survey or preview (e.g. `survey.humanize()` or `survey.preview()`), you can pass a **humanize schema** to control styling and optionality per question. The schema is validated against your survey before use.

## Fields

<ParamField path="questions" type="dict" required={false}>
  Map of question name (string) -> `HumanizeQuestionSchema`.
</ParamField>

<ParamField path="survey" type="dict" required={false}>
  Optional survey-level options (e.g. custom CSS).

  <Expandable title="properties">
    <ParamField path="survey.custom_css" type="string" required={false}>
      Optional custom CSS for the survey. Can be `null` or omitted. See [Custom CSS](/en/latest/custom_css) for the `edsl-*` CSS classes and HTML structure per question type.
    </ParamField>
  </Expandable>
</ParamField>

## HumanizeQuestionSchema (per question)

Each `questions[question_name]` value is a `HumanizeQuestionSchema` object.

<ParamField path="optional" type="boolean" default={false}>
  Whether the question is optional. Default is `false` when omitted.

  **Supported question types:** `free_text`, `budget`, `checkbox`, `file_upload`, `interview`, `likert_five`, `linear_scale`, `list`, `matrix`, `multiple_choice`, `multiple_choice_with_other`, `numerical`, `rank`, `top_k`, `yes_no`.

  Note: `optional` currently has no effect for `interview` and `rank`.
</ParamField>

<ParamField path="format" type="dict" required={false}>
  Display format. Varies by question type:

  * **MC-style** (`likert_five`, `linear_scale`, `multiple_choice`, `yes_no`): `"radio"` (list of radio buttons) or `"dropdown"` (single `<select>`). Default is `"radio"`. Use `{"type": "radio"}` or `{"type": "dropdown"}`.
  * **Numerical** (`numerical`): `"input"` (number input field) or `"slider"` (range slider). Default is `"input"`. Use `{"type": "input"}` or `{"type": "slider", "min": 0, "max": 100, "step": 1}`. For slider, `min` must be less than `max`, `step` must be positive and not exceed `(max - min)`.

  Other question types do not support `format`.
</ParamField>

<ParamField path="custom_validation" type="dict" required={false}>
  **Multiple choice only** (`multiple_choice`). Optional custom validation rules.

  <Expandable title="properties">
    <ParamField path="select_exact_answer" type="string" required={false}>
      If set, the human survey will require the respondent to select this exact option. Use for attention checks or forcing a particular choice. Omit or set to `null` to allow any selection.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="interview_mode" type="string" default="text">
  **Interview only** (`interview`). Controls the input mode offered to respondents.

  * `"text"` — text-based interview (default).
  * `"voice"` — voice-based interview.
  * `"both"` — respondent can choose between text and voice.
</ParamField>

<ParamField path="voice_interview_config" type="dict" required={false}>
  **Interview only** (`interview`). Optional configuration for voice-mode interviews. Ignored when `interview_mode` is `"text"`.

  <Expandable title="properties">
    <ParamField path="language" type="string" default="english">
      The spoken language for the voice interview. A lowercase language id from the supported set below. Case and surrounding whitespace are normalized (e.g. `"  French "` becomes `"french"`); omit or set to `null` to use the default (`"english"`). An unsupported value raises a validation error.

      **Supported languages:** `english`, `french`, `german`, `spanish`, `portuguese`, `chinese`, `japanese`, `hindi`, `italian`, `korean`, `dutch`, `polish`, `russian`, `swedish`, `turkish`, `tagalog`, `bulgarian`, `romanian`, `arabic`, `czech`, `greek`, `finnish`, `croatian`, `malay`, `slovak`, `danish`, `tamil`, `ukrainian`, `hungarian`, `norwegian`, `vietnamese`, `bangla`, `thai`, `hebrew`, `georgian`, `indonesian`, `telugu`, `gujarati`, `kannada`, `malayalam`, `marathi`, `punjabi`, `multilingual`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="text_interview_config" type="dict" required={false}>
  **Interview only** (`interview`). Optional configuration for text-mode interviews. Ignored when `interview_mode` is `"voice"`.

  <Expandable title="properties">
    <ParamField path="language" type="string" default="english">
      The language for the text interview. A lowercase language id from the supported set below. Case and surrounding whitespace are normalized (e.g. `"  French "` becomes `"french"`); omit or set to `null` to use the default (`"english"`). An unsupported value raises a validation error.

      **Supported languages:** `english`, `french`, `german`, `spanish`, `portuguese`, `chinese`, `japanese`, `hindi`, `italian`, `korean`, `dutch`, `polish`, `russian`, `swedish`, `turkish`, `tagalog`, `bulgarian`, `romanian`, `arabic`, `czech`, `greek`, `finnish`, `croatian`, `malay`, `slovak`, `danish`, `tamil`, `ukrainian`, `hungarian`, `norwegian`, `vietnamese`, `bangla`, `thai`, `hebrew`, `georgian`, `indonesian`, `telugu`, `gujarati`, `kannada`, `malayalam`, `marathi`, `punjabi`, `multilingual`.
    </ParamField>

    <ParamField path="interviewer_name" type="string" required={false}>
      Label shown above the interviewer's messages. Must be between 1 and 200 characters. Defaults to `"AI Interviewer"`. Omit or set to `null` to use the default.
    </ParamField>

    <ParamField path="end_interview_message" type="string" required={false}>
      Message shown to respondents when the interview ends. Must be between 1 and 2500 characters. Defaults to `"Thank you for your time. This interview has been ended. Your responses have been recorded and will be valuable for our research."` Omit or set to `null` to use the default.
    </ParamField>

    <ParamField path="checklist" type="dict" required={false}>
      A checklist of items the interviewer works through during the interview. Omit or set to `null` to run without a checklist.

      <Expandable title="properties">
        <ParamField path="initial" type="dict" required={false}>
          The starting set of checklist items, written by the survey author. Defaults to an empty list.

          <Expandable title="properties">
            <ParamField path="type" type="string" default="manual">
              Must be `"manual"`.
            </ParamField>

            <ParamField path="items" type="array" default="[]">
              The list of checklist items. Each item is an object with:

              * `id` (string, required) — a unique identifier, 1–64 characters, restricted to letters, digits, underscores, and hyphens (regex pattern `^[A-Za-z0-9_\-]+$`). Item ids must be unique within the checklist.
              * `label` (string, required) — participant-facing text shown in the checklist UI, 1–200 characters.
              * `instructions` (string, required) — model-facing text describing the condition under which the model should check the item off, 1–2000 characters.
            </ParamField>
          </Expandable>
        </ParamField>

        <ParamField path="participant_visibility" type="string" default="visible">
          Whether the participant sees the checklist. The model always sees it (it's in the system prompt) regardless of this setting.

          * `"visible"` — the floating checklist panel is shown to the participant during the interview (default).
          * `"hidden"` — the participant never sees the checklist; it acts as a pure interviewer instrument (status is still tracked internally).
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField path="structured_questions" type="dict" required={false}>
      Lets the interviewer ask multiple-choice and numerical questions mid-interview, rendered as input widgets rather than prose. Omit or set to `null` to disable structured questions.

      <Expandable title="properties">
        <ParamField path="allowed_types" type="array" default={["multiple_choice", "numerical"]}>
          Which question types the model may use. Each entry must be `"multiple_choice"` or `"numerical"`. Defaults to both. Narrowing this restricts the model to the listed types. Must be non-empty (omit `structured_questions` instead of passing an empty list) and must not contain duplicates.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField path="end_policy" type="dict" required={false}>
      Controls how the interview is allowed to end. Omit to use the default (`{"control": "respondent"}`).

      <Expandable title="properties">
        <ParamField path="control" type="string" default="respondent">
          Who decides when the interview is over.

          * `"respondent"` — the participant can end at any time; the End Interview button is always available (default). No other fields apply.
          * `"interviewer_gated"` — the End Interview button is hidden until the model has signalled that its goals are met; only then can the participant end. This mode also accepts `interview_marked_complete_message`.
        </ParamField>

        <ParamField path="interview_marked_complete_message" type="dict" required={false}>
          **`interviewer_gated` only.** A message to surface on the turn the interviewer first marks the interview complete. If omitted or set to `null`, the LLM will generate text for that turn instead.

          <Expandable title="properties">
            <ParamField path="message" type="string" required={true}>
              The message text. Must be between 1 and 2500 characters.
            </ParamField>

            <ParamField path="method" type="string" default="replace">
              How `message` relates to the model's text on the marking turn.

              * `"replace"` — show this `message` for that one turn instead of using a model to generate text.
            </ParamField>
          </Expandable>
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="comment" type="dict" required={false}>
  Optional comment input shown with the question.
  Submitted comment text appears in survey results under `comment.{question_name}_comment`.

  **Supported question types:** `free_text`, `budget`, `checkbox`, `likert_five`, `linear_scale`, `list`, `matrix`, `multiple_choice`, `multiple_choice_with_other`, `numerical`, `rank`, `top_k`, `yes_no`.

  <Expandable title="properties">
    <ParamField path="label" type="string" required={true}>
      Label text displayed for the comment field.
    </ParamField>
  </Expandable>
</ParamField>

## Example

```python theme={null}
from edsl import Survey, QuestionFreeText, QuestionMultipleChoice

survey = Survey([
    QuestionMultipleChoice(
        question_name="rating",
        question_text="Rate your experience.",
        question_options=["Good", "OK", "Bad"],
    ),
    QuestionFreeText(
        question_name="feedback",
        question_text="Any additional feedback you'd like to share?",
    ),
])

humanize_schema = {
    "questions": {
        "rating": {
            "optional": False,
            "format": {"type": "dropdown"},
            "comment": {"label": "Why did you choose this rating?"},
        },
        "feedback": {"optional": True},
    },
    "survey": {"custom_css": None},
}

# Validation runs before creating the human survey
survey.humanize(humanize_schema=humanize_schema)
```

## Validation

If the schema is invalid, Expected Parrot raises `HumanizeSchemaValidationError`. Common causes:

* A key in `questions` is not a question name in the survey, or is an instruction.
* A question's type is not supported for humanize schema (e.g. `demand`, `dropdown`).
* A question's entry has the wrong shape for its type (e.g. wrong field types or extra fields that aren’t allowed).
* Top-level structure is invalid (e.g. `questions` not a dict, or an entry not a dict).

You can also pass the schema to `survey.preview(humanize_schema=...)` to get a preview URL.

Ensure your humanize schema matches the parameters above for each question type in your survey.
