From 13212d59fa70a282eaa78dcdb94fd5b6637911ce Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Mon, 22 Jul 2024 09:55:57 +0930 Subject: [PATCH] Add custom populatedContext property to in app population --- .../components/PlaygroundRenderer.tsx | 6 +- .../utils/createInputParameters.ts | 20 ++--- .../utils/populateQuestionnaire.ts | 82 ++++++++++++++----- 3 files changed, 77 insertions(+), 31 deletions(-) diff --git a/apps/smart-forms-app/src/features/playground/components/PlaygroundRenderer.tsx b/apps/smart-forms-app/src/features/playground/components/PlaygroundRenderer.tsx index 6b9ba5431..0b949ec12 100644 --- a/apps/smart-forms-app/src/features/playground/components/PlaygroundRenderer.tsx +++ b/apps/smart-forms-app/src/features/playground/components/PlaygroundRenderer.tsx @@ -40,6 +40,7 @@ function PlaygroundRenderer(props: PlaygroundRendererProps) { const sourceQuestionnaire = useQuestionnaireStore.use.sourceQuestionnaire(); const targetStructureMap = useExtractOperationStore.use.targetStructureMap(); + const setPopulatedContext = useQuestionnaireStore.use.setPopulatedContext(); const [isPopulating, setIsPopulating] = useState(false); @@ -70,10 +71,13 @@ function PlaygroundRenderer(props: PlaygroundRendererProps) { return; } - const { populatedResponse } = populateResult; + const { populatedResponse, populatedContext } = populateResult; // Call to buildForm to pre-populate the QR which repaints the entire BaseRenderer view await buildForm(sourceQuestionnaire, populatedResponse, undefined, TERMINOLOGY_SERVER_URL); + if (populatedContext) { + setPopulatedContext(populatedContext); + } setIsPopulating(false); }); diff --git a/packages/sdc-populate/src/inAppPopulation/utils/createInputParameters.ts b/packages/sdc-populate/src/inAppPopulation/utils/createInputParameters.ts index e4776516b..526ea9760 100644 --- a/packages/sdc-populate/src/inAppPopulation/utils/createInputParameters.ts +++ b/packages/sdc-populate/src/inAppPopulation/utils/createInputParameters.ts @@ -34,13 +34,13 @@ import type { export function createPopulateInputParameters( questionnaire: Questionnaire, + patient: Patient, + user: Practitioner | null, + encounter: Encounter | null, launchContexts: LaunchContext[], sourceQueries: SourceQuery[], questionnaireLevelVariables: QuestionnaireLevelXFhirQueryVariable[], - context: Record, - patient: Patient, - user?: Practitioner, - encounter?: Encounter + context: Record ): Parameters | null { const patientSubject = createPatientSubject(questionnaire, patient); if (!patientSubject) { @@ -73,10 +73,10 @@ export function createPopulateInputParameters( for (const launchContext of launchContexts) { const launchContextParam = createLaunchContextParam( launchContext, - context, patient, user, - encounter + encounter, + context ); if (launchContextParam) { contexts.push(launchContextParam); @@ -147,10 +147,10 @@ function createLocalParam(): ParametersParameter { function createLaunchContextParam( launchContext: LaunchContext, - context: Record, patient: Patient, - user?: Practitioner, - encounter?: Encounter + user: Practitioner | null, + encounter: Encounter | null, + fhirPathContext: Record ): ParametersParameter | null { const name = launchContext.extension[0].valueId ?? launchContext.extension[0].valueCoding?.code; if (!name) { @@ -174,7 +174,7 @@ function createLaunchContextParam( } // Update context with launchContext resources - context[name] = resource; + fhirPathContext[name] = resource; return { name: 'context', diff --git a/packages/sdc-populate/src/inAppPopulation/utils/populateQuestionnaire.ts b/packages/sdc-populate/src/inAppPopulation/utils/populateQuestionnaire.ts index 65e6093a8..5e071f116 100644 --- a/packages/sdc-populate/src/inAppPopulation/utils/populateQuestionnaire.ts +++ b/packages/sdc-populate/src/inAppPopulation/utils/populateQuestionnaire.ts @@ -22,9 +22,11 @@ import type { Patient, Practitioner, Questionnaire, + QuestionnaireItem, QuestionnaireResponse } from 'fhir/r4'; import type { + CustomContextResultParameter, FetchResourceCallback, InputParameters, IssuesParameter, @@ -42,10 +44,13 @@ import type { QuestionnaireLevelXFhirQueryVariable, SourceQuery } from '../interfaces/inAppPopulation.interface'; +import { Base64 } from 'js-base64'; +import { isRecord } from '@aehrc/smart-forms-app/src/features/prepopulate/typePredicates/isRecord'; export interface PopulateResult { populatedResponse: QuestionnaireResponse; issues?: OperationOutcome; + populatedContext?: Record; } /** @@ -103,7 +108,7 @@ export async function populateQuestionnaire(params: PopulateQuestionnaireParams) // Get launch contexts, source queries and questionnaire-level variables const launchContexts = getLaunchContexts(questionnaire); const sourceQueries = getSourceQueries(questionnaire); - const questionnaireLevelVariables = getQuestionnaireLevelXFhirQueryVariables(questionnaire); + const questionnaireLevelVariables = getXFhirQueryVariables(questionnaire); if ( launchContexts.length === 0 && @@ -119,13 +124,13 @@ export async function populateQuestionnaire(params: PopulateQuestionnaireParams) // Define population input parameters from launch contexts, source queries and questionnaire-level variables const inputParameters = createPopulateInputParameters( questionnaire, + patient, + user ?? null, + encounter ?? null, launchContexts, sourceQueries, questionnaireLevelVariables, - context, - patient, - user, - encounter + context ); if (!inputParameters) { @@ -164,22 +169,30 @@ export async function populateQuestionnaire(params: PopulateQuestionnaireParams) const issuesParameter = outputParameters.parameter.find((param) => param.name === 'issues') as | IssuesParameter | undefined; + const contextResultParameter = outputParameters.parameter.find( + (param) => param.name === 'contextResult-custom' + ) as CustomContextResultParameter | undefined; + + const populateResult: PopulateResult = { + populatedResponse: responseParameter.resource + }; + + // Add populated context to populateResult if it exists + if (contextResultParameter?.valueAttachment.data) { + const contextResult = JSON.parse(Base64.decode(contextResultParameter.valueAttachment.data)); + + if (isRecord(contextResult)) { + populateResult.populatedContext = contextResult; + } + } if (issuesParameter) { - return { - populateSuccess: true, - populateResult: { - populatedResponse: responseParameter.resource, - issues: issuesParameter.resource - } - }; + populateResult.issues = issuesParameter.resource; } return { populateSuccess: true, - populateResult: { - populatedResponse: responseParameter.resource - } + populateResult: populateResult }; } @@ -327,14 +340,43 @@ function isXFhirQueryVariable( ); } -function getQuestionnaireLevelXFhirQueryVariables( +function getXFhirQueryVariables( questionnaire: Questionnaire ): QuestionnaireLevelXFhirQueryVariable[] { + const xFhirQueryVariables: QuestionnaireLevelXFhirQueryVariable[] = []; if (questionnaire.extension && questionnaire.extension.length > 0) { - return questionnaire.extension.filter((extension) => - isXFhirQueryVariable(extension) - ) as QuestionnaireLevelXFhirQueryVariable[]; + xFhirQueryVariables.push( + ...(questionnaire.extension.filter((extension) => + isXFhirQueryVariable(extension) + ) as QuestionnaireLevelXFhirQueryVariable[]) + ); } - return []; + if (questionnaire.item && questionnaire.item.length > 0) { + for (const qItem of questionnaire.item) { + xFhirQueryVariables.push( + ...(getXFhirQueryVariablesRecursive(qItem) as QuestionnaireLevelXFhirQueryVariable[]) + ); + } + } + + return xFhirQueryVariables; +} + +function getXFhirQueryVariablesRecursive(qItem: QuestionnaireItem) { + let xFhirQueryVariables: Extension[] = []; + + if (qItem.item) { + for (const childItem of qItem.item) { + xFhirQueryVariables = xFhirQueryVariables.concat(getXFhirQueryVariablesRecursive(childItem)); + } + } + + if (qItem.extension) { + xFhirQueryVariables.push( + ...qItem.extension.filter((extension) => isXFhirQueryVariable(extension)) + ); + } + + return xFhirQueryVariables; }