Skip to content

Commit

Permalink
Fix calculated expressions map Q to QR index
Browse files Browse the repository at this point in the history
  • Loading branch information
fongsean committed Jul 8, 2024
1 parent 0767f5e commit 8d0cd2b
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,36 @@ export function removeEmptyAnswersFromResponse(
return questionnaireResponse;
}

const newQuestionnaireResponse: QuestionnaireResponse = { ...questionnaireResponse, item: [] };
for (const [i, topLevelQRItem] of topLevelQRItems.entries()) {
const qItem = topLevelQItems[i];
if (!qItem) {
continue;
}

// If QR item don't have either item.item and item.answer, continue
if (!qrItemHasItemsOrAnswer(topLevelQRItem)) {
continue;
}

const newTopLevelQRItem = removeEmptyAnswersFromItemRecursive(qItem, topLevelQRItem);
if (newTopLevelQRItem && questionnaireResponse.item) {
questionnaireResponse.item[i] = { ...newTopLevelQRItem };
if (newTopLevelQRItem && newQuestionnaireResponse.item) {
newQuestionnaireResponse.item.push(newTopLevelQRItem);
}
}

return questionnaireResponse;
return newQuestionnaireResponse;
}

function removeEmptyAnswersFromItemRecursive(
qItem: QuestionnaireItem,
qrItem: QuestionnaireResponseItem
): QuestionnaireResponseItem | null {
// If QR item don't have either item.item and item.answer, return null
if (!qrItemHasItemsOrAnswer(qrItem)) {
return null;
}

const qItems = qItem.item;
const qrItems = qrItem.item;

Expand Down Expand Up @@ -120,3 +131,12 @@ function answerIsEmpty(qrItem: QuestionnaireResponseItem) {

return false;
}

/**
* Check if a QuestionnaireResponseItem has either an item or an answer property.
*
* @author Sean Fong
*/
function qrItemHasItemsOrAnswer(qrItem: QuestionnaireResponseItem): boolean {
return (!!qrItem.item && qrItem.item.length > 0) || (!!qrItem.answer && qrItem.answer.length > 0);
}
56 changes: 30 additions & 26 deletions packages/smart-forms-renderer/src/utils/calculatedExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { getQrItemsIndex, mapQItemsIndex } from './mapItem';
import { updateQrItemsInGroup } from './qrItem';
import cloneDeep from 'lodash.clonedeep';
import dayjs from 'dayjs';
import { qrItemHasItemsOrAnswer } from './manageForm';

interface EvaluateInitialCalculatedExpressionsParams {
initialResponse: QuestionnaireResponse;
Expand Down Expand Up @@ -186,17 +187,24 @@ export function initialiseCalculatedExpressionValues(
}

// Populate calculated expression values into QR
const qItemsIndexMap = mapQItemsIndex(questionnaire);
const topLevelQRItemsByIndex = getQrItemsIndex(
questionnaire.item,
populatedResponse.item,
qItemsIndexMap
);

const topLevelQrItems: QuestionnaireResponseItem[] = [];
for (const [index, topLevelQItem] of questionnaire.item.entries()) {
const populatedTopLevelQrItem = populatedResponse.item[index] ?? {
const topLevelQRItemOrItems = topLevelQRItemsByIndex[index] ?? {
linkId: topLevelQItem.linkId,
text: topLevelQItem.text,
item: []
};

const updatedTopLevelQRItem = initialiseItemCalculatedExpressionValueRecursive(
topLevelQItem,
populatedTopLevelQrItem,
topLevelQRItemOrItems,
calculatedExpressionsWithValues
);

Expand All @@ -207,7 +215,7 @@ export function initialiseCalculatedExpressionValues(
continue;
}

if (updatedTopLevelQRItem) {
if (updatedTopLevelQRItem && qrItemHasItemsOrAnswer(updatedTopLevelQRItem)) {
topLevelQrItems.push(updatedTopLevelQRItem);
}
}
Expand All @@ -217,48 +225,44 @@ export function initialiseCalculatedExpressionValues(

function initialiseItemCalculatedExpressionValueRecursive(
qItem: QuestionnaireItem,
qrItem: QuestionnaireResponseItem | undefined,
qrItemOrItems: QuestionnaireResponseItem | QuestionnaireResponseItem[] | null,
calculatedExpressions: Record<string, CalculatedExpression[]>
): QuestionnaireResponseItem[] | QuestionnaireResponseItem | null {
): QuestionnaireResponseItem | QuestionnaireResponseItem[] | null {
// For repeat groups
const hasMultipleAnswers = Array.isArray(qrItemOrItems);
if (hasMultipleAnswers) {
return qrItemOrItems;
}

const qrItem = qrItemOrItems;
const childQItems = qItem.item;
if (childQItems && childQItems.length > 0) {
// iterate through items of item recursively
const childQrItems = qrItem?.item ?? [];
// const updatedChildQrItems: QuestionnaireResponseItem[] = [];

// FIXME Not implemented for repeat groups
if (qItem.type === 'group' && qItem.repeats) {
return qrItem ?? null;
}

const indexMap = mapQItemsIndex(qItem);
const qrItemsByIndex = getQrItemsIndex(childQItems, childQrItems, indexMap);

// Otherwise loop through qItem as usual
for (const [index, childQItem] of childQItems.entries()) {
const childQrItem = qrItemsByIndex[index];

// FIXME Not implemented for repeat groups
if (Array.isArray(childQrItem)) {
continue;
}
const childQRItemOrItems = qrItemsByIndex[index];

const newQrItem = initialiseItemCalculatedExpressionValueRecursive(
const updatedChildQRItemOrItems = initialiseItemCalculatedExpressionValueRecursive(
childQItem,
childQrItem,
childQRItemOrItems ?? null,
calculatedExpressions
);

// FIXME Not implemented for repeat groups
if (Array.isArray(newQrItem)) {
if (Array.isArray(updatedChildQRItemOrItems)) {
continue;
}

if (newQrItem) {
const updatedChildQRItem = updatedChildQRItemOrItems;
if (updatedChildQRItem) {
updateQrItemsInGroup(
newQrItem,
updatedChildQRItem,
null,
qrItem ?? { linkId: qItem.linkId, text: qItem.text, item: [] },
updatedChildQRItem ?? { linkId: qItem.linkId, text: qItem.text, item: [] },
indexMap
);
}
Expand Down Expand Up @@ -287,7 +291,7 @@ function getCalculatedExpressionAnswer(

function constructGroupItem(
qItem: QuestionnaireItem,
qrItem: QuestionnaireResponseItem | undefined,
qrItem: QuestionnaireResponseItem | null,
calculatedExpressions: Record<string, CalculatedExpression[]>
): QuestionnaireResponseItem | null {
const calculatedExpressionAnswer = getCalculatedExpressionAnswer(qItem, calculatedExpressions);
Expand Down Expand Up @@ -317,7 +321,7 @@ function constructGroupItem(

function constructSingleItem(
qItem: QuestionnaireItem,
qrItem: QuestionnaireResponseItem | undefined,
qrItem: QuestionnaireResponseItem | null,
calculatedExpressions: Record<string, CalculatedExpression[]>
): QuestionnaireResponseItem | null {
const calculatedExpressionAnswer = getCalculatedExpressionAnswer(qItem, calculatedExpressions);
Expand Down
11 changes: 10 additions & 1 deletion packages/smart-forms-renderer/src/utils/manageForm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
import type { Questionnaire, QuestionnaireResponse, QuestionnaireResponseItem } from 'fhir/r4';
import {
questionnaireResponseStore,
questionnaireStore,
Expand Down Expand Up @@ -120,3 +120,12 @@ export function removeEmptyAnswersFromResponse(
enableWhenExpressions
});
}

/**
* Check if a QuestionnaireResponseItem has either an item or an answer property.
*
* @author Sean Fong
*/
export function qrItemHasItemsOrAnswer(qrItem: QuestionnaireResponseItem): boolean {
return (!!qrItem.item && qrItem.item.length > 0) || (!!qrItem.answer && qrItem.answer.length > 0);
}
28 changes: 22 additions & 6 deletions packages/smart-forms-renderer/src/utils/removeEmptyAnswers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type {
import type { EnableWhenExpressions, EnableWhenItems } from '../interfaces/enableWhen.interface';
import { isHiddenByEnableWhen } from './qItem';
import cloneDeep from 'lodash.clonedeep';
import { qrItemHasItemsOrAnswer } from './manageForm';

interface removeEmptyAnswersParams {
questionnaire: Questionnaire;
Expand Down Expand Up @@ -60,21 +61,31 @@ export function removeEmptyAnswers(params: removeEmptyAnswersParams): Questionna
return updatedQuestionnaireResponse;
}

topLevelQRItems.forEach((qrItem, i) => {
const newQuestionnaireResponse: QuestionnaireResponse = { ...questionnaireResponse, item: [] };
for (const [i, topLevelQRItem] of topLevelQRItems.entries()) {
const qItem = topLevelQItems[i];
if (!qItem) {
continue;
}

// If QR item don't have either item.item and item.answer, continue
if (!qrItemHasItemsOrAnswer(topLevelQRItem)) {
continue;
}

const newTopLevelQRItem = removeEmptyAnswersFromItemRecursive({
qItem,
qrItem,
qrItem: topLevelQRItem,
enableWhenIsActivated,
enableWhenItems,
enableWhenExpressions
});
if (newTopLevelQRItem && questionnaireResponse.item) {
questionnaireResponse.item[i] = { ...newTopLevelQRItem };
if (newTopLevelQRItem && newQuestionnaireResponse.item) {
newQuestionnaireResponse.item.push(newTopLevelQRItem);
}
});
}

return questionnaireResponse;
return newQuestionnaireResponse;
}

interface removeEmptyAnswersFromItemRecursiveParams {
Expand All @@ -90,6 +101,11 @@ function removeEmptyAnswersFromItemRecursive(
): QuestionnaireResponseItem | null {
const { qItem, qrItem, enableWhenIsActivated, enableWhenItems, enableWhenExpressions } = params;

// If QR item don't have either item.item and item.answer, return null
if (!qrItemHasItemsOrAnswer(qrItem)) {
return null;
}

const qItems = qItem.item;
const qrItems = qrItem.item;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { ItemToRepopulate } from './repopulateItems';
import { getQrItemsIndex, mapQItemsIndex } from './mapItem';
import { isSpecificItemControl } from './itemControl';
import { questionnaireResponseStore, questionnaireStore } from '../stores';
import { qrItemHasItemsOrAnswer } from './manageForm';

/**
* Re-populate checked items in the re-population dialog into the current QuestionnaireResponse
Expand Down Expand Up @@ -67,7 +68,7 @@ export function repopulateItemsIntoResponse(
continue;
}

if (updatedTopLevelQRItem) {
if (updatedTopLevelQRItem && qrItemHasItemsOrAnswer(updatedTopLevelQRItem)) {
topLevelQrItems.push(updatedTopLevelQRItem);
}
}
Expand All @@ -79,7 +80,7 @@ function repopulateItemRecursive(
qItem: QuestionnaireItem,
qrItemOrItems: QuestionnaireResponseItem | QuestionnaireResponseItem[] | null,
checkedItemsToRepopulate: Record<string, ItemToRepopulate>
): QuestionnaireResponseItem[] | QuestionnaireResponseItem | null {
): QuestionnaireResponseItem | QuestionnaireResponseItem[] | null {
// For repeat groups
const hasMultipleAnswers = Array.isArray(qrItemOrItems);
if (hasMultipleAnswers) {
Expand Down

0 comments on commit 8d0cd2b

Please sign in to comment.