Questions & Answers

The question/answer data model - how to render each question type and construct valid answers

Every turn of the conversation is: render a Question, collect a selection, send back an answer with the same type. This chapter is the reference for that contract.

Question

{
  "type": "symptoms",
  "id": "q-123",
  "messages": [ ... ],
  "choices": [ ... ],
  "constraints": { "min_selections": 0, "max_selections": 4 }
}
FieldDescription
typeDiscriminator — drives which answer type you send back and hints at the UI control (below).
idQuestion identifier (opaque; useful for analytics/logging).
messagesOrdered display content — render all of them, in order.
choicesThe selectable options (may be absent for pure-message states).
constraintsSelection validation the client must enforce before submitting.

Message

The universal display unit, used in questions, legal content, and reports:

{ "value": "Have you ever been diagnosed with any of the following?",
  "type": "HEADER",
  "format": "PLAIN_TEXT" }
  • type — placement hint: HEADER (title), PARAGRAPH (body text), BULLET_POINT (list item), INPUT_HINT (placeholder/help text for an input control), OTHER.
  • format — how to interpret value: PLAIN_TEXT, MARKDOWN, or HTML. Render markdown/HTML safely (sanitize HTML; don't execute scripts).
📘

For MARKDOWN, make sure your implementation correctly renders links, bolding, italics, new lines (\n) and bullet points (-).

Choice

{
  "id": "C0041105",
  "text": "Ringing in the ears",
  "symptom_definition": {
    "value": "A perception of sound (often ringing or buzzing) without an external source.",
    "type": "PARAGRAPH",
    "format": "PLAIN_TEXT"
  }
}
  • id — opaque identifier; echo it back in included or excluded. Ids may be UMLS CUIs (C0041105), clarify codes (A290201), autocomplete ids (assessment_C2316035), duration buckets (year), or action ids — treat them all the same way.
  • text — the label to display.
  • symptom_definition — optional plain-language helper copy explaining the symptom; when present, show it as supporting text under the label (a standard Message; respect its format). Omitted when no definition exists.

Constraints

{ "min_selections": 1, "max_selections": 1 }

Enforce these client-side before enabling "Continue" (the server also validates and returns 400 on violation):

minmaxUI pattern
11Mandatory single choice — radio buttons / tappable list
01Optional single choice — radio + explicit skip/none
0n>1Optional multi-select — checkboxes, "None of these" allowed
1n>1Mandatory multi-select — checkboxes, submit locked until ≥1 selected

Either bound may be absent — treat a missing min_selections as 0 and a missing max_selections as unlimited.

Question types and how to render them

typeSemanticsTypical UI
genericGeneral-purpose question: symptom ratification, clarifications, duration, continue/add-symptoms decisions, onboarding age/genderSingle- or multi-choice per constraints
symptomOne symptom's conditionality ("Is the pain sharp?")Single choice
symptoms"Do you have any of these?" — multi-symptom collectionMulti-select checkboxes with per-choice symptom_definition support copy
symptoms_choose_onePick which of two presentations appliesConstrained choice (1–2 selections)
factorInfluencing factors (injury, medication, pregnancy…)Single choice
health_backgroundPre-existing conditions / medical historyMulti-select, "none apply" very common
autocompleteSymptom not recognised — user searches for itSearch box driven by GET /symptom-search, plus a skip control (skip_autocomplete)

Notes:

  • Build one generic renderer keyed on messages + choices + constraints, with a special case only for autocomplete (search box). New copy and choices then work without client releases.
  • Don't switch behaviour on specific choice ids or message text — both are content, not contract.
  • The onboarding_response questions returned by POST /terms use three additional types: numeric (age — integer input; its constraints are the valid range), choice (gender — single select), and text (initial symptom — free text; its constraints are the character length range). These are never answered via POST /chat — their values are submitted together as the POST /initial body.

The answer

{
  "type": "<same type as the question>",
  "included": ["C0041105"],
  "excluded": ["C0006325", "C0018681", "C0015468"]
}

Rules:

  1. type echoes the question's type. Sending a mismatched type is a 400.
  2. included = choices the user affirmed. excluded = choices the user saw and did not select. Populating excluded matters medically — an unselected symptom is evidence of absence, and the diagnostic engine uses it. Required: every choice id in the question must appear in exactly one of the two lists, for every question type except autocomplete. Otherwise the API returns 400 (Selection mismatch…).
  3. At least one of the lists must be non-empty.
  4. Respect constraints on the size of included.
  5. For autocomplete, included carries the selected suggestions' ids from /symptom-search (e.g. assessment_C2316035), as many as the question's constraints allow. To skip the step without adding anything, send the literal id skip_autocomplete in included. Send only included: excluded must be omitted or empty (otherwise 400), and skip_autocomplete can't be combined with other ids.

Worked examples

Multi-select symptoms question — user has ringing in the ears, nothing else:

{ "type": "symptoms",
  "included": ["C0041105"],
  "excluded": ["C0006325", "C0018681", "C0015468"] }

Duration (a generic question) — "about a year":

{ "type": "generic",
  "included": ["year"],
  "excluded": ["hour", "day", "week", "month"] }

Clarification (a generic question with clarify-code ids):

{ "type": "generic",
  "included": ["A290201"],
  "excluded": ["A290202", "A290203"] }

Health background — nothing applies:

{ "type": "health_background",
  "included": [],
  "excluded": ["C0011849", "C0004096", "C0010054", "C0403447"] }

Autocomplete — user picked a suggestion from /symptom-search:

{ "type": "autocomplete",
  "included": ["assessment_C2316035"] }

Special turns without a normal answer

  • Step back — POST /step-back with an empty body (not a /chat answer). Gate on step_back_possible.
  • Restore — GET /chat re-reads the current question without answering.
  • Report — GET /report once report_possible is true.

Did this page help you?