Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FI-3299: Fix Questionnaire Package verification in Payer suite #29

Merged
merged 3 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 37 additions & 140 deletions lib/davinci_dtr_test_kit/cql_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,53 +48,10 @@ def reset_cql_tests
extension_presence.each_key { |k| extension_presence[k] = false }
end

def evaluate_responses_extensions(resource)
found_questionnaire = false
resource.each do |individual_resource|
next unless individual_resource.resourceType == 'QuestionnaireResponse'

individual_resource.contained.each_with_index do |questionnaire, q_index|
# Do out put parameters have a bundle?
next unless questionnaire.resourceType == 'Questionnaire'

# check the libraries first so references in questionnaires can be checked after
found_questionnaire = true
check_questionnaire_extensions(questionnaire, q_index)
end
end
found_questionnaire
end

def evaluate_parameters_extensions(resource)
found_questionnaire = false
resource.parameter.each do |param|
# Do out put parameters have a bundle?
next unless param.resource.resourceType == 'Bundle'

param.resource.entry.each_with_index do |entry, q_index|
# check questionnaire extensions
next unless entry.resource.resourceType == 'Questionnaire'

found_questionnaire = true
check_questionnaire_extensions(entry.resource, q_index)
## NEED TO FIGURE OUT HOW TO FAIL TEST WHEN POORLY FORMATTED EXPRESSIONS FOUND
end
end
found_questionnaire
end

# extensions
def questionnaire_extensions_test(response)
resource = process_response(response)
assert !resource.nil?, 'Response is null or not a valid type.'
found_questionnaire = false
if resource.instance_of? Array
found_questionnaire = evaluate_responses_extensions(resource)
elsif resource.resourceType == 'Parameters'
found_questionnaire = evaluate_parameters_extensions(resource)
end
def verify_questionnaire_extensions(questionnaires)
assert questionnaires&.any? && questionnaires.all? { |q| q.is_a? FHIR::Questionnaire }, 'No questionnaires found.'
questionnaires.each_with_index { |q, q_index| check_questionnaire_extensions(q, q_index) }
check_library_references
assert found_questionnaire, 'No questionnaires found.'
assert extension_presence.value?(true), 'No extensions found. Questionnaire must demonstrate prepopulation.'
assert cql_presence['variable'], 'Variable expression logic not written in CQL.'
assert cql_presence['launch_context'], 'Launch context expression logic not written in CQL.'
Expand Down Expand Up @@ -140,53 +97,11 @@ def check_questionnaire_extensions(questionnaire, q_index)
assert misformatted_expressions.compact.empty?, 'Expression in questionnaire misformatted.'
end

# items
def evaluate_responses_items(resource)
found_questionnaire = false
resource.each_with_index do |individual_resource, q_index|
next unless individual_resource.resourceType == 'QuestionnaireResponse'

individual_resource.contained.each do |questionnaire|
next unless questionnaire.resourceType == 'Questionnaire'
def verify_questionnaire_items(questionnaires, final_cql_test: false)
assert questionnaires&.any? && questionnaires.all? { |q| q.is_a? FHIR::Questionnaire }, 'No questionnaires found.'
questionnaires.each_with_index { |q, q_index| check_questionnaire_items(q, q_index) }

# check the libraries first so references in questionnaires can be checked after
found_questionnaire = true
check_questionnaire_items(questionnaire, q_index)
end
end
found_questionnaire
end

def evaluate_parameters_items(resource)
found_bundle = found_questionnaire = false
resource.parameter.each do |param|
next unless param.resource.resourceType == 'Bundle'

found_bundle = true
# check the libraries first so references in questionnaires can be checked after
param.resource.entry.each_with_index do |entry, q_index|
if entry.resource.resourceType == 'Questionnaire'
found_questionnaire = true
check_questionnaire_items(entry.resource, q_index)
end
end
end
[found_bundle, found_questionnaire]
end

def questionnaire_items_test(response, final_cql_test)
resource = process_response(response)
assert !resource.nil?, 'Response is null or not a valid type.'
found_questionnaire = false
# are extensions present in any questionnaire?
if resource.instance_of? Array
found_questionnaire = evaluate_responses_items(resource)
elsif resource.resourceType == 'Parameters'
found_bundle, found_questionnaire = evaluate_parameters_items(resource)
assert found_bundle, 'No questionnaire bundles found.'
end
begin
assert found_questionnaire, 'No questionnaires found.'
assert !found_non_cql_expression, 'Found non-cql expression.'
assert extension_presence.value?(true), 'No extensions found. Questionnaire must demonstrate prepopulation.'
assert cql_presence['init_expression'], 'Initial expression logic not written in CQL.'
Expand Down Expand Up @@ -281,55 +196,43 @@ def check_questionnaire_items(questionnaire, q_index)
assert misformatted_expressions.compact.to_set.empty?, 'Expression in questionnaire misformatted.'
end

def evaluate_library(entry, lib_index)
def evaluate_library(library)
found_cql = found_elm = false
entry.resource.content.each do |content|
library.content.each do |content|
if content.data.nil?
messages << { type: 'info',
message: format_markdown("[library #{lib_index + 1}] content element included no data.") }
message: format_markdown("[library #{library.url}] content element included no data.") }
end
if content.contentType == 'text/cql'
found_cql = true
elsif content.contentType == 'application/elm+json'
found_elm = true
else
messages << { type: 'info',
message: format_markdown("[library #{lib_index + 1}] has non-cql/elm content.") }
message: format_markdown("[library #{library.url}] has non-cql/elm content.") }
true
end
next unless library_names.include? entry.resource.name
next unless library_names.include? library.name

found_duplicate_library_name = true
messages << { type: 'info', message: format_markdown("[library #{lib_index + 1}] has a name,
#{entry.resource.name}, that is already included in the bundle.") }
messages << { type: 'info', message: format_markdown("[library #{library.url}] has a name,
#{library.name}, that is already included in the bundle.") }
assert !found_duplicate_library_name, 'Found duplicate library names - all names must be unique.'
end
assert found_cql, "[library #{lib_index + 1}] does not include CQL."
assert found_elm, "[library #{lib_index + 1}] does not include ELM."
assert found_cql, "[library #{library.url}] does not include CQL."
assert found_elm, "[library #{library.url}] does not include ELM."
end

def check_libraries(payer_response)
resource = process_response(payer_response)
assert !resource.nil?, 'Response is null or not a valid type.'
found_bundle = found_libraries = false
# are extensions present in any questionnaire?
resource.parameter.each do |param|
# Do out put parameters have a bundle?
next unless param.resource.resourceType == 'Bundle'

found_bundle = true
# check the libraries first so references in questionnaires can be checked after
param.resource.entry.each_with_index do |entry, index|
next unless entry.resource.resourceType == 'Library'

found_libraries = true
library_urls.add(entry.resource.url) unless entry.resource.url.nil?
evaluate_library(entry, index)
library_names.add(entry.resource.name)
end
assert found_libraries, 'No Libraries found.'
def check_libraries(questionnaire_bundles)
libraries = extract_libraries_from_bundles(questionnaire_bundles)

assert libraries.any?, 'No Libraries found.'

libraries.each do |lib|
library_urls.add(lib.url) unless lib.url.nil?
evaluate_library(lib)
library_names.add(lib.name)
end
assert found_bundle, 'No questionnaire bundles found.'
end

def check_library_references
Expand Down Expand Up @@ -405,28 +308,22 @@ def check_for_cql(extension, extension_name, index, q_index, url, link_id = '')
end
end

def q_responses(response)
questionnaire_responses = []
response.each do |resource|
fhir_resource = FHIR.from_contents(resource.response_body)
questionnaire_responses << fhir_resource if fhir_resource.resourceType == 'QuestionnaireResponse'
next unless resource.instance_of? Inferno::Entities::Request

if fhir_resource.resourceType == 'Questionnaire' || fhir_resource.resourceType == 'Parameters'
return fhir_resource
end
end
questionnaire_responses
def extract_contained_questionnaires(questionnaire_responses)
questionnaire_responses&.filter_map do |qr|
qr.contained&.filter { |resource| resource.is_a?(FHIR::Questionnaire) }
end&.flatten&.compact
end

def process_response(response)
if response.instance_of?(FHIR::Parameters) || response.instance_of?(FHIR::QuestionnaireResponse)
return response
elsif response.instance_of? Array
return q_responses(response)
end
def extract_questionnaires_from_bundles(questionnaire_bundles)
questionnaire_bundles.filter_map do |qb|
qb.entry.filter_map { |entry| entry.resource if entry.resource.is_a?(FHIR::Questionnaire) }
end&.flatten&.compact
end

nil
def extract_libraries_from_bundles(questionnaire_bundles)
questionnaire_bundles.filter_map do |qb|
qb.entry.filter_map { |entry| entry.resource if entry&.resource.is_a?(FHIR::Library) }
end&.flatten&.compact
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class PayerAdaptiveFormLibrariesTest < Inferno::Test

run do
skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
skip_if scratch[:adaptive_responses].nil?, 'No questionnaire bundle returned.'
check_libraries(scratch[:adaptive_responses])
skip_if scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundle returned.'
check_libraries(scratch[:adaptive_questionnaire_bundles])
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ class PayerAdaptiveFormExpressionsTest < Inferno::Test

run do
skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
skip_if scratch[:adaptive_responses].nil?, 'No questionnaire bundle returned.'
questionnaire_items_test(scratch[:adaptive_responses], final_cql_test: false)
scratch[:adaptive_responses] = nil
skip_if scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundle returned.'
questionnaires = extract_questionnaires_from_bundles(scratch[:adaptive_questionnaire_bundles])
verify_questionnaire_items(questionnaires)
scratch[:adaptive_questionnaire_bundles] = nil
karlnaden marked this conversation as resolved.
Show resolved Hide resolved
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ class PayerAdaptiveFormExtensionsTest < Inferno::Test

run do
skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
skip_if scratch[:adaptive_responses].nil?, 'No questionnaire bundle returned.'
questionnaire_extensions_test(scratch[:adaptive_responses])
skip_if scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundle returned.'
questionnaires = extract_questionnaires_from_bundles(scratch[:adaptive_questionnaire_bundles])
verify_questionnaire_extensions(questionnaires)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ class PayerStaticFormExpressionsTest < Inferno::Test

run do
skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
skip_if scratch[:next_responses].nil?, 'No questionnaires returned.'
questionnaire_items_test(scratch[:next_responses], final_cql_test: true)
scratch[:next_responses] = nil
skip_if scratch[:next_question_questionnaire_responses].nil?, 'No questionnaires returned.'
questionnaires = extract_contained_questionnaires(scratch[:next_question_questionnaire_responses])
verify_questionnaire_items(questionnaires, final_cql_test: true)
scratch[:next_question_questionnaire_responses] = nil
end
end
end
karlnaden marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ class PayerStaticFormExtensionsTest < Inferno::Test

run do
skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
skip_if scratch[:next_responses].nil?, 'No questionnaires returned.'
questionnaire_extensions_test(scratch[:next_responses])
skip_if scratch[:next_question_questionnaire_responses].nil?, 'No questionnaires returned.'
questionnaires = extract_contained_questionnaires(scratch[:next_question_questionnaire_responses])
verify_questionnaire_extensions(questionnaires)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,12 @@ class PayerAdaptiveFormResponseBundlesTest < Inferno::Test
skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
test_passed = true
profile_url = 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/DTR-QPackageBundle|2.0.1'
assert !scratch[:adaptive_responses].nil?, 'No resources to validate.'
scratch[:adaptive_responses].each_with_index do |resource, index|
fhir_resource = FHIR.from_contents(resource.response[:body])
fhir_resource.parameter.each do |param|
resource_is_valid = validate_resource(param.resource, :bundle, profile_url, index)
test_passed = false unless resource_is_valid
rescue StandardError
next
end
assert !scratch[:adaptive_questionnaire_bundles].nil?, 'No resources to validate.'
scratch[:adaptive_questionnaire_bundles].each_with_index do |bundle, index|
resource_is_valid = validate_resource(bundle, :bundle, profile_url, index)
test_passed = false unless resource_is_valid
rescue StandardError
next
end
if !test_passed && !tests_failed[profile_url].blank?
assert test_passed, "Not all returned resources conform to the profile: #{profile_url}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module DaVinciDTRTestKit
class PayerAdaptiveFormResponseSearchTest < Inferno::Test
include DaVinciDTRTestKit::ValidationTest
id :payer_server_adaptive_response_search_validation_test
title 'Validate that the adaptive response contains valid a Adaptive Form Search resource'
title 'Validate that the adaptive response contains a valid Adaptive Form Search resource'
description %(
This test validates the conformance of the payer's response to the
[DTR Questionnaire for Adaptive Form Search](http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt-search)
Expand All @@ -22,18 +22,21 @@ class PayerAdaptiveFormResponseSearchTest < Inferno::Test
skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.'
test_passed = true
profile_url = 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt-search|2.0.1'
assert !scratch[:adaptive_responses].nil?, 'No resources to validate.'
scratch[:adaptive_responses].each_with_index do |resource, index|
fhir_resource = FHIR.from_contents(resource.response[:body])
fhir_resource.parameter.each do |param|
param.resource.entry.each do |entry|
resource_is_valid = validate_resource(entry.resource, :questionnaire, profile_url, index)
test_passed = false unless resource_is_valid
end
rescue StandardError
next
end
assert !scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundles to validate.'

questionnaires = scratch[:adaptive_questionnaire_bundles].filter_map do |bundle|
bundle.entry&.filter_map { |entry| entry.resource if entry.resource&.resourceType == 'Questionnaire' }
end&.flatten&.compact

assert questionnaires&.any?, 'No adaptive questionnaires to validate.'

questionnaires.each_with_index do |questionnaire, index|
resource_is_valid = validate_resource(questionnaire, :questionnaire, profile_url, index)
test_passed = false unless resource_is_valid
rescue StandardError
next
end

if !test_passed && !tests_failed[profile_url].blank?
assert test_passed, "Not all returned resources conform to the profile: #{profile_url}"
end
Expand Down
Loading
Loading