Module: DaVinciDTRTestKit::DTRQuestionnaireResponseValidation
Constant Summary
collapse
- CQL_EXPRESSION_EXTENSIONS =
[
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression',
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression',
'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression'
].freeze
Constants included
from CQLTest
CQLTest::ADAPTIVE_QUESTIONNAIRE_PACKAGE_BUNDLE_PROFILE, CQLTest::ADAPTIVE_QUESTIONNAIRE_PACKAGE_PARAMETER_PROFILE, CQLTest::STATIC_QUESTIONNAIRE_PACKAGE_BUNDLE_PROFILE, CQLTest::STATIC_QUESTIONNAIRE_PACKAGE_PARAMETER_PROFILE
Instance Method Summary
collapse
-
#answer_value_equal?(expected, actual) ⇒ Boolean
-
#check_answer(link_id, override, expected_answer, answer) ⇒ Object
-
#check_answer_presence(response_items, required_link_ids = [], index = nil) ⇒ Object
Ensures that all required questions have been answered.
-
#check_is_questionnaire_response(questionnaire_response_json) ⇒ Object
-
#check_item_prepopulation(item, expected_answer, override) ⇒ Object
-
#check_missing_origin_sources(questionnaire_response, index = nil) ⇒ Object
-
#check_origin_source(origin_source, link_id, is_cql_expression, override: false, index: nil) ⇒ Object
rubocop:disable Metrics/CyclomaticComplexity.
-
#check_origin_sources(questionnaire_items, response_items, expected_overrides: [], index: nil) ⇒ Object
This only checks answers in the questionnaire response, meaning it does not catch missing answers.
-
#coding_equal?(expected, actual) ⇒ Boolean
-
#collect_questionnaire_cql_expression_link_ids(items, link_ids = []) ⇒ Object
-
#extract_expected_answers_from_template(template_questionnaire_response, questionnaire_cql_expression_link_ids, expected_prepopulated = {}, expected_overrides = {}) ⇒ Object
-
#extract_origin_sources(items, origin_sources = []) ⇒ Object
-
#extract_required_link_ids(questionnaire_items) ⇒ Object
-
#find_extension(element, url) ⇒ Object
-
#find_item_by_link_id(items, link_id) ⇒ Object
-
#find_origin_source(item) ⇒ Object
-
#item_is_cql_expression?(item) ⇒ Boolean
-
#origin_source_error(link_id, expected, actual, msg_prefix = nil) ⇒ Object
-
#validate_cql_executed(actual_items, questionnaire_cql_expression_link_ids, template_prepopulation_expectations, template_override_expectations) ⇒ Object
-
#validate_questionnaire_pre_population(questionnaire, template_questionnaire_response, questionnaire_response) ⇒ Object
Requirements: - Prior to exposing the draft QuestionnaireResponse to the user for completion and/or review, the DTR client SHALL execute all CQL necessary to resolve the initialExpression, candidateExpression and calculatedExpression extensions found in the Questionnaire for any enabled elements.
-
#validate_questionnaire_response_correctness(questionnaire_response_json, custom_response = nil) ⇒ Object
-
#value_for_display(answer) ⇒ Object
-
#verify_basic_conformance(questionnaire_response_json, profile_url = nil) ⇒ Object
Methods included from CQLTest
#add_formatting_messages, #add_item_messages, #add_launch_context_messages, #check_expression_format, #check_for_cql, #check_item_extension, #check_libraries, #check_library_references, #check_nested_items, #check_questionnaire_extensions, #check_questionnaire_items, #cqf_reference_libraries, #cql_presence, #evaluate_library, #extension_presence, #extract_bundles_from_parameter, #extract_contained_questionnaires, #extract_libraries_from_bundles, #extract_questionnaire_bundles, #extract_questionnaire_from_questionnaire_package, #extract_questionnaires_from_bundles, #found_duplicate_library_name, #found_non_cql_elm_library, #found_non_cql_expression, #found_questionnaire, #library_names, #library_urls, #perform_questionnaire_package_validation, #questionnaire_package_profile_urls, #reset_cql_tests, #total_cqf_libs, #validate_questionnaire_package, #verify_questionnaire_extensions, #verify_questionnaire_items
Instance Method Details
#answer_value_equal?(expected, actual) ⇒ Boolean
289
290
291
292
293
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 289
def answer_value_equal?(expected, actual)
return coding_equal?(expected.value, actual.value) if expected.valueCoding.present?
expected.value == actual.value
end
|
#check_answer(link_id, override, expected_answer, answer) ⇒ Object
240
241
242
243
244
245
246
247
248
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 240
def check_answer(link_id, override, expected_answer, answer)
if override && answer_value_equal?(expected_answer, answer)
add_message('error', %(Answer to item `#{link_id}` was not overriden from the pre-populated value.
Found #{expected_answer}, but should be different))
elsif !override && !answer_value_equal?(expected_answer, answer)
add_message('error', %(Answer to item `#{link_id}` contains unexpected value. Expected:
#{value_for_display(expected_answer)}. Found #{value_for_display(answer)}))
end
end
|
#check_answer_presence(response_items, required_link_ids = [], index = nil) ⇒ Object
Ensures that all required questions have been answered. If required_link_ids not provided, all questions are treated as optional.
118
119
120
121
122
123
124
125
126
127
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 118
def check_answer_presence(response_items, required_link_ids = [], index = nil)
prefix = index ? "Request #{index}: " : ''
required_link_ids.each do |link_id|
item = find_item_by_link_id(response_items, link_id)
unless item&.answer&.any? { |answer| answer.value.present? }
add_message('error', "#{prefix}No answer for item #{link_id}")
end
end
end
|
#check_is_questionnaire_response(questionnaire_response_json) ⇒ Object
54
55
56
57
58
59
60
61
62
63
64
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 54
def check_is_questionnaire_response(questionnaire_response_json)
assert_valid_json(questionnaire_response_json)
questionnaire_response = begin
FHIR.from_contents(questionnaire_response_json)
rescue StandardError
nil
end
assert questionnaire_response.present?, 'The QuestionnaireResponse is not a recognized FHIR object'
assert_resource_type(:questionnaire_response, resource: questionnaire_response)
end
|
#check_item_prepopulation(item, expected_answer, override) ⇒ Object
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 217
def check_item_prepopulation(item, expected_answer, override)
answer = item.answer.first
link_id = item.linkId
unless answer&.value&.present?
add_message('error', "No answer for item `#{link_id}`")
return
end
check_answer(link_id, override, expected_answer, answer)
origin_source = find_origin_source(item)
expected_origin_source = override ? 'override' : 'auto'
if origin_source.present?
unless origin_source == expected_origin_source
origin_source_error(link_id, expected_origin_source, origin_source)
end
else
add_message('error', "Required `origin.source` extension not present on answer to item `#{item.linkId}`")
end
end
|
#check_missing_origin_sources(questionnaire_response, index = nil) ⇒ Object
42
43
44
45
46
47
48
49
50
51
52
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 42
def check_missing_origin_sources(questionnaire_response, index = nil)
missing_origin_sources = ['auto', 'manual', 'override'] - (questionnaire_response.item)
return if missing_origin_sources.empty?
prefix = index ? "Request #{index}: " : ''
add_message(
'error',
"#{prefix}All origin sources (auto, manual, override) must be present in the QuestionnaireResponse. " \
"Missing #{missing_origin_sources.to_sentence}"
)
end
|
#check_origin_source(origin_source, link_id, is_cql_expression, override: false, index: nil) ⇒ Object
rubocop:disable Metrics/CyclomaticComplexity
96
97
98
99
100
101
102
103
104
105
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 96
def check_origin_source(origin_source, link_id, is_cql_expression, override: false, index: nil) prefix = index ? "Request #{index}: " : ''
if override
origin_source_error(link_id, ['override'], origin_source, prefix) unless origin_source == 'override'
elsif is_cql_expression && !['auto', 'override'].include?(origin_source)
origin_source_error(link_id, 'auto or override', origin_source, prefix)
elsif !is_cql_expression && origin_source != 'manual'
origin_source_error(link_id, 'manual', origin_source, prefix)
end
end
|
#check_origin_sources(questionnaire_items, response_items, expected_overrides: [], index: nil) ⇒ Object
This only checks answers in the questionnaire response, meaning it does not catch missing answers
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 73
def check_origin_sources(questionnaire_items, response_items, expected_overrides: [], index: nil)
prefix = index ? "Request #{index}: " : ''
response_items&.each do |response_item|
check_origin_sources(questionnaire_items, response_item.item, expected_overrides:, index:)
next unless response_item.answer&.any?
link_id = response_item.linkId
origin_source = find_origin_source(response_item)
questionnaire_item = find_item_by_link_id(questionnaire_items, link_id)
is_cql_expression = item_is_cql_expression?(questionnaire_item)
if origin_source.nil?
add_message('error', "#{prefix}Required `origin.source` extension not present on answer to item `#{link_id}`")
else
check_origin_source(
origin_source, link_id, is_cql_expression,
override: expected_overrides.include?(link_id),
index:
)
end
end
end
|
#coding_equal?(expected, actual) ⇒ Boolean
295
296
297
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 295
def coding_equal?(expected, actual)
expected.system == actual&.system && expected.code == actual&.code
end
|
#collect_questionnaire_cql_expression_link_ids(items, link_ids = []) ⇒ Object
277
278
279
280
281
282
283
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 277
def collect_questionnaire_cql_expression_link_ids(items, link_ids = [])
items.each do |item|
link_ids << item.linkId if item_is_cql_expression?(item)
collect_questionnaire_cql_expression_link_ids(item.item, link_ids) if item&.item&.any?
end
link_ids
end
|
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 188
def (template_questionnaire_response,
questionnaire_cql_expression_link_ids,
expected_prepopulated = {},
expected_overrides = {})
questionnaire_cql_expression_link_ids.each do |target_link_id|
target_item = find_item_by_link_id(template_questionnaire_response.item, target_link_id)
raise "Template QuestionnaireResponse missing item with link id `#{target_link_id}`" unless target_item.present?
target_item_answer = target_item.answer.first
unless target_item_answer.present?
raise "Template QuestionnaireResponse missing an answer for item with link id `#{target_link_id}`"
end
origin_source = find_origin_source(target_item)
unless origin_source.present?
raise "Template QuestionnaireResponse item `#{target_link_id}` missing the `origin.source` extension"
end
if origin_source == 'auto'
expected_prepopulated[target_link_id] = target_item_answer
elsif origin_source == 'override'
expected_overrides[target_link_id] = target_item_answer
else
raise "`origin.source` extension for item `#{target_link_id}` has unexpected value: #{origin_source}"
end
end
end
|
107
108
109
110
111
112
113
114
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 107
def (items, origin_sources = [])
items&.each do |item|
(item&.item, origin_sources)
origin_sources << find_origin_source(item) if item&.answer&.any?
end
origin_sources
end
|
129
130
131
132
133
134
135
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 129
def (questionnaire_items)
questionnaire_items.each_with_object([]) do |item, required_link_ids|
required_link_ids << item.linkId if item.required
required_link_ids.concat((item.item)) if item.item.present?
end
end
|
#find_extension(element, url) ⇒ Object
273
274
275
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 273
def find_extension(element, url)
element&.extension&.find { |e| e.url == url }
end
|
#find_item_by_link_id(items, link_id) ⇒ Object
255
256
257
258
259
260
261
262
263
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 255
def find_item_by_link_id(items, link_id)
items.each do |item|
return item if item.linkId == link_id
match = find_item_by_link_id(item.item, link_id)
return match if match
end
nil
end
|
#find_origin_source(item) ⇒ Object
265
266
267
268
269
270
271
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 265
def find_origin_source(item)
origin_extension = find_extension(
item&.answer&.first,
'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/information-origin'
)
find_extension(origin_extension, 'source')&.value
end
|
#item_is_cql_expression?(item) ⇒ Boolean
285
286
287
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 285
def item_is_cql_expression?(item)
item&.extension&.any? { |ext| CQL_EXPRESSION_EXTENSIONS.include?(ext.url) }
end
|
#origin_source_error(link_id, expected, actual, msg_prefix = nil) ⇒ Object
250
251
252
253
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 250
def origin_source_error(link_id, expected, actual, msg_prefix = nil)
add_message('error', %(#{msg_prefix}`origin.source` extension on item `#{link_id}` contains unexpected value.
Expected: #{expected}. Found: #{actual}))
end
|
#validate_cql_executed(actual_items, questionnaire_cql_expression_link_ids, template_prepopulation_expectations, template_override_expectations) ⇒ Object
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 169
def validate_cql_executed(actual_items, questionnaire_cql_expression_link_ids, template_prepopulation_expectations,
template_override_expectations)
actual_items&.each do |item_to_validate|
link_id = item_to_validate.linkId
if questionnaire_cql_expression_link_ids.include?(link_id)
if template_prepopulation_expectations.key?(link_id)
check_item_prepopulation(item_to_validate, template_prepopulation_expectations.delete(link_id), false)
elsif template_override_expectations.include?(link_id)
check_item_prepopulation(item_to_validate, template_override_expectations.delete(link_id), true)
else
raise "template missing expectation for question `#{link_id}`"
end
end
validate_cql_executed(item_to_validate.item, questionnaire_cql_expression_link_ids,
template_prepopulation_expectations, template_override_expectations)
end
end
|
#validate_questionnaire_pre_population(questionnaire, template_questionnaire_response, questionnaire_response) ⇒ Object
Requirements:
- Prior to exposing the draft QuestionnaireResponse to the user for completion and/or review, the DTR client
SHALL execute all CQL necessary to resolve the initialExpression, candidateExpression and
calculatedExpression extensions found in the Questionnaire for any enabled elements.
- All items that are pre-populated (whether by the payer in the initial QuestionnaireResponse provided in the
questionnaire package, or from data retrieved from the EHR) SHALL have their origin.source set to ‘auto’.
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 143
def validate_questionnaire_pre_population(questionnaire, template_questionnaire_response, questionnaire_response)
questionnaire_cql_expression_link_ids = collect_questionnaire_cql_expression_link_ids(questionnaire.item)
template_prepopulation_expectations = {}
template_override_expectations = {}
(template_questionnaire_response,
questionnaire_cql_expression_link_ids,
template_prepopulation_expectations,
template_override_expectations)
validate_cql_executed(questionnaire_response.item, questionnaire_cql_expression_link_ids,
template_prepopulation_expectations, template_override_expectations)
if template_prepopulation_expectations.size.positive?
add_message('error', %(Items expected to be pre-populated not found:
#{template_prepopulation_expectations.keys.join(', ')}))
end
if template_override_expectations.size.positive?
add_message('error', %(Items expected to be pre-poplated and overridden not found:
#{template_override_expectations.keys.join(', ')}))
end
assert(messages.none? { |m| m[:type] == 'error' },
'QuestionnaireResponse is not conformant. Check messages for issues found.')
end
|
#validate_questionnaire_response_correctness(questionnaire_response_json, custom_response = nil) ⇒ Object
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 12
def validate_questionnaire_response_correctness(questionnaire_response_json, custom_response = nil)
check_is_questionnaire_response(questionnaire_response_json)
qr = FHIR.from_contents(questionnaire_response_json)
questionnaire = nil
expected_overrides = []
if custom_response.blank?
questionnaire = Fixtures.questionnaire_for_test(id)
expected_overrides = ['PBD.2']
else
assert_valid_json custom_response, 'Custom response provided is not a valid JSON'
questionnaire = (
custom_response, qr.questionnaire
)
skip_if questionnaire.blank?,
"Couldn't find Questionnaire #{qr.questionnaire} in the provided custom questionnaire package
to validate the QuestionnaireResponse."
check_missing_origin_sources(qr)
end
check_origin_sources(questionnaire.item, qr.item, expected_overrides:)
required_link_ids = (questionnaire.item)
check_answer_presence(qr.item, required_link_ids)
assert(messages.none? { |m| m[:type] == 'error' }, 'QuestionnaireResponse is not correct, see error message(s)')
end
|
#value_for_display(answer) ⇒ Object
299
300
301
302
303
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 299
def value_for_display(answer)
return "#{answer.value&.system}|#{answer.value&.code}" if answer.valueCoding.present?
answer.value
end
|
66
67
68
69
70
|
# File 'lib/davinci_dtr_test_kit/dtr_questionnaire_response_validation.rb', line 66
def verify_basic_conformance(questionnaire_response_json, profile_url = nil)
profile_url ||= 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaireresponse|2.0.1'
check_is_questionnaire_response(questionnaire_response_json)
assert_valid_resource(resource: FHIR.from_contents(questionnaire_response_json), profile_url:)
end
|