$populate silently drops open-choice answers that are not in answerOption
Describe the bug
During pre-population, @aehrc/sdc-populate drops valueCoding answers produced by an initialExpression when they do not match one of the item's answerOption codings - including on open-choice items, where FHIR allows answers outside the listed options.
What makes this hard to deal with is that nothing surfaces. The expression resolves correctly and the answer is present in the intermediate response, then it disappears before the QuestionnaireResponse is returned, no error, no warning, no entry in the OperationOutcome. An answer that was produced and then discarded looks exactly like one that was never produced, so there is nothing to triage on and each occurrence has to be traced by hand.
To Reproduce
An open-choice item that lists common diagnoses as options but has to accept any ICD-10 code recorded on the patient:
{
"linkId": "dx-primary",
"type": "open-choice",
"answerOption": [
{
"valueCoding": {
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "E11.9",
"display": "Type 2 diabetes mellitus without complications"
}
}
],
"extension": [
{
"url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
"valueExpression": {
"language": "text/fhirpath",
"expression": "%condition.code.coding.first()"
}
}
]
}
- Populate it for a patient whose
Condition.code.coding.first() is any ICD-10 coding other than E11.9, for example J44.9.
dx-primary comes back with no answer.
Expected behavior
dx-primary is answered with the populated coding. open-choice exists so that answers outside the listed options are allowed, and the same function already preserves non-matching valueString answers for exactly that reason - the coding path just misses the same consideration.
Plain choice items should keep filtering as they do today.
Additional context
There appear to be two separable defects behind this:
-
filterAndNormaliseAnswers (utils/processValueSets.ts) filters regardless of item type. valueCoding answers absent from the item's options - answerOption, expanded valueSet, or contained valueSet are dropped, and the item type is not available at that point, so open-choice cannot be distinguished from choice. The neighbouring valueString branch already carries the comment "to support open-choice questions where arbitrary strings are allowed".
-
findInAnswerOptions (utils/answerOption.ts) never matches Coding values. parseValueToAnswer passes raw FHIRPath results to it, but the signature is (options, str: string) and it compares with === against option codes and displays. Coding objects, anything from %condition.code.coding.first() and similar, never match, so a Coding that is among the options is not normalised to that option's coding. It survives today only because the later options filter happens to compare by code.
- Reproduced on
@aehrc/sdc-populate 4.7.1.
- A fix for the first defect is behaviour-changing only for
open-choice items; choice items keep their current filtering, so existing forms are unaffected.
- Found in a Da Vinci DTR prior-authorization form (CMS-0057 tooling) on an ICD-10 primary-diagnosis field: the
Condition was passed correctly, the FHIRPath resolved correctly, and the answer vanished between the two.
- I have a working implementation for both defects with regression tests, and would be happy to submit a PR if this approach is acceptable.
$populatesilently drops open-choice answers that are not inanswerOptionDescribe the bug
During pre-population,
@aehrc/sdc-populatedropsvalueCodinganswers produced by aninitialExpressionwhen they do not match one of the item'sanswerOptioncodings - including onopen-choiceitems, where FHIR allows answers outside the listed options.What makes this hard to deal with is that nothing surfaces. The expression resolves correctly and the answer is present in the intermediate response, then it disappears before the QuestionnaireResponse is returned, no error, no warning, no entry in the OperationOutcome. An answer that was produced and then discarded looks exactly like one that was never produced, so there is nothing to triage on and each occurrence has to be traced by hand.
To Reproduce
An
open-choiceitem that lists common diagnoses as options but has to accept any ICD-10 code recorded on the patient:{ "linkId": "dx-primary", "type": "open-choice", "answerOption": [ { "valueCoding": { "system": "http://hl7.org/fhir/sid/icd-10-cm", "code": "E11.9", "display": "Type 2 diabetes mellitus without complications" } } ], "extension": [ { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression", "valueExpression": { "language": "text/fhirpath", "expression": "%condition.code.coding.first()" } } ] }Condition.code.coding.first()is any ICD-10 coding other thanE11.9, for exampleJ44.9.dx-primarycomes back with no answer.Expected behavior
dx-primaryis answered with the populated coding.open-choiceexists so that answers outside the listed options are allowed, and the same function already preserves non-matchingvalueStringanswers for exactly that reason - the coding path just misses the same consideration.Plain
choiceitems should keep filtering as they do today.Additional context
There appear to be two separable defects behind this:
filterAndNormaliseAnswers(utils/processValueSets.ts) filters regardless of item type.valueCodinganswers absent from the item's options -answerOption, expanded valueSet, or contained valueSet are dropped, and the item type is not available at that point, soopen-choicecannot be distinguished fromchoice. The neighbouringvalueStringbranch already carries the comment "to support open-choice questions where arbitrary strings are allowed".findInAnswerOptions(utils/answerOption.ts) never matches Coding values.parseValueToAnswerpasses raw FHIRPath results to it, but the signature is(options, str: string)and it compares with===against option codes and displays. Coding objects, anything from%condition.code.coding.first()and similar, never match, so a Coding that is among the options is not normalised to that option's coding. It survives today only because the later options filter happens to compare bycode.@aehrc/sdc-populate4.7.1.open-choiceitems;choiceitems keep their current filtering, so existing forms are unaffected.Conditionwas passed correctly, the FHIRPath resolved correctly, and the answer vanished between the two.