Skip to content

Open-End Coding

Open-end coding turns free-text answers into labelled categorical variables you can cross-tabulate, weight and export like any other question. It runs as the first step of a Bundle's Bronze → Silver derive, before weighting, so a coded variable is available as a weighting factor in the same derive.

The idea in one screen

An open-ended question rarely varies on one thing. Ask "What do you do at work?" and the answers vary on what kind of activity it is and on which domain it happens in — two different questions to ask of the same sentence. Askalot calls each of those a dimension.

Each dimension you keep becomes exactly one column on the Silver dataset, holding one category per respondent:

q_work (kept as-is) q_work_activity_type q_work_domain
Respondent 1 "I draft and file the ward's paperwork" Documentation Public administration
Respondent 2 "I look after the machines on the line" Maintenance Manufacturing
Respondent 3 "mostly emails" Unclassified Unclassified
Respondent 4 (left blank) (missing) (missing)

Two things to read off that table:

  • One column per dimension, not one per category. The cell holds the category, decoded from a stored code by the column's value labels — the same shape a dropdown question produces, so every export format and every chart already knows what to do with it.
  • "Unclassified" and "blank" are different answers. Respondent 3 said something the codebook could not place; respondent 4 said nothing at all. The first is a real, weightable category; the second is missing data.

The original text column is always preserved beside the coded ones.

You choose which questions get coded, per Bundle

Coding is not something a questionnaire declares. Which open-text answers are worth a codebook is an analysis judgement about this Bundle — the same questionnaire fielded twice can be worth coding once and not the other time — so you pick the questions on the Bundle's Silver page, and the choice is stored on that Bundle's Silver recipe. See Choosing what to code below.

The two-round cycle

Coding is deliberately two rounds with your decision between them. What the answers vary on is something a model can propose; which of those axes is worth a column in your dataset is a judgement only you can make.

   You select the questions
  ①  Propose ──────────────  reads each question plus a sample of its answers,
              │               and names the dimensions they vary on, each with
              │               starter categories. Writes a codebook — no dataset.
  ②  Review  ──────────────  you read each dimension's categories with their
              │               counts and example answers, rename, re-cut, add an
              │               axis nobody proposed, and TICK the ones to keep.
  ③  Derive  ──────────────  every answer is matched to its nearest category in
                              each dimension you kept. One column per dimension,
                              then weighting runs on the coded data.

Only step ③ produces a dataset. Proposing costs you nothing but time — it writes a codebook you can throw away — and only saving your selection triggers a re-derive.

Step 1 — Propose

On the Bundle's Silver page, under Open-End Coding, click Propose dimensions. One round covers every question you selected that does not have a codebook yet; it runs in the background and shows its progress, and you can leave the page and come back to it.

For each question, the proposal reads the question text plus a sample of its answers. Reading the question is the point: the axes come from what was asked, not only from whatever happened to come back. There is no limit on how many dimensions can be proposed — a question that genuinely varies on four things gets four, and you decide which are real.

A question that already has a codebook is skipped, not re-proposed. Re-cutting an existing codebook is the review screen's job; re-proposing would replace the columns you have already selected and invalidate any weighting targets naming them.

Discovery needs an AI credential; coding itself does not

Proposing dimensions from a question is an AI call, made with your own Direct LLM slot (Anthropic API key or AWS Bedrock — see AI Functions & Model Options). Everything else — matching answers to categories, deriving the columns, weighting, exporting — uses the platform's own embedding service and needs no credential of yours.

Without a credential, coding does not refuse. The round falls back to grouping the answers themselves and proposing one dimension, whose categories are the groups it found. It is labelled Degraded wherever it appears, so a genuine one-axis result is never confused with a credential-less one. A credential that is rejected — an expired AWS session is the usual cause — fails the round loudly instead of quietly dropping to this path.

Step 2 — Review

The review is a two-pane screen: your coded questions on the left, the focused question's dimensions on the right. For each dimension you get:

  • its categories, each with how many answers landed in it and up to two of those answers verbatim, so you can check the label means what it says;
  • the Unclassified count and share — answers that matched nothing;
  • an overlap figure, described below.

What you can do:

Action Effect
Tick a dimension It becomes a column on the next derive
Untick it It does not — its proposal stays on screen for a later pass
Rename it The heading changes; the column name does not (see below)
Add / remove / rename a category Re-cuts the codebook for the next derive
Add a dimension Type a name and its categories; it is coded exactly like a proposed one
Save with nothing ticked The question stays uncoded — no column, no fallback

Renaming a dimension never renames its column. The column name is fixed the first time the dimension is proposed. That is deliberate: it keeps any weighting target naming that column valid, and it keeps two exports of the same Bundle comparable. The heading you read and the column name in the file can therefore differ, and that is the intended behaviour rather than a bug.

Overlap is a property of one dimension's categories, not of the question. It counts how often an answer fits more than one category of that same dimension. A high number means those categories are blurring into each other and want re-cutting — it does not mean the question needs another dimension. How many axes a question carries is simply how many dimensions it has.

Because it shows respondents' own words, the review screen is manager-only and each opening is recorded in the audit trail, exactly like opening a dataset preview.

Step 3 — Derive

Saving your selection invalidates the current Silver (and Gold) and applies on the next derive, exactly as editing Calibration Targets does. A derive already running keeps the selection it started with.

At derive time each answer is compared against every category of every dimension you kept — the category's label together with its example phrasings — and takes the one it is closest to. An answer that is not close enough to any of them is recorded as Unclassified rather than forced into the nearest category.

A derive that codes nothing fails instead of finishing

If one question cannot be coded — its column is missing from the extraction, or a category has nothing to compare answers against — that question is left uncoded with its raw text intact and the rest still code. But a derive that set out to code and produced no coded column at all stops and marks the Silver stage failed, naming each question and what went wrong. It does not publish a Silver that looks finished while quietly containing none of the coding you asked for.

The exception is planned emptiness: a question you selected but have not reviewed yet contributes no column by design, and that derive succeeds as normal.

A question lost mid-derive is named on the page, not hidden

When one question of several fails, the derive publishes — the other questions' columns are real work and you should get them. The Silver page and the Export page both then say so: "This derive coded 3 of 4 attempted questions", naming the one that produced nothing and why. Exporting is still allowed; re-derive once the cause is fixed and the notice clears.

A Silver derived before this notice existed shows nothing rather than a clean bill — the page can only report what the derive wrote down.

Choosing what to code

Open the Bundle's Silver page and use the Questions to code picker at the top of the Open-End Coding section. It lists every open-text question the Bundle's Bronze offers; tick the ones worth coding and Save selection.

A few things worth knowing about the list:

  • Only free-text questions appear, and appearing does not imply coding. A question is offered when it was authored as a Textarea, which is how a questionnaire asks for open text. Appearing is not a recommendation: leave out text that records a fact rather than an opinion (a name, an address, an employer), since those group by shared wording rather than by meaning.
  • A Roster question is one entry, not one per iteration. A Roster asks the same question of several entities, so its iterations are pooled into a single codebook and the dimensions are applied to every iteration's column. The list marks these as pooled roster with the number of iterations.
  • Saving a different selection invalidates the current Silver and applies on the next derive, exactly as editing Calibration Targets does. A derive in flight keeps the selection it started with.
  • Selecting nothing is a decision, not a gap. The page says so explicitly — Silver derives straight to weighting.
  • Un-ticking a question clears its dimension selection but keeps its proposals and your renames. Re-tick it later and the review resumes where you left it — you are asked to decide again, but not to start again.

The choice belongs to the Bundle, not to the questionnaire, so cloning a Bundle carries it along and two Bundles over the same questionnaire can code different questions.

Weighting on a coded dimension

A coded dimension is a single weighting factor across its whole category set, not one margin per category — its categories partition the respondents, so they belong in one distribution.

Add it in the Bundle's Calibration Targets editor like any other column and enter one target proportion per category. Every category needs one, Unclassified included. A distribution that leaves one out is refused with a message naming what is missing, because raking would otherwise quietly aim at a distribution you never wrote.

Unclassified is a real group of respondents — they answered, and their answer matched none of your categories — so it is weightable like any other category. It is worth knowing that no external benchmark will ever publish a target for it: you are supplying a share for a residue that is a property of your codebook rather than of the population. Reducing it (better categories, better examples) is usually the better move.

What lands in the dataset

Each selected dimension adds one column carrying:

  • a category code per respondent, with Unclassified reserved as its own code and unanswered items left missing;
  • value labels naming every category, so exports carry the meaning: SPSS attaches them as value labels, R as factor levels, Excel on its schema sheet;
  • metadata naming the source question, the dimension, and whether it came from the degraded round.

Column names are derived from the dimension's first name, so two dimensions whose names reduce to the same column name — or a dimension colliding with a multi-select option column already in the dataset — get a numeric suffix. Nothing is dropped or overwritten.

Exports carry codes, not text

This is the same convention every other coded question follows. A cell in the dataset holds the category's code; the label travels as metadata alongside it. The Silver preview in the app shows you the label.

Via MCP / API

An agent can read the coding state and write the question selection. It cannot select dimensions, rename them, or edit their categories — that is a judgement about generated content and it stays with you, in the app. There is no tool for it.

get_bundle_coding reports, per question: the proposed dimensions with their categories, which are selected, whether the round was degraded, awaiting_review naming the questions still owed a decision, and coding_outcome naming any question the last derive attempted and got no column from. It never returns respondents' verbatim answers.

state = get_bundle_coding(bundle_id="<bundle-id>")
# state["candidates"]      — every open-text unit this Bundle's Bronze offers
# state["selected"]        — null = nobody has chosen yet; [] = "code nothing"
# state["units"]           — per unit: its dimensions, categories, selection state, degraded
# state["awaiting_review"] — selected units whose dimensions nobody has reviewed
# state["coding_outcome"] — what the last derive attempted and lost (null = no record)
# state["pending_review"]  — true if either decision is still open

set_coding_selection(bundle_id="<bundle-id>", selected=["q_why_left"])

set_coding_selection takes the full replacement set of unit_key values from the candidate list — a Roster is one key covering every iteration — and an empty list means "code nothing". It requires manager permissions and an identified user: a service token acting on nobody's behalf is rejected, because which answers are worth a codebook is a research judgement. Present the candidates and write the answer you are given rather than choosing.

code_open_ends (alias: derive_silver) then starts the background Bronze→Silver derive over the Bundle's ready Bronze. It returns immediately with the Silver dataset in a processing state; poll get_dataset(dataset_id) until it reaches ready or error:

result = code_open_ends(bundle_id="<bundle-id>")
silver_id = result["silver_dataset_id"]

while True:
    dataset = get_dataset(dataset_id=silver_id)
    if dataset["processing_status"] in ("ready", "error"):
        break
    time.sleep(5)

Surface a pending review rather than deriving past it. A question awaiting dimension review produces no coded column, and the derive still succeeds — so an agent that ignores awaiting_review produces a file that is silently missing exactly the columns coding was asked for:

state = get_bundle_coding(bundle_id="<bundle-id>")
if state["pending_review"]:
    # ask the researcher to choose questions / review dimensions first
    ...

Integration with the Bundle pipeline

🥉 Bronze          🥈 Silver                🥇 Gold
(raw extraction) → (coded, then weighted) → (analysis-ready)
  • Input: the Bundle's own Bronze dataset — no other source is selectable
  • Output: the Bundle's Silver dataset, carrying the original data, one column per selected dimension, and the _weight column
  • Downstream: Gold refinement (rename/reorder/computed fields) and export both work from this Silver
  • Export: Silver and Gold export to CSV, Excel, SPSS, Parquet or R with full variable labels, once ready

Designing surveys for coding

Open-ended questions use the Textarea control in QML. It does not mark a question for coding — the questionnaire only decides what may be offered, and the choice is made per Bundle at analysis time:

- id: q_feedback
  kind: Question
  title: "What could we improve?"
  input:
    control: Textarea
    placeholder: "Share your suggestions..."
    maxLength: 1000

Best practices

  • Ask focused questions: "What do you like most?" gives cleaner dimensions than "Any comments?"
  • Use multiple open-ended questions: each targets a different aspect (likes, dislikes, use cases)
  • Set reasonable maxLength: 500–1000 characters encourages substantive answers
  • Combine with structured questions: demographics and rating scales let you cross-tabulate against the coded dimensions
  • Plan for sample size: aim for 30+ answers per open-ended question

Troubleshooting

The Silver derived fine but has no coded columns Either nothing is selected for coding, or the selected questions are still awaiting dimension review — the Silver page and the Export page both name which. Review the dimensions, tick the ones to keep, and re-derive. If the picker offers nothing at all, the questionnaire asks no open-text question; use Textarea for the questions you want coded.

The Silver is missing the columns for one question, and the rest are there That question was attempted and produced nothing. The Silver page and the Export page both name it beside the reason — its column was missing from the extraction, one of its categories had nothing to compare answers against, or the run hit something unexpected. Re-extract or fix the codebook and re-derive; the other questions' columns are unaffected.

The export was refused because a question "still awaits dimension review" That Silver carries no coded column for the named questions. Reviewing and re-deriving is the fix. The refusal exists so you do not ship a file that looks complete and is quietly missing the coding you asked for.

Too many answers came back Unclassified The categories are not reaching those answers. Read the Unclassified examples on the review screen — they usually show an axis nobody proposed, or a category whose label is narrower than the answers it should be catching. Renaming a category, adding one, or authoring a new dimension all help; re-derive afterwards.

A dimension reports high overlap Its own categories are blurring into each other — several of them fit the same answers. Merge or re-cut those categories. This is not a sign that the question needs another dimension.

The proposal came back with only one dimension, labelled "Degraded" No Direct LLM slot was configured for you, so the round grouped the answers instead of reading the question. Configure a slot in Profile Settings → AI Models before proposing. A question that already has a codebook is skipped by every later round, a saved dimension cannot be discarded from the review screen, and cloning the Bundle copies its recipe — so today a fresh Bundle over the same questionnaire and campaigns is the only way to get a question proposed for again. You can still work with the degraded codebook: renaming its dimension, re-cutting its categories and adding your own dimensions all behave exactly as they do for a proposed one, and an authored dimension is coded by the same path.

The derive is refused because a weighting factor is "backed by a dimension this Bundle no longer selects for coding" A Calibration Target names a column produced by a dimension you have since un-ticked. Either re-select that dimension in the review or drop the factor from the Calibration Targets, then derive again — the check runs before any work, so nothing is half-derived. The sibling message naming an open-text unit rather than a dimension means the whole question was un-ticked in the picker.

The degraded round says there are too few answers Grouping answers needs enough of them to find any density; proposing from the question does not. Configure a Direct LLM slot, or code that question once more answers are in.

The Silver stage failed saying the coding "produced no columns" Every question the derive tried to code came back with nothing — usually because those questions' text columns are missing from the extraction, or because their categories have no wording to compare answers against. The message names each question and the reason. Re-extract Bronze if the columns are missing, or fix the codebook on the review screen, then derive again. This is a stop, not a stall: the derive does not retry itself, because a second attempt would meet the same questions and produce the same nothing.

The Silver derive failed while the local models were unavailable Matching answers to categories runs on the platform's own embedding service, and a derive that cannot reach it stops rather than finishing empty. It is retried automatically a few times with a growing pause, so a service that is merely waking up or briefly restarting resolves itself. If the stage still ends failed, the models are genuinely down — the Local models indicator in the top navigation bar says so — and re-deriving once they are back is all that is needed. Nothing is half-written: the previous Silver file is untouched until a derive succeeds.