diff --git a/functions/models/src/fhir/fhirQuestionnaireResponse.ts b/functions/models/src/fhir/fhirQuestionnaireResponse.ts index 0aee1813..94aa9bee 100644 --- a/functions/models/src/fhir/fhirQuestionnaireResponse.ts +++ b/functions/models/src/fhir/fhirQuestionnaireResponse.ts @@ -20,37 +20,76 @@ import { optionalish } from '../helpers/optionalish.js' import { SchemaConverter } from '../helpers/schemaConverter.js' import { type SymptomQuestionnaireResponse } from '../types/symptomQuestionnaireResponse.js' -export const fhirQuestionnaireResponseItemConverter = new Lazy( - () => - new SchemaConverter({ - schema: z.object({ - answer: optionalish( - z - .object({ - valueCoding: optionalish( - z.lazy(() => fhirCodingConverter.value.schema), - ), - }) - .array(), - ), - linkId: optionalish(z.string()), - }), - encode: (object) => ({ - answer: - object.answer?.flatMap((value) => ({ - valueCoding: - value.valueCoding ? - fhirCodingConverter.value.encode(value.valueCoding) - : null, - })) ?? null, - linkId: object.linkId ?? null, - }), - }), -) +const fhirQuestionnaireResponseItemBaseConverter = new SchemaConverter({ + schema: z.object({ + answer: optionalish( + z + .object({ + valueCoding: optionalish( + z.lazy(() => fhirCodingConverter.value.schema), + ), + }) + .array(), + ), + linkId: optionalish(z.string()), + }), + encode: (object) => ({ + answer: + object.answer?.flatMap((value) => ({ + valueCoding: + value.valueCoding ? + fhirCodingConverter.value.encode(value.valueCoding) + : null, + })) ?? null, + linkId: object.linkId ?? null, + }), +}) + +export interface FHIRQuestionnaireResponseItemValue + extends z.input< + typeof fhirQuestionnaireResponseItemBaseConverter.value.schema + > { + item?: + | Array> + | null + | undefined +} -export type FHIRQuestionnaireResponseItem = z.output< - typeof fhirQuestionnaireResponseItemConverter.value.schema -> +export const fhirQuestionnaireResponseItemConverter = (() => { + const fhirQuestionnaireResponseItemSchema: z.ZodType< + FHIRQuestionnaireResponseItem, + z.ZodTypeDef, + FHIRQuestionnaireResponseItemValue + > = fhirQuestionnaireResponseItemBaseConverter.value.schema.extend({ + item: optionalish( + z.array(z.lazy(() => fhirQuestionnaireResponseItemSchema)), + ), + }) + + function fhirQuestionnaireResponseItemEncode( + object: z.output, + ): z.input { + return { + ...fhirQuestionnaireResponseItemBaseConverter.value.encode(object), + item: + object.item ? + object.item.map(fhirQuestionnaireResponseItemConverter.value.encode) + : null, + } + } + + return new SchemaConverter({ + schema: fhirQuestionnaireResponseItemSchema, + encode: fhirQuestionnaireResponseItemEncode, + }) +})() + +export interface FHIRQuestionnaireResponseItem + extends z.output< + typeof fhirQuestionnaireResponseItemBaseConverter.value.schema + > { + item?: FHIRQuestionnaireResponseItem[] +} export const fhirQuestionnaireResponseConverter = new Lazy( () => @@ -274,12 +313,41 @@ export class FHIRQuestionnaireResponse extends FHIRResource { // Methods numericSingleAnswerForLink(linkId: string): number { - const answers = - this.item?.find((item) => item.linkId === linkId)?.answer ?? [] + for (const item of this.item ?? []) { + const answer = this.numericSingleAnswerForNestedItem(linkId, item) + if (answer !== undefined) return answer + } + throw new Error(`No answer found in response for linkId ${linkId}.`) + } + + private numericSingleAnswerForNestedItem( + linkId: string, + item: FHIRQuestionnaireResponseItem, + ): number | undefined { + if (item.linkId === linkId) { + return this.numericSingleAnswerForItem(linkId, item) + } + for (const child of item.item ?? []) { + const childAnswer = this.numericSingleAnswerForNestedItem(linkId, child) + if (childAnswer !== undefined) return childAnswer + } + return undefined + } + + private numericSingleAnswerForItem( + linkId: string, + item: FHIRQuestionnaireResponseItem, + ): number { + const answers = item.answer ?? [] if (answers.length !== 1) - throw new Error(`Zero or multiple answers found for linkId ${linkId}.`) + throw new Error( + `Zero or multiple answers found in response item for linkId ${linkId}.`, + ) const code = answers[0].valueCoding?.code - if (!code) throw new Error(`No answer code found for linkId ${linkId}.`) + if (!code) + throw new Error( + `No answer code found in response item for linkId ${linkId}.`, + ) return parseInt(code) } }