> ## 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.

# Surveys

> Assemble questions, control survey flow, and prepare surveys for AI or human respondents.

A `Survey` is your research instrument: it groups questions and defines their order, branching, and dependencies. It can collect a rating and explanation, apply several codes to a document, or guide a longer interview. The survey holds the questions and rules; completed answers belong to a [Results](https://docs.expectedparrot.com/en/latest/results.md) object.

You can reuse the same survey with different scenarios, agent profiles, or models, or publish it for human participants. These choices determine who answers and what material they see without requiring you to rewrite the instrument.

To transform a survey into structured population-level response estimates, see [Meta-surveys](https://docs.expectedparrot.com/en/latest/meta_surveys.md). This converts supported question types, preserves simple skip conditions in the generated prompts, and optionally uses a fixed theme codebook for free-text items.

For [data labeling](https://docs.expectedparrot.com/en/latest/guides/data_labeling.md), a survey might ask about each item's topic, sentiment, and urgency; the answers become its labels. An exploratory survey can also help you discover useful categories before you construct a structured labeling survey.

## Assemble questions

Construct questions before passing them to `Survey([...])`. Each question must have a unique name. See [Questions](https://docs.expectedparrot.com/en/latest/questions.md) for choosing types; the survey controls how those questions work together.

## Logic, piping, and memory

This example stops after the first question for respondents who have not used a feature. Otherwise, it pipes their description into a follow-up and supplies the earlier answer as memory. Constructing the survey makes no model calls.

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

used = QuestionYesNo(question_name="used", question_text="Have you used the export feature?")
problem = QuestionFreeText(
    question_name="problem",
    question_text="What problem, if any, did you encounter when exporting?",
)
followup = QuestionFreeText(
    question_name="followup",
    question_text="What would resolve this problem: {{ problem.answer }}?",
)
survey = (
    Survey([used, problem, followup])
    .add_stop_rule(used, "{{ used.answer }} == 'No'")
    .add_targeted_memory(followup, problem)
)
```

A stop rule is evaluated after its question. A skip rule can bypass an individual question before it is asked. Write expressions using named answers, and test each possible route. Piping inserts an answer in question text; memory includes a previous question and answer in the context supplied to the model.

See [Survey logic and piping](https://docs.expectedparrot.com/en/latest/guides/survey_logic.md) for testing routes and [Survey messages](https://docs.expectedparrot.com/en/latest/survey_messages.md) for instructions shown between questions.

## Prepare a run

Combine a survey with scenarios, agents, and models using `by()` to construct a [Jobs](https://docs.expectedparrot.com/en/latest/jobs.md) object. For example, `survey.by(messages).by(model)` prepares one interview per message scenario; calling `.run()` collects the answers. Use `survey.run()` when you want to ask it once with the default model and no custom profiles or scenarios. See [Model access](https://docs.expectedparrot.com/en/latest/api_keys.md) for connecting a model.

For human participants, use [Human studies](https://docs.expectedparrot.com/en/latest/guides/human_studies.md). A web survey requires an Expected Parrot account.

## Create a visual review survey

Use `create_review_survey()` when reviewers need to inspect how every question
looks before an instrument is fielded. It renders each source question as an HTML
card, captures the card as an image, and creates a second survey containing:

* an optional reviewer-name question;
* an approval question for every source question; and
* a conditional text question when the reviewer reports an issue.

The method returns a review `Survey` and one `Scenario` containing all screenshots.
Keep them together when previewing, running, or publishing the review:

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

source = Survey.example()
review, screenshot_scenario = source.create_review_survey(
    output_dir="review-screenshots",
)

info = review.by(ScenarioList([screenshot_scenario])).humanize(
    human_survey_name="Questionnaire review",
    scenario_list_method="single_scenario",
)
print(info["respondent_url"])
```

`single_scenario` is important: the review instrument uses one scenario containing
every image, rather than one respondent assignment per source question. The image
is included in both the approval and feedback questions so the reviewer retains
visual context after selecting the issue option.

Automatic screenshots require Playwright and Chromium:

```bash theme={null}
pip install playwright
playwright install chromium
```

In a build system that captures screenshots separately, pass the paths in source
question order. This path does not require Playwright:

```python theme={null}
review, screenshot_scenario = source.create_review_survey(
    screenshot_paths=["q0.png", "q1.png", "q2.png"],
)
```

### Customize the review instrument

The first value in `approval_options` means approval and causes the associated
feedback question to be skipped. The second value means an issue was found.
Custom text templates may use `{question_name}`, `{question_number}`, and
`{screenshot}`. Always include `{screenshot}` when the image should be visible.

```python theme={null}
review, screenshot_scenario = source.create_review_survey(
    screenshot_paths=["q0.png", "q1.png", "q2.png"],
    reviewer_question_text=None,
    approval_options=("Approve", "Needs revision"),
    review_question_text=(
        "Review question {question_number} ({question_name}).\n\n{screenshot}"
    ),
    feedback_question_text=(
        "What should change, and why?\n\n{screenshot}"
    ),
    question_name_prefix="wording_review",
    screenshot_key_prefix="question_card",
)
```

Use prefixes when combining generated review questions with another survey or
when the default names would collide. The generated survey remains an ordinary
`Survey`: you can edit questions, add instructions, change routing, apply a
Humanize schema, or run it with agents before asking people to review it.
Set `reviewer_question_name` when identity should be collected under a field name
other than `reviewer_name`.

## Edit and inspect a survey

Access a question by name with `survey.get("feedback")` or `survey["feedback"]`. Use `survey.question_names` to inspect the order. To revise wording while keeping the original survey:

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

survey = Survey([
    QuestionFreeText(question_name="feedback", question_text="What should improve?"),
])
revised = survey.with_edited_question(
    "feedback", {"question_text": "What one change would help you most?"}
)
renamed = revised.with_renamed_question("feedback", "priority")
renamed.codebook()
```

`with_renamed_question()` also updates references to the question. `survey.rename({"old_name": "new_name", ...})` renames multiple questions. `codebook()` returns a dictionary of names and question text.

| Task | Method and behavior |
| - | - |
| Move or remove a question | `survey.move_question("feedback", new_index=0)` and `survey.delete_question("feedback")` modify the survey. |
| Build a smaller questionnaire | `survey.subset("q1", "q2")` keeps those questions; `survey.drop("q3")` removes named questions. Both return a new Survey. Reapply and check routing, instructions, and memory for the new instrument. |
| Inspect question definitions as rows | `survey.to_dataset()` or `survey.to_scenario_list()`; `survey.select("question_name", "question_text")` selects columns of a table, not a smaller Survey. |
| Inspect routing | `survey.show_rules()` displays rules; `survey.show_flow()` renders a flow diagram and may require Graphviz. |
| Export for review | `survey.to_markdown()` returns text; `survey.to_docx("survey.docx")` and `survey.to_html(filename="survey.html")` return FileStore objects and write the files. |
| Prepare execution without added components | `survey.to_jobs()` returns Jobs. `survey.run(...)` and `await survey.run_async(...)` execute the survey with run options. |

For grouped human survey pages, use `survey.add_question_group(start_question, end_question, group_name)`; `get_question_group(question)` reports a question's group. See [Question groups](https://docs.expectedparrot.com/en/latest/humanize_groups.md) for grouping constraints.

## Import, save, and reuse

Use the [Importing data](https://docs.expectedparrot.com/en/latest/guides/importing_data.md) guide to load data for questions and respondent profiles. Review imported questionnaire text, answer types, and branching before fielding it. See the [Builder guide](https://docs.expectedparrot.com/en/latest/survey_builder.md) for visual questionnaire import; use the installed CLI's `ep surveys --help` for available survey operations.

Save a survey as an `.ep` package and reopen it in another session. See [Saving and sharing objects](https://docs.expectedparrot.com/en/latest/guides/saving_objects.md) and [Survey package format](https://docs.expectedparrot.com/en/latest/survey_ep_format.md) for the basic workflow and format details.

## Common methods

| Call | Arguments and result |
| - | - |
| `Survey([question, ...])` | Create a survey with questions in the supplied order. |
| `survey.add_question(question, index=None)` | Insert a question at the given index, or append it when the index is omitted; return the survey. |
| `survey.add_skip_rule(question, expression)` | Skip the named question when the expression is true before it is asked. |
| `survey.add_stop_rule(question, expression)` | End the survey when the expression is true after the named question. |
| `survey.add_targeted_memory(focal_question, prior_question)` | Include a prior question and answer in the focal question's model context. |
| `survey.by(agents).by(scenarios).by(model)` | Return a Jobs object configured with those study components. |
| `survey.save("survey.ep")` / `Survey.load("survey.ep")` | Save and restore the survey definition locally. |

Rule and memory methods accept question names or question objects. They return the survey so you can chain calls. For example, this constructs a two-question survey with a stop rule:

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

used = QuestionYesNo(question_name="used", question_text="Have you used this service?")
feedback = QuestionFreeText(question_name="feedback", question_text="What should improve?")
survey = Survey([used, feedback]).add_stop_rule("used", "{{ used.answer }} == 'No'")
```

For model runs, call `run()` on the configured job; see [Jobs](https://docs.expectedparrot.com/en/latest/jobs.md#execution-options) for execution options. For a participant link, follow [Human surveys](https://docs.expectedparrot.com/en/latest/humanize.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.