The PHQ-9 is the SDC walkthrough that most FHIR teams reach for first. Nine items, an integer sum, a severity band, and a well-known LOINC code for the total score. The value of running the exercise end to end is that it forces the team to decide where the scoring rule lives, how the extraction is expressed, and which downstream Observation shape the analytics jobs will actually read.
The Questionnaire and the Scoring Rule
A production PHQ-9 Questionnaire looks like nine integer items with an answerValueSet that binds to the 0-3 severity scale. A tenth item, hidden from the patient, carries the calculated total using an SDC calculatedExpression extension:
%resource.item.where(linkId!='total').answer.valueInteger.sum()
Some teams model severity as a second calculated item that maps the total to the LOINC banding (minimal, mild, moderate, moderately severe, severe). Others emit only the total and let the downstream Observation carry the interpretation. The downstream Observation shape is the driver here: pick the model the analytics jobs already expect.
The ExtractDefinition Contract
SDC ExtractDefinition is the standard mechanism; whether you build the runtime yourself or lean on an engine like Formbox, the extraction contract is the same. If you want to see the shape without spinning up a full server, form-builder.aidbox.app consumes standard FHIR Questionnaire JSON in the browser and is a fast way to sanity-check a PHQ-9 definition before committing. The ExtractDefinition points at a StructureMap or a definition-based extract template that maps the QuestionnaireResponse into one or more Observation resources.
For coding side, PHQ-9 total binds to LOINC 44249-1. Each item can bind to its own LOINC code if the analytics jobs need item-level data (LOINC 44250-9 through 44258-2). Most population health teams only extract the total; research teams extract the whole panel.
Wire the Extract Job
The extract job runs either when the QuestionnaireResponse hits completed status or on a downstream Subscription that watches for the status change. Both patterns are legitimate. The Subscription pattern scales better when the extract job also touches a separate scoring service; the direct pattern is simpler when everything runs inside the same FHIR server.
Emit the Observation with:
code= LOINC 44249-1 for total; item-level codes for panel extraction.valueInteger= the calculated total (0-27).interpretation= the severity band, coded to a local CodeSystem or a shared one.derivedFrom= a reference back to the QuestionnaireResponse.subjectandencounter= pulled from the QuestionnaireResponse context.
The derivedFrom link matters more than teams expect. Downstream jobs that need to re-run the scoring rule with a corrected item can walk back to the original QuestionnaireResponse without a second query. See the FHIR product comparison hub for related evaluation patterns.
Handle the Edge Cases
A production PHQ-9 pipeline has to handle three edge cases. Partial responses where the patient answered eight of nine items should not emit a total; the extract job checks completeness before writing. Item 9 (thoughts of self-harm) at a non-zero value should trigger an alert flow separate from the Observation write, because the analytics path and the safety path have different SLAs. Rescoring after a corrected item value should emit an updated Observation with status = amended rather than overwriting the previous one.
For related workflows, see 5 SDC form engines that handle multi-step telehealth intake cleanly and best SDC tools for healthcare software teams shipping in 2026.
The short version: keep the scoring rule inside the Questionnaire's calculatedExpression, let the ExtractDefinition move the result into a LOINC-coded Observation, and let the derivedFrom link preserve the audit trail back to the answered items. A team that gets those three right can extend the same pattern to GAD-7, PROMIS-29, and the rest of the SDC library without changing the pipeline shape.
