Module: DaVinciPASTestKit::PasBundleValidation

Constant Summary collapse

US_CORE_VERSION =
'6.1.0'
US_CORE_PROFILE_BASE =
'http://hl7.org/fhir/us/core/StructureDefinition'
BASE_R4_PROFILE =
:base_r4
CLAIM_ENCOUNTER_EXTENSION_URL =
'http://hl7.org/fhir/5.0/StructureDefinition/extension-Claim.encounter'
LOINC_SYSTEM =
'http://loinc.org'
TERMINOLOGY_CONDITION_CATEGORY_SYSTEM =
'http://terminology.hl7.org/CodeSystem/condition-category'
OBSERVATION_CATEGORY_SYSTEM =
'http://terminology.hl7.org/CodeSystem/observation-category'
DIAGNOSTIC_REPORT_CATEGORY_SYSTEM =
'http://terminology.hl7.org/CodeSystem/v2-0074'
US_CORE_SINGLE_PROFILE_IDS_BY_RESOURCE =
{
  'AllergyIntolerance' => 'us-core-allergyintolerance',
  'CarePlan' => 'us-core-careplan',
  'CareTeam' => 'us-core-careteam',
  'Coverage' => 'us-core-coverage',
  'Device' => 'us-core-implantable-device',
  'DocumentReference' => 'us-core-documentreference',
  'Encounter' => 'us-core-encounter',
  'Goal' => 'us-core-goal',
  'Immunization' => 'us-core-immunization',
  'Location' => 'us-core-location',
  'Medication' => 'us-core-medication',
  'MedicationDispense' => 'us-core-medicationdispense',
  'MedicationRequest' => 'us-core-medicationrequest',
  'Organization' => 'us-core-organization',
  'Patient' => 'us-core-patient',
  'Practitioner' => 'us-core-practitioner',
  'PractitionerRole' => 'us-core-practitionerrole',
  'Procedure' => 'us-core-procedure',
  'Provenance' => 'us-core-provenance',
  'QuestionnaireResponse' => 'us-core-questionnaireresponse',
  'RelatedPerson' => 'us-core-relatedperson',
  'ServiceRequest' => 'us-core-servicerequest',
  'Specimen' => 'us-core-specimen'
}.freeze
US_CORE_CONDITION_ENCOUNTER_DIAGNOSIS_PROFILE_ID =
'us-core-condition-encounter-diagnosis'
US_CORE_CONDITION_PROBLEMS_HEALTH_CONCERNS_PROFILE_ID =
'us-core-condition-problems-health-concerns'
US_CORE_DIAGNOSTIC_REPORT_LAB_PROFILE_ID =
'us-core-diagnosticreport-lab'
US_CORE_DIAGNOSTIC_REPORT_NOTE_PROFILE_ID =
'us-core-diagnosticreport-note'
US_CORE_OBSERVATION_CLINICAL_RESULT_PROFILE_ID =
'us-core-observation-clinical-result'
US_CORE_OBSERVATION_LAB_PROFILE_ID =
'us-core-observation-lab'
US_CORE_OBSERVATION_SCREENING_ASSESSMENT_PROFILE_ID =
'us-core-observation-screening-assessment'
US_CORE_SIMPLE_OBSERVATION_PROFILE_ID =
'us-core-simple-observation'
US_CORE_SMOKING_STATUS_PROFILE_ID =
'us-core-smokingstatus'
US_CORE_VITAL_SIGNS_PROFILE_ID =
'us-core-vital-signs'
US_CORE_OBSERVATION_CODE_PROFILE_IDS =
{
  '11341-5' => 'us-core-observation-occupation',
  '86645-9' => 'us-core-observation-pregnancyintent',
  '82810-3' => 'us-core-observation-pregnancystatus',
  '76690-7' => 'us-core-observation-sexual-orientation',
  '8289-1' => 'head-occipital-frontal-circumference-percentile',
  '59576-9' => 'pediatric-bmi-for-age',
  '77606-2' => 'pediatric-weight-for-height',
  '85354-9' => 'us-core-blood-pressure',
  '39156-5' => 'us-core-bmi',
  '8302-2' => 'us-core-body-height',
  '8310-5' => 'us-core-body-temperature',
  '29463-7' => 'us-core-body-weight',
  '9843-4' => 'us-core-head-circumference',
  '8867-4' => 'us-core-heart-rate',
  '59408-5' => 'us-core-pulse-oximetry',
  '2708-6' => 'us-core-pulse-oximetry',
  '9279-1' => 'us-core-respiratory-rate',
  '72166-2' => 'us-core-smokingstatus'
}.freeze

Instance Method Summary collapse

Methods included from ParametersHelper

#extract_bundles_from_pas_inquiry_response_parameters

Methods included from ValidationTest

#perform_validation_test

Instance Method Details

#absolute_url(reference, base_url) ⇒ Object



655
656
657
658
659
660
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 655

def absolute_url(reference, base_url)
  return if reference.blank?
  return reference if base_url.blank? || reference.starts_with?('urn:uuid:') || URI(reference).absolute?

  "#{base_url}/#{reference}"
end

#add_declared_profiles(instance, bundle_entry, version) ⇒ Object

Adds declared profiles from an instance (meta.profile) to the resource target profile map. It recursively processes each entry for further profile extraction.

Parameters:

  • instance (Object)

    The instance from which profiles are extracted.

  • bundle_entry (Array)

    The bundle.entry contents.

  • version (String)

    The IG version.



602
603
604
605
606
607
608
609
610
611
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 602

def add_declared_profiles(instance, bundle_entry, version)
  return unless instance.resource.present?

  instance.resource.meta&.profile&.each do |url|
    next if bundle_resources_target_profile_map[instance.fullUrl][:profile_urls].include?(url)

    bundle_resources_target_profile_map[instance.fullUrl][:profile_urls] << url
    extract_profiles_to_validate_each_entry(bundle_entry, instance, url, version)
  end
end

#add_profile_to_instance(instance, profile_url, bundle_entry, version) ⇒ Object

Adds a specific profile URL to an instance in the resource target profile map. It recursively processes the instance for further profile extraction.

Parameters:

  • instance (Object)

    The instance to which the profile URL is added.

  • profile_url (String)

    The profile URL to be added.

  • bundle_entry (Array)

    The bundle.entry contents.

  • version (String)

    The IG version.



619
620
621
622
623
624
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 619

def add_profile_to_instance(instance, profile_url, bundle_entry, version)
  return if bundle_resources_target_profile_map[instance.fullUrl][:profile_urls].include?(profile_url)

  bundle_resources_target_profile_map[instance.fullUrl][:profile_urls] << profile_url
  extract_profiles_to_validate_each_entry(bundle_entry, instance, profile_url, version)
end

#add_resource_target_profile_to_map(resource_full_url, resource, profile_url = nil) ⇒ Object

Adds a resource and its associated profile URL to the resource target profile map. If the resource is already in the map, it adds the profile URL to the resource's list of profile URLs.

Parameters:

  • resource_full_url (String)

    The full URL of the resource.

  • resource (Object)

    The resource object.

  • profile_url (String) (defaults to: nil)

    The profile URL to associate with the resource.



293
294
295
296
297
298
299
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 293

def add_resource_target_profile_to_map(resource_full_url, resource, profile_url = nil)
  entry = bundle_resources_target_profile_map[resource_full_url] ||= { resource:, profile_urls: [] }

  return if profile_url.blank? || entry[:profile_urls].include?(profile_url)

  entry[:profile_urls] << profile_url
end

#add_us_core_profiles_to_unprofiled_entries(bundle_entry, version) ⇒ Object



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 339

def add_us_core_profiles_to_unprofiled_entries(bundle_entry, version)
  return unless us_core_profile_fallback_enabled?(version)

  bundle_entry.each do |entry|
    resource = entry.resource
    next if resource.blank?

    resource_full_url = resource_full_url_for_entry(entry)
    next if bundle_resources_target_profile_map[resource_full_url]&.dig(:profile_urls).present?

    profile_urls = us_core_profile_urls_for_resource(resource)
    profile_urls = [BASE_R4_PROFILE] if profile_urls.blank?

    profile_urls.each do |profile_url|
      add_resource_target_profile_to_map(resource_full_url, resource, profile_url)
    end
  end
end

#bundle_entry_map(bundle_entry) ⇒ Object



637
638
639
640
641
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 637

def bundle_entry_map(bundle_entry)
  @bundle_entry_map ||= bundle_entry.to_h do |entry|
    [entry.fullUrl, entry]
  end
end

#bundle_profile_valid?(bundle, profile_url) ⇒ Boolean

Validates a Bundle resource against a profile, filtering out entry-resource-level issues that are covered by individual resource validation to avoid duplicate messages.

Parameters:

  • bundle (FHIR::Bundle)

    The Bundle resource to validate.

  • profile_url (String)

    The profile URL (with version) to validate against.

Returns:

  • (Boolean)

    true if no unfiltered errors remain after removing entry-resource issues.



454
455
456
457
458
459
460
461
462
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 454

def bundle_profile_valid?(bundle, profile_url)
  response_details = []
  resource_is_valid?(resource: bundle, profile_url:,
                     add_messages_to_runnable: false,
                     validator_response_details: response_details)
  bundle_level_issues = reject_entry_resource_issues(response_details)
  bundle_level_issues.each { |issue| messages << { type: issue.severity, message: issue.message } }
  bundle_level_issues.none? { |issue| issue.severity == 'error' }
end

#bundle_resources_target_profile_mapHash

Returns a hash map where the keys are resource full URLs and the values are a hash containing the resource object and an array of associated profile URLs.

Returns:

  • (Hash)

    The resource target profile map.



284
285
286
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 284

def bundle_resources_target_profile_map
  @bundle_resources_target_profile_map ||= {}
end

#category_code_present?(resource, code, system:) ⇒ Boolean

Returns:

  • (Boolean)


436
437
438
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 436

def category_code_present?(resource, code, system:)
  codeable_concept_has_code?(resource.category, code, system:)
end

#check_presence_of_referenced_resources(target_resource, base_url, resources_to_match, skip_claim_related: false) ⇒ Object

This method traverses references within a FHIR resource, ensuring that referenced resources are populated in the bundle. It also enforces that a referenced resource appears only once in the bundle, as required by the PAS IG.

Parameters:

  • target_resource (FHIR::Model)

    The FHIR resource to traverse and validate.

  • base_url (String)

    The server base url.

  • resources_to_match (Array<FHIR:Bundle:Entry] The list of FHIR bundle entries to match references against.)

    esources_to_match [Array<FHIR:Bundle:Entry] The list of FHIR bundle entries to match references against.

  • skip_claim_related (Boolean) (defaults to: false)

    When true, a Claim's related element is not traversed. This is set for any Claim reached by following a reference (i.e. a non-primary Claim in a Claim Update chain), whose own Claim.related.claim (the grandparent) is deliberately omitted from the Bundle per spec-65/66. The primary Claim passed in by the caller keeps skip_claim_related: false, so its parent reference - and the parent's own referenced resources - are still checked.



763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 763

def check_presence_of_referenced_resources(target_resource, base_url, resources_to_match,
                                           skip_claim_related: false)
  return if target_resource.blank?

  if target_resource.is_a?(FHIR::Reference) && target_resource.reference.present?
    ref = target_resource.reference
    absolute_ref = absolute_url(ref, base_url)
    matching_resources = resources_to_match.find_all { |res| res.fullUrl == absolute_ref }

    if matching_resources.length != 1
      validation_error_messages << resource_shall_appear_once_message(absolute_ref,
                                                                      matching_resources.length)
    end

    if matching_resources.length.positive?
      # A resource reached by following a reference is an included resource, not the primary Claim
      # being validated; if it is itself a Claim Update, its referenced grandparent Claim is omitted.
      check_presence_of_referenced_resources(matching_resources.first.resource, base_url, resources_to_match,
                                             skip_claim_related: true)
    end
  else
    target_resource.source_hash.each_key do |attr|
      next if claim_response_request_attr?(target_resource, attr)
      next if skip_claim_related && claim_related_attr?(target_resource, attr)

      value = target_resource.send(attr.to_sym)
      if value.is_a?(FHIR::Model)
        check_presence_of_referenced_resources(value, base_url, resources_to_match, skip_claim_related:)
      elsif value.is_a?(Array) && value.all?(FHIR::Model)
        value.each do |elmt|
          check_presence_of_referenced_resources(elmt, base_url, resources_to_match, skip_claim_related:)
        end
      end
    end
  end
end

Claim.related.claim points to the Claim being updated. For a non-primary Claim in a Claim Update chain (one reached by following a reference), that prior Claim is the grandparent, which spec-65/66 require to be omitted from the Bundle. Used with the skip_claim_related flag so the generic reference-presence check does not flag the deliberately-omitted grandparent as missing.

Returns:

  • (Boolean)


815
816
817
818
819
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 815

def claim_related_attr?(resource, attr)
  attr.to_s == 'related' &&
    resource.respond_to?(:resourceType) &&
    resource.resourceType == 'Claim'
end

#claim_response_request_attr?(resource, attr) ⇒ Boolean

ClaimResponse.request is a back-reference to the submitted Claim. The PAS IG response bundle profile (profile-pas-response-bundle) has no required Claim entry slice, so the Claim need not be present in the response bundle. Skipping here matches the identical guard in ResponseGenerator#referenced_entities, which also skips ClaimResponse.request when building mock response bundles.

Returns:

  • (Boolean)


805
806
807
808
809
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 805

def claim_response_request_attr?(resource, attr)
  attr.to_s == 'request' &&
    resource.respond_to?(:resourceType) &&
    resource.resourceType == 'ClaimResponse'
end

#codeable_concept_has_code?(codeable_concepts, code, system:) ⇒ Boolean

Returns:

  • (Boolean)


440
441
442
443
444
445
446
447
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 440

def codeable_concept_has_code?(codeable_concepts, code, system:)
  systems = Array(system).compact
  Array(codeable_concepts).compact.any? do |codeable_concept|
    Array(codeable_concept&.coding).compact.any? do |coding|
      coding.code == code && systems.include?(coding.system)
    end
  end
end

#determine_claim_submit_profile_url(version, claim) ⇒ String

Determines the target profile URL for a Claim resource in a submit request bundle.

In v2.2.1, profile-pas-request-bundle permits either profile-claim or profile-claim-update. The structural discriminator is Claim.related: profile-claim-update requires it (must-support) while profile-claim disallows it (max: 0). For all other versions, profile-claim-update is used unconditionally.

Parameters:

  • version (String)

    The IG version (e.g. '2.2.1').

  • claim (FHIR::Claim)

    The Claim resource to inspect.

Returns:

  • (String)

    The profile URL to validate against.



704
705
706
707
708
709
710
711
712
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 704

def determine_claim_submit_profile_url(version, claim)
  return PASConstants::CLAIM_PROFILE unless version == '2.2.1'

  if claim.related.blank?
    PASConstants::CLAIM_PROFILE_FIRST_SUBMIT
  else
    PASConstants::CLAIM_PROFILE
  end
end

#extract_base_url(absolute_url) ⇒ String

Extracts the base URL from an absolute URL by removing the resource type and ID.

Parameters:

  • absolute_url (String)

    The absolute URL.

Returns:

  • (String)

    The base URL, or an empty string if the URL format is not as expected.



665
666
667
668
669
670
671
672
673
674
675
676
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 665

def extract_base_url(absolute_url)
  return '' if absolute_url.blank?

  uri = URI(absolute_url)
  return '' unless uri.scheme && uri.host

  # Split the path segments and remove the last two segments (resource type and id)
  path_segments = uri.path.split('/')
  base_path = path_segments[0...-2].join('/')

  "#{uri.scheme}://#{uri.host}#{base_path}"
end

#extract_profiles_to_validate_each_entry(bundle_entry, current_entry, current_entry_profile_url, version) ⇒ Object

Processes each entry in a FHIR bundle to extract resource and possible profiles to validate against. It recursively evaluates referenced instances and their profiles, expanding the validation scope.

Parameters:

  • bundle_entry (Object)

    The current bundle entry being processed.

  • current_entry (Object)

    The current entry within the bundle.

  • current_entry_profile_url (String)

    The profile URL associated with the current entry.

  • version (String)

    The IG version.



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 487

def extract_profiles_to_validate_each_entry(bundle_entry, current_entry, current_entry_profile_url, version)
  return if current_entry.blank?

  # NOTE: the IG does not have the metadata for us-core profiles.
   = ("v#{version}")[profile_url_without_version(current_entry_profile_url)]
  return if .blank?

  bundle_map = bundle_entry_map(bundle_entry)
  reference_elements = .references.dup

  # Special handling for Claim submit profile
  claim_submit_profile_urls = [
    PASConstants::CLAIM_PROFILE,
    PASConstants::CLAIM_PROFILE_FIRST_SUBMIT
  ]
  if claim_submit_profile_urls.include?(current_entry_profile_url)
    handle_claim_profile(reference_elements,
                         current_entry_profile_url)
  end

  reference_elements.each do |reference_element|
    process_reference_element(reference_element, current_entry, bundle_entry, bundle_map, version)
  end
end

#extract_resources_from_bundle(bundle: nil, response: nil, reply_handler: nil, max_pages: 20) ⇒ Object

Extracts resources from a bundle while following "next" links.

This method extracts resources from a FHIR bundle, following "next" links in the bundle until the specified maximum number of pages is reached. It collects resources and invokes the reply_handler for each response.

Parameters:

  • bundle (FHIR::Bundle) (defaults to: nil)

    The initial FHIR bundle to extract resources from.

  • response (Object) (defaults to: nil)

    The HTTP response object for the bundle retrieval.

  • reply_handler (Proc) (defaults to: nil)

    A callback function to handle responses.

  • max_pages (Integer) (defaults to: 20)

    The maximum number of pages to process.



831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 831

def extract_resources_from_bundle(
  bundle: nil,
  response: nil,
  reply_handler: nil,
  max_pages: 20
)
  page_count = 1
  resources = []

  until bundle.nil? || page_count == max_pages
    resources += bundle&.entry&.map { |entry| entry&.resource }
    next_bundle_link = bundle&.link&.find { |link| link.relation == 'next' }&.url
    reply_handler&.call(response)

    break if next_bundle_link.blank?

    page_count += 1
  end

  resources
end

#find_profile_url(request_type) ⇒ Object

Resource Types to validate in request/ response bundle



679
680
681
682
683
684
685
686
687
688
689
690
691
692
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 679

def find_profile_url(request_type)
  {
    'Claim' => if request_type.start_with?('submit')
                 PASConstants::CLAIM_PROFILE
               else
                 PASConstants::CLAIM_INQUIRY_PROFILE
               end,
    'ClaimResponse' => if %w[submit submit_response].include?(request_type)
                         PASConstants::CLAIM_RESPONSE_PROFILE
                       else
                         PASConstants::CLAIM_INQUIRY_RESPONSE_PROFILE
                       end
  }
end

#find_referenced_instance_in_bundle(reference, enclosing_entry_fullurl, bundle_map) ⇒ Object

Finds a referenced instance in a FHIR bundle based on a reference and the full URL of the enclosing entry.

Parameters:

  • reference (String)

    The reference to find.

  • enclosing_entry_fullurl (String)

    The full URL of the enclosing entry.

  • bundle_map (Hash)

    A map of the bundle contents.

Returns:

  • (Object)

    The found instance, or nil if not found.



648
649
650
651
652
653
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 648

def find_referenced_instance_in_bundle(reference, enclosing_entry_fullurl, bundle_map)
  base_url = extract_base_url(enclosing_entry_fullurl)
  key = absolute_url(reference, base_url)

  bundle_map[key]
end

#generate_non_conformance_message(item) ⇒ Object



476
477
478
479
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 476

def generate_non_conformance_message(item)
  "#{item[:resource].resourceType}/#{item[:resource].id} is not conformant to any of the " \
    "target profiles: #{item[:profile_urls]}."
end

#handle_claim_profile(reference_elements, current_entry_profile_url) ⇒ Object

Handles the special case for the Claim profile in a FHIR bundle. It adds missing reference elements for the Claim profile. Claim.item.extension:requestedService value is a reference, but somehow not included in the metadata references.

Parameters:

  • reference_elements (Array)

    The array of reference elements to be updated.

  • current_entry_profile_url (String)

    The profile URL of the current entry being processed.



517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 517

def handle_claim_profile(reference_elements, current_entry_profile_url)
  claim_submit_profile_urls = [
    PASConstants::CLAIM_PROFILE,
    PASConstants::CLAIM_PROFILE_FIRST_SUBMIT
  ]
  return unless claim_submit_profile_urls.include?(current_entry_profile_url)

  claim_ref_element = {
    path: 'Claim.item.extension.value',
    profiles: [
      'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-medicationrequest',
      'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-servicerequest',
      'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-devicerequest',
      'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-nutritionorder'
    ]
  }

  # This temporary traversal handling may be replaced later by shared metadata-driven navigation logic.
  claim_encounter_ref_element = {
    path: "Claim.extension.where(url = '#{CLAIM_ENCOUNTER_EXTENSION_URL}').value",
    profiles: [
      'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/profile-encounter'
    ]
  }

  reference_elements << claim_ref_element
  reference_elements << claim_encounter_ref_element
end

#metadata_map(version) ⇒ Object

Mapping profile url to metadata



627
628
629
630
631
632
633
634
635
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 627

def (version)
  @metadata ||= {}
  @metadata[version] ||= YAML.load_file(File.join(__dir__, "generated/#{version}/metadata.yml"),
                                        aliases: true)
  @metadata_map ||= {}
  @metadata_map[version] ||= @metadata[version][:profiles].to_h do ||
    [[:profile_url], Generator::ProfileMetadata.new()]
  end
end

#perform_bundle_validation(bundle, operation, type, ig_version) ⇒ Object



83
84
85
86
87
88
89
90
91
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 83

def perform_bundle_validation(bundle, operation, type, ig_version)
  target_profile = PASConstants.bundle_profile_url_for_operation_and_type(operation, type)
  request_type = "#{operation}_#{type}"
  if type == 'request'
    perform_request_validation(bundle, target_profile, ig_version.delete_prefix('v'), request_type)
  else
    perform_response_validation(bundle, target_profile, ig_version.delete_prefix('v'), request_type)
  end
end

#perform_request_validation(bundle, profile_url, version, request_type) ⇒ Object



93
94
95
96
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 93

def perform_request_validation(bundle, profile_url, version, request_type)
  validate_pa_request_payload_structure(bundle, request_type)
  validate_resources_conformance_against_profile(bundle, profile_url, version, request_type)
end

#perform_response_validation(response_bundle, profile_url, version, request_type, request_bundle = nil) ⇒ Object



98
99
100
101
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 98

def perform_response_validation(response_bundle, profile_url, version, request_type, request_bundle = nil)
  validate_pa_response_body_structure(response_bundle, request_bundle) if request_type == 'submit'
  validate_resources_conformance_against_profile(response_bundle, profile_url, version, request_type)
end

#process_instance_profiles(instance, bundle_entry, reference_element, version) ⇒ Object

Processes the profiles associated with a given instance in a FHIR bundle. It adds the instance's profiles to the resource target profile map and handles recursive profile extraction. The profiles collected here are possible profiles the given instance may conform to. The conformance validation will ensure that the resource is conformant to at least one of the target profiles.

Parameters:

  • instance (Object)

    The instance whose profiles are to be processed.

  • bundle_entry (Array)

    The bundle.entry contents.

  • reference_element (Hash)

    The reference element related to the instance.

  • version (String)

    The IG version.



576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 576

def process_instance_profiles(instance, bundle_entry, reference_element, version)
  add_resource_target_profile_to_map(instance.fullUrl, instance.resource)
  # Add the declared profile conformance
  add_declared_profiles(instance, bundle_entry, version)

  reference_element[:profiles].each do |profile_url|
    # NOTE: the IG does not have the metadata for us-core profiles.
     = ("v#{version}")[profile_url_without_version(profile_url)]
    resource_type = instance.resource.resourceType
    next unless &.resource == resource_type || profile_url.include?(resource_type)

    add_profile_to_instance(instance, profile_url, bundle_entry, version)
    # NOTE: The algorithm assumes OR semantics for profile conformance, where the resource needs to conform to
    # at least one of the collected profiles. However, it may not cover all scenarios, such as cases
    # where AND semantics are required for multiple profile conformance. Also, it does not address complex
    # situations where profile requirements may conflict or have dependencies across referenced instances.
    # Therefore, this algorithm may not provide complete validation for all scenarios, and additional checks may be
    # necessary depending on the use case.
  end
end

#process_reference_element(reference_element, current_entry, bundle_entry, bundle_map, version) ⇒ Object

Processes a given reference element definition from a FHIR bundle entry. It evaluates FHIRPath expressions and processes each referenced instance and its profiles.

Parameters:

  • reference_element (Hash)

    The reference element to process.

  • current_entry (Object)

    The current entry within the FHIR bundle.

  • bundle_entry (Array)

    The bundle.entry.

  • bundle_map (Hash)

    A map of the bundle contents.

  • version (String)

    The FHIR version.



553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 553

def process_reference_element(reference_element, current_entry, bundle_entry, bundle_map, version)
  fhirpath_result = evaluate_fhirpath(resource: current_entry.resource, path: reference_element[:path])
  reference_element_values = fhirpath_result.filter_map do |entry|
    entry['element']&.reference if entry['type'] == 'Reference'
  end

  referenced_instances = reference_element_values.filter_map do |value|
    find_referenced_instance_in_bundle(value, current_entry.fullUrl, bundle_map)
  end

  referenced_instances.each do |instance|
    process_instance_profiles(instance, bundle_entry, reference_element, version)
  end
end

#profile_url_for_validation(url, base_profile, version) ⇒ Object



326
327
328
329
330
331
332
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 326

def profile_url_for_validation(url, base_profile, version)
  url = url.to_s
  return url if url.include?('|') || profile_url_without_version(url) == base_profile
  return "#{url}|#{US_CORE_VERSION}" if us_core_profile_url?(url)

  "#{url}|#{version}"
end

#profile_url_without_version(url) ⇒ Object



428
429
430
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 428

def profile_url_without_version(url)
  url.to_s.split('|', 2).first
end

#reject_entry_resource_issues(issues) ⇒ Array<ValidatorIssue>

Rejects validator issues that are already filtered or are located at/below Bundle.entry.resource, since those are validated individually per resource. Entry resources that receive direct PAS or US Core target profiles are validated individually after this filter.

Parameters:

  • issues (Array<ValidatorIssue>)

    Raw issues from the validator.

Returns:

  • (Array<ValidatorIssue>)

    Issues relevant only to the Bundle structure itself.



470
471
472
473
474
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 470

def reject_entry_resource_issues(issues)
  issues.reject do |issue|
    issue.filtered || issue.location&.match?(/\ABundle\.entry\[\d+\]\.resource/)
  end
end

#reset_bundle_profile_inference_stateObject



334
335
336
337
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 334

def reset_bundle_profile_inference_state
  bundle_resources_target_profile_map.clear
  @bundle_entry_map = nil
end

#resource_full_url_for_entry(entry) ⇒ Object



358
359
360
361
362
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 358

def resource_full_url_for_entry(entry)
  resource = entry.resource

  entry.fullUrl.presence || "#{resource.resourceType}/#{resource.id}"
end

#resource_present_in_pa_request_and_response_msg(resource) ⇒ Object

Generates a message for a resource present in both the PA request and response bundles.

This method generates an error message when a resource appears in both the PA request and response bundles but does not have the same fullUrl or identifiers.

Parameters:

  • resource (FHIR::Model)

    The resource present in both bundles.



871
872
873
874
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 871

def resource_present_in_pa_request_and_response_msg(resource)
  "Resource #{resource.resourceType}/#{resource.id} is an entry in both the PA Request Bundle and the Response " \
    'Bundle, but they do not have the same fullUrl or identifiers'
end

#resource_shall_appear_once_message(absolute_ref, total_matches) ⇒ Object

Generates a message for a resource that appears more than once in a bundle.

This method generates an error message when a referenced resource appears more than once in a FHIR bundle, which is not allowed.

Parameters:

  • reference_resource_type (String)

    The resource type being referenced.

  • reference_resource_id (String)

    The resource ID being referenced.

  • total_matches (Integer)

    The total number of matches found in the bundle.



861
862
863
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 861

def resource_shall_appear_once_message(absolute_ref, total_matches)
  " The referenced #{absolute_ref} resource SHALL appear exactly once in the Bundle, but found #{total_matches}."
end

#us_core_condition_profile_ids(resource) ⇒ Object



384
385
386
387
388
389
390
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 384

def us_core_condition_profile_ids(resource)
  if category_code_present?(resource, 'encounter-diagnosis', system: TERMINOLOGY_CONDITION_CATEGORY_SYSTEM)
    [US_CORE_CONDITION_ENCOUNTER_DIAGNOSIS_PROFILE_ID]
  else
    [US_CORE_CONDITION_PROBLEMS_HEALTH_CONCERNS_PROFILE_ID]
  end
end

#us_core_diagnostic_report_profile_ids(resource) ⇒ Object



392
393
394
395
396
397
398
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 392

def us_core_diagnostic_report_profile_ids(resource)
  if category_code_present?(resource, 'LAB', system: DIAGNOSTIC_REPORT_CATEGORY_SYSTEM)
    [US_CORE_DIAGNOSTIC_REPORT_LAB_PROFILE_ID]
  else
    [US_CORE_DIAGNOSTIC_REPORT_NOTE_PROFILE_ID]
  end
end

#us_core_observation_profile_ids(resource) ⇒ Object



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 400

def us_core_observation_profile_ids(resource)
  code_profile_id = US_CORE_OBSERVATION_CODE_PROFILE_IDS.find do |code, _profile_id|
    codeable_concept_has_code?(resource.code, code, system: LOINC_SYSTEM)
  end&.last
  return [code_profile_id] if code_profile_id.present?

  profile_ids = []
  profile_ids << US_CORE_SMOKING_STATUS_PROFILE_ID if category_code_present?(resource, 'social-history',
                                                                             system: OBSERVATION_CATEGORY_SYSTEM)
  profile_ids << US_CORE_VITAL_SIGNS_PROFILE_ID if category_code_present?(resource, 'vital-signs',
                                                                          system: OBSERVATION_CATEGORY_SYSTEM)
  if category_code_present?(resource, 'survey', system: OBSERVATION_CATEGORY_SYSTEM)
    profile_ids << US_CORE_OBSERVATION_SCREENING_ASSESSMENT_PROFILE_ID
  end
  # Keep candidate profiles most-specific first because validation stops at the first conformant profile.
  profile_ids << if category_code_present?(resource, 'laboratory', system: OBSERVATION_CATEGORY_SYSTEM)
                   US_CORE_OBSERVATION_LAB_PROFILE_ID
                 else
                   US_CORE_OBSERVATION_CLINICAL_RESULT_PROFILE_ID
                 end
  profile_ids << US_CORE_SIMPLE_OBSERVATION_PROFILE_ID
  profile_ids.uniq
end

#us_core_profile_fallback_enabled?(version) ⇒ Boolean

Returns:

  • (Boolean)


364
365
366
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 364

def us_core_profile_fallback_enabled?(version)
  version.to_s.delete_prefix('v').match?(/\A2\.2(\.|\z)/)
end

#us_core_profile_url?(url) ⇒ Boolean

Returns:

  • (Boolean)


432
433
434
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 432

def us_core_profile_url?(url)
  profile_url_without_version(url).start_with?("#{US_CORE_PROFILE_BASE}/")
end

#us_core_profile_urls(profile_ids) ⇒ Object



424
425
426
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 424

def us_core_profile_urls(profile_ids)
  Array(profile_ids).map { |profile_id| "#{US_CORE_PROFILE_BASE}/#{profile_id}|#{US_CORE_VERSION}" }
end

#us_core_profile_urls_for_resource(resource) ⇒ Object



368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 368

def us_core_profile_urls_for_resource(resource)
  profile_ids =
    case resource.resourceType
    when 'Condition'
      us_core_condition_profile_ids(resource)
    when 'DiagnosticReport'
      us_core_diagnostic_report_profile_ids(resource)
    when 'Observation'
      us_core_observation_profile_ids(resource)
    else
      Array(US_CORE_SINGLE_PROFILE_IDS_BY_RESOURCE[resource.resourceType])
    end

  us_core_profile_urls(profile_ids)
end

#valid_url_or_urn_uuid?(string) ⇒ Boolean

Checks if a string is a valid url or in the form “urn:uuid:[some guid]”

Parameters:

  • string (String)

    The url string to check

Returns:

  • (Boolean)

    true if valid url or urn_uuid, otherwise false



745
746
747
748
749
750
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 745

def valid_url_or_urn_uuid?(string)
  url_regex = /\A#{URI::DEFAULT_PARSER.make_regexp(%w[http https])}\z/
  urn_uuid_regex = /\Aurn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i

  string&.match?(url_regex) || string&.match?(urn_uuid_regex)
end

#validate_bundle_entries_against_profiles(version) ⇒ Object

Validates bundle resource and each entry in the bundle against its target profiles. It logs a message for each conformant entry and collects error messages for non-conformant entries. Asserts that there are no validation errors.

Parameters:

  • version (String)

    The version of the IG.



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 305

def validate_bundle_entries_against_profiles(version)
  bundle_resources_target_profile_map.each do |key, item|
    resource = item[:resource]
    base_profile = FHIR::Definitions.resource_definition(resource.resourceType).url

    success_profile = item[:profile_urls].find do |url|
      if key == 'bundle'
        profile_to_validate = profile_url_for_validation(url, base_profile, version)
        bundle_profile_valid?(resource, profile_to_validate)
      elsif url == BASE_R4_PROFILE
        resource_is_valid?(resource:)
      else
        profile_to_validate = profile_url_for_validation(url, base_profile, version)
        resource_is_valid?(resource:, profile_url: profile_to_validate)
      end
    end

    validation_error_messages << generate_non_conformance_message(item) unless success_profile
  end
end

#validate_bundle_entries_full_url(bundle) ⇒ Object



734
735
736
737
738
739
740
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 734

def validate_bundle_entries_full_url(bundle)
  msg = "[Bundle/#{bundle.id}]: Bundle.entry.fullUrl values SHALL be a valid url or in the form " \
        "'urn:uuid:[some guid]'."
  bundle.entry.each do |entry|
    validation_error_messages << msg unless valid_url_or_urn_uuid?(entry.fullUrl)
  end
end

#validate_pa_request_payload_structure(bundle, request_type) ⇒ Object

Validates the structure of a Prior Authorization (PA) request Bundle.

This method performs various checks on the PA request payload, including validating the FHIR bundle structure, checking the resource type, and validating the resources referenced in the Claim resource are included in the bundle. It ensures that the first entry in the Bundle is a Claim resource and additional entries are populated with referenced resources, following the traversal of references. Duplicate resources are handled as required (appearing only once in the bundle entry).

Parameters:

  • bundle (FHIR::Bundle)

    The FHIR Bundle of the PA request.



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 162

def validate_pa_request_payload_structure(bundle, request_type)
  bundle_entry_resources = bundle.entry.map(&:resource)
  first_entry = bundle_entry_resources.first
  base_url = extract_base_url(bundle.entry.first&.fullUrl)

  check_presence_of_referenced_resources(first_entry, base_url, bundle.entry)

  if request_type == 'submit'
    unless first_entry.is_a?(FHIR::Claim)
      validation_error_messages << "[Bundle/#{bundle.id}]: The first bundle entry must be a Claim"
    end

    validate_uniqueness_of_supporting_info_sequences(first_entry)
    validate_bundle_entries_full_url(bundle)
  else
    claim_resource = bundle_entry_resources.find { |resource| resource.resourceType == 'Claim' }
    if claim_resource.blank?
      validation_error_messages << "[Bundle/#{bundle.id}]: Claim must be present for inquiry request"
    end

    # The inquiry operation must contain a requesting provider organization,
    # a payer organization, and a patient for the inquiry
    patient_reference = claim_resource&.patient&.reference
    provider_reference = claim_resource&.provider&.reference
    payer_reference = claim_resource&.insurer&.reference

    if patient_reference.blank?
      validation_error_messages <<
        "[Bundle/#{bundle.id}]: The Claim for inquiry operation must reference a patient."
    end
    if provider_reference.blank?
      validation_error_messages << "[Bundle/#{bundle.id}]: The claim for inquiry operation must reference " \
                                   'a requesting provider organization.'
    end
    if payer_reference.blank?
      validation_error_messages << "[Bundle/#{bundle.id}]: The Claim for inquiry operation must contain " \
                                   'a payer organization.'
    end
  end
end

#validate_pa_response_body_structure(pa_response_bundle, _pa_request_bundle) ⇒ Object

Validates the response body structure of a Prior Authorization (PA) response.

This method performs validation of the PA response bundle structure. It follows the PAS IG requirement that the FHIR Bundle generated from the response starts with a ClaimResponse entry. For response of $submit request: Additional Bundle entries are populated with resources referenced by the ClaimResponse or descendant references, ensuring that only one resource is created for a given combination of content. Resources echoed back from the request are validated to ensure the same fullUrl and resource identifiers as in the request are used.

Parameters:

  • pa_response_bundle (FHIR::Bundle)

    The FHIR bundle representing the PA response.

  • pa_request_bundle (String)

    The JSON payload of the PA request bundle.



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 217

def validate_pa_response_body_structure(pa_response_bundle, _pa_request_bundle)
  first_entry = pa_response_bundle.entry.first.resource
  unless first_entry.is_a?(FHIR::ClaimResponse)
    validation_error_messages <<
      "[Bundle/#{pa_response_bundle.id}]: The first bundle entry must be a ClaimResponse"
  end

  base_url = extract_base_url(pa_response_bundle.entry.last&.fullUrl)
  check_presence_of_referenced_resources(first_entry, base_url, pa_response_bundle.entry)

  # Testing: When echoing back resources that are the same as were present in the prior authorization request,
  # the system SHALL ensure that the same fullUrl and resource identifiers are used in the response as appeared
  # in the request
  # pa_request_bundle = FHIR.from_contents(pa_request_bundle)
  # pa_response_bundle.entry.each do |entry|
  #   res = entry.resource
  #   request_entry = pa_request_bundle.entry.find do |ent|
  #     ent.resource.resourceType == res.resourceType && ent.resource.id == res.id
  #   end
  #   next unless request_entry.present?

  #   assert(
  #     request_entry.fullUrl == entry.fullUrl && request_entry.resource.identifier == res.identifier,
  #     resource_present_in_pa_request_and_response_msg(res)
  #   )
  # end
end

#validate_pas_bundle_json(json, profile_url, version, request_type, bundle_type, skips: false, message: '') ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 103

def validate_pas_bundle_json(json, profile_url, version, request_type, bundle_type, skips: false, message: '')
  assert_valid_json(json)
  resource = FHIR.from_contents(json)
  assert resource.present?, 'Not a FHIR resource'

  # For v2.2.1 inquire responses, expect Parameters resource
  if version == '2.2.1' && request_type == 'inquire' && bundle_type == 'response_bundle'
    if resource.resourceType == 'Parameters'
      # Extract and validate each Bundle in the Parameters
      bundles = extract_bundles_from_pas_inquiry_response_parameters(resource)

      bundles.each do |bundle|
        perform_response_validation(bundle, profile_url, version, request_type)
      end
    elsif resource.is_a?(FHIR::Bundle)
      # Bundle received instead of Parameters - validate it but log an error
      validation_error_messages << 'Expected Parameters resource for v2.2.1 inquire response, but received ' \
                                   'Bundle. The response Bundle should be wrapped in a ' \
                                   'Parameters resource with a return parameter.'
      perform_response_validation(resource, profile_url, version, request_type)
    else
      assert false,
             "Expected Parameters resource for v2.2.1 inquire response, but received #{resource.resourceType}"
    end
  else
    # For v2.0.1 or non-inquire operations, expect Bundle resource
    assert_resource_type(:bundle, resource: resource)
    bundle = resource

    if bundle_type == 'request_bundle'
      perform_request_validation(bundle, profile_url, version, request_type)
    else
      perform_response_validation(bundle, profile_url, version, request_type)
    end
  end

  validation_error_messages.each do |msg|
    messages << { type: 'error', message: msg }
  end
  msg = 'Bundle and/or entry resources are not conformant. Check messages for issues found.'
  assert validation_error_messages.blank?, msg
rescue Inferno::Exceptions::AssertionException => e
  msg = "#{message} #{e.message}".strip
  raise e.class, msg unless skips

  skip msg
end

#validate_resources_conformance_against_profile(bundle, profile_url, version, request_type) ⇒ Object

Profile conformance of Prior Authorization (PA) resources.

This method performs conformance validation on the PA bundle and bundle entries. The request/response bundle and includes resources are validated against their respective profile.

Parameters:

  • bundle (FHIR::Bundle)

    The FHIR bundle representing the PA request/response.

  • profile_url (String)

    The URL of the FHIR profile to validate against.

  • version (String)

    The version of the profile.

  • request_type (String)

    the request operation: submit or inquiry



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 255

def validate_resources_conformance_against_profile(bundle, profile_url, version, request_type)
  reset_bundle_profile_inference_state
  add_resource_target_profile_to_map('bundle', bundle, profile_url)

  bundle_entry = bundle.entry

  root_entry = bundle_entry.find do |entry|
    ['Claim', 'ClaimResponse'].include?(entry.resource.resourceType)
  end

  if root_entry.present?
    root_resource_profile_url = if %w[submit submit_request].include?(request_type) &&
                                   root_entry.resource.resourceType == 'Claim'
                                  determine_claim_submit_profile_url(version, root_entry.resource)
                                else
                                  find_profile_url(request_type)[root_entry.resource.resourceType]
                                end

    add_resource_target_profile_to_map(root_entry.fullUrl, root_entry.resource, root_resource_profile_url)
    extract_profiles_to_validate_each_entry(bundle_entry, root_entry, root_resource_profile_url, version)
  end

  add_us_core_profiles_to_unprofiled_entries(bundle_entry, version)
  validate_bundle_entries_against_profiles(version)
end

#validate_uniqueness_of_supporting_info_sequences(claim) ⇒ Object

Checks the following requirement: The Claim.supportingInfo.sequence for each entry SHALL be unique within the Claim.

Since the cardinality for Claim.supportingInfo is 0..*, we will check the uniqueness if Array not empty.

Parameters:

  • claim (FHIR::Claim)

    The FHIR Claim resource.



720
721
722
723
724
725
726
727
728
729
730
731
732
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 720

def validate_uniqueness_of_supporting_info_sequences(claim)
  return unless claim.is_a?(FHIR::Claim)

  supporting_info = claim.supportingInfo
  return unless supporting_info.present?

  sequences = supporting_info.map(&:sequence)
  is_unique = sequences.uniq.length == sequences.length
  return if is_unique

  validation_error_messages << "[Claim/#{claim.id}]: The sequence element for each supportingInfo entry SHALL be " \
                               'unique within the Claim.'
end

#validation_error_messagesObject



79
80
81
# File 'lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb', line 79

def validation_error_messages
  @validation_error_messages ||= []
end