Skip to main content
Version: 3.0

Questionnaire Response

A QuestionnaireResponse is the set of answers a patient gives to a questionnaire; a FormSubmission is the envelope that carries that payload through a draft-to-finalised lifecycle. You touch these when reading back what was answered or when submitting new answers.

The Django models are the storage; the Pydantic resource specs define the submit payload, answer structure, status enums, and read/write API schemas.

Source:

Models

ModelPurpose
QuestionnaireResponseA completed set of answers to a Questionnaire for a subject (patient/encounter)
FormSubmissionA submission envelope (draft → submitted) holding a raw response dump for a questionnaire

Both extend EMRBaseModel, the shared Care EMR base that provides external_id, audit fields, soft-delete via deleted, and history/meta JSON.

QuestionnaireResponse fields

FieldTypeNotes
questionnaireFK → QuestionnaireCASCADE, nullable. The questionnaire that was answered
subject_idUUIDFieldThe subject the response is about (e.g. patient external id)
responsesJSONFielddefault=list. The raw answers — a list of { "question_id": UUID, ... } entries
structured_responsesJSONFielddefault=dict. Extracted/structured representation of the answers
structured_response_typeCharFieldNullable. Discriminator for the structured payload
patientFK → PatientCASCADE. The patient
encounterFK → EncounterCASCADE, nullable. Set when the response is captured in a visit context
form_submissionFK → FormSubmissionCASCADE, nullable. The submission this response came from
statusCharField(255)default="completed". One of QuestionnaireResponseStatusChoices (see enum)

render_responses()

Joins each entry in responses with its matching question definition from questionnaire.get_questions_by_id(), returning a list of { "answer": <response>, "question": <question> } for display. Returns [] when responses is empty or no questionnaire is linked.

FormSubmission fields

FieldTypeNotes
questionnaireFK → QuestionnaireCASCADE. The questionnaire being submitted
patientFK → PatientCASCADE. The patient
encounterFK → EncounterCASCADE, nullable. Encounter context, if any
statusCharField(255)One of FormSubmissionStatusChoices (see enum)
response_dumpJSONFielddefault=dict. Raw submitted payload

Enums

Questionnaire response status

QuestionnaireResponseStatusChoices (resources/questionnaire_response/spec.py), str values.

ValueMeaning
completedThe response is finalised (default)
entered_in_errorThe response was recorded in error

Form submission status

FormSubmissionStatusChoices (resources/form_submission/spec.py), str values.

ValueMeaning
draftSaved but not finalised
submittedFinalised submission
entered_in_errorRecorded in error

Submit payload (nested shapes)

Answers go through the questionnaire submit endpoint, whose body is QuestionnaireSubmitRequest. Group questions nest via sub_results, and each answer can carry multiple values:

QuestionnaireSubmitRequest {
resource_id: UUID4 # questionnaire being answered
patient: UUID4 (required)
encounter: UUID4 | None
form_submission: UUID4 | None
results: list[QuestionnaireSubmitResult]
}

QuestionnaireSubmitResult {
question_id: UUID4 | UUID5 (required)
body_site: Coding | None
method: Coding | None
taken_at: datetime | None
values: list[QuestionnaireSubmitResultValue]
note: str | None
sub_results: list[list[QuestionnaireSubmitResult]] # nested, for group questions
}

QuestionnaireSubmitResultValue {
value: str | None
unit: Coding | None # for Quantity answers
coding: Coding | None # for coded answers
}

Resource specs (API schema)

All specs extend EMRResource (resources/base.py).

QuestionnaireResponse (resources/questionnaire_response/spec.py)

There is no generic create spec — new responses come from the questionnaire submit flow (QuestionnaireSubmitRequest). The update spec carries status only, so the one write you make directly is flagging a response entered_in_error.

Spec classRoleNotes
EMRQuestionnaireResponseBaseshared__model__ = QuestionnaireResponse
QuestionnaireResponseUpdatewrite · updateOnly status (QuestionnaireResponseStatusChoices, default completed) — used to mark a response entered_in_error
QuestionnaireResponseReadSpecreadid, status, questionnaire (nested QuestionnaireReadSpec), subject_id, responses, encounter (external id or null), structured_responses, structured_response_type, created_by/updated_by (UserSpec), created_date/modified_date

FormSubmission (resources/form_submission/spec.py)

Spec classRoleNotes
BaseFormSubmissionSpecshared__model__ = FormSubmission; id (UUID4?)
FormSubmissionUpdateSpecwrite · updatestatus (FormSubmissionStatusChoices), response_dump (dict)
FormSubmissionWriteSpecwrite · createAdds questionnaire (slug str), patient (UUID4), encounter (UUID4?). perform_extra_deserialization resolves the questionnaire by slug and the patient/encounter by external id; when an encounter is given, the patient is taken from it
FormSubmissionReadSpecreadstatus, response_dump, created_date/modified_date, created_by/updated_by (UserSpec?)

API integration notes

  • Both map loosely onto the FHIR QuestionnaireResponse resource: responses holds the raw answers, and structured_responses is the extracted, query-friendly form.
  • Create a QuestionnaireResponse by submitting a QuestionnaireSubmitRequest. The update spec exists to flag responses entered_in_error.
  • FormSubmission runs a draft→submitted lifecycle. Its response_dump is opaque JSON — the questionnaire definition validates it, the model does not.
  • The server maintains the audit users (created_by/updated_by); don't set them on write.