onc_certification_g10_test_kit 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/inferno/exceptions.rb +31 -0
  4. data/lib/inferno/ext/bloomer.rb +24 -0
  5. data/lib/inferno/repositiories/validators.rb +17 -0
  6. data/lib/inferno/repositiories/value_sets.rb +26 -0
  7. data/lib/inferno/terminology/bcp47.rb +95 -0
  8. data/lib/inferno/terminology/bcp_13.rb +26 -0
  9. data/lib/inferno/terminology/codesystem.rb +49 -0
  10. data/lib/inferno/terminology/expected_manifest.yml +1123 -0
  11. data/lib/inferno/terminology/fhir_package_manager.rb +69 -0
  12. data/lib/inferno/terminology/loader.rb +298 -0
  13. data/lib/inferno/terminology/tasks/check_built_terminology.rb +77 -0
  14. data/lib/inferno/terminology/tasks/cleanup.rb +13 -0
  15. data/lib/inferno/terminology/tasks/cleanup_precursors.rb +23 -0
  16. data/lib/inferno/terminology/tasks/count_codes_in_value_set.rb +20 -0
  17. data/lib/inferno/terminology/tasks/create_value_set_validators.rb +34 -0
  18. data/lib/inferno/terminology/tasks/download_fhir_terminology.rb +27 -0
  19. data/lib/inferno/terminology/tasks/download_umls.rb +109 -0
  20. data/lib/inferno/terminology/tasks/download_umls_notice.rb +20 -0
  21. data/lib/inferno/terminology/tasks/expand_value_set_to_file.rb +36 -0
  22. data/lib/inferno/terminology/tasks/process_umls.rb +91 -0
  23. data/lib/inferno/terminology/tasks/process_umls_translations.rb +85 -0
  24. data/lib/inferno/terminology/tasks/run_umls_jar.rb +75 -0
  25. data/lib/inferno/terminology/tasks/temp_dir.rb +27 -0
  26. data/lib/inferno/terminology/tasks/unzip_umls.rb +42 -0
  27. data/lib/inferno/terminology/tasks/validate_code.rb +36 -0
  28. data/lib/inferno/terminology/tasks.rb +11 -0
  29. data/lib/inferno/terminology/terminology_configuration.rb +52 -0
  30. data/lib/inferno/terminology/terminology_validation.rb +42 -0
  31. data/lib/inferno/terminology/validator.rb +64 -0
  32. data/lib/inferno/terminology/value_set.rb +462 -0
  33. data/lib/inferno/terminology.rb +16 -0
  34. data/lib/onc_certification_g10_test_kit/authorization_request_builder.rb +87 -0
  35. data/lib/onc_certification_g10_test_kit/base_token_refresh_group.rb +48 -0
  36. data/lib/onc_certification_g10_test_kit/bulk_data_authorization.rb +235 -0
  37. data/lib/onc_certification_g10_test_kit/bulk_data_group_export.rb +255 -0
  38. data/lib/onc_certification_g10_test_kit/bulk_data_group_export_validation.rb +474 -0
  39. data/lib/onc_certification_g10_test_kit/bulk_data_jwks.json +58 -0
  40. data/lib/onc_certification_g10_test_kit/bulk_export_validation_tester.rb +171 -0
  41. data/lib/onc_certification_g10_test_kit/configuration_checker.rb +104 -0
  42. data/lib/onc_certification_g10_test_kit/export_kick_off_performer.rb +12 -0
  43. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyheight.json +3772 -0
  44. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodytemp.json +3772 -0
  45. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyweight.json +3772 -0
  46. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bp.json +6034 -0
  47. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-heartrate.json +3756 -0
  48. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-resprate.json +3756 -0
  49. data/lib/onc_certification_g10_test_kit/limited_scope_grant_test.rb +66 -0
  50. data/lib/onc_certification_g10_test_kit/multi_patient_api.rb +43 -0
  51. data/lib/onc_certification_g10_test_kit/patient_context_test.rb +30 -0
  52. data/lib/onc_certification_g10_test_kit/profile_guesser.rb +69 -0
  53. data/lib/onc_certification_g10_test_kit/resource_access_test.rb +96 -0
  54. data/lib/onc_certification_g10_test_kit/restricted_access_test.rb +12 -0
  55. data/lib/onc_certification_g10_test_kit/restricted_resource_type_access_group.rb +303 -0
  56. data/lib/onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group.rb +136 -0
  57. data/lib/onc_certification_g10_test_kit/smart_ehr_practitioner_app_group.rb +209 -0
  58. data/lib/onc_certification_g10_test_kit/smart_invalid_token_group.rb +197 -0
  59. data/lib/onc_certification_g10_test_kit/smart_limited_app_group.rb +123 -0
  60. data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group.rb +113 -0
  61. data/lib/onc_certification_g10_test_kit/smart_scopes_test.rb +153 -0
  62. data/lib/onc_certification_g10_test_kit/smart_standalone_patient_app_group.rb +177 -0
  63. data/lib/onc_certification_g10_test_kit/terminology_binding_validator.rb +140 -0
  64. data/lib/onc_certification_g10_test_kit/token_revocation_group.rb +133 -0
  65. data/lib/onc_certification_g10_test_kit/unauthorized_access_test.rb +25 -0
  66. data/lib/onc_certification_g10_test_kit/unrestricted_resource_type_access_group.rb +375 -0
  67. data/lib/onc_certification_g10_test_kit/version.rb +3 -0
  68. data/lib/onc_certification_g10_test_kit/visual_inspection_and_attestations_group.rb +470 -0
  69. data/lib/onc_certification_g10_test_kit/well_known_capabilities_test.rb +37 -0
  70. data/lib/onc_certification_g10_test_kit.rb +223 -0
  71. metadata +310 -0
@@ -0,0 +1,20 @@
1
+ module Inferno
2
+ module Terminology
3
+ module Tasks
4
+ module DownloadUMLSNotice
5
+ def download_umls_notice
6
+ Inferno.logger.info <<~NOTICE
7
+ UMLS file not found. Download the US National Library of Medicine (NLM) Unified
8
+ Medical Language System (UMLS) Full Release files:
9
+ https://www.nlm.nih.gov/research/umls/licensedcontent/umlsknowledgesources.html
10
+
11
+ Install the metathesaurus with the following data sources:
12
+ CVX|CVX;ICD10CM|ICD10CM;ICD10PCS|ICD10PCS;ICD9CM|ICD9CM;LNC|LNC;MTHICD9|ICD9CM;RXNORM|RXNORM;SNOMEDCT_US|SNOMEDCT;CPT;HCPCS
13
+ After installation, copy `{install path}/META/MRCONSO.RRF` into your
14
+ `./tmp/terminology` folder, and rerun this task.
15
+ NOTICE
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ module Inferno
2
+ module Terminology
3
+ module Tasks
4
+ class ExpandValueSetToFile
5
+ attr_reader :filename, :type, :value_set_url
6
+
7
+ def initialize(vs:, filename:, type:) # rubocop:disable Naming/MethodParameterName
8
+ @value_set_url = vs
9
+ @filename = filename
10
+ @type = type
11
+ end
12
+
13
+ def run
14
+ # JSON is a special case, because we need to add codes from valuesets from several versions
15
+ # We accomplish this by collecting and merging codes from each version
16
+ # Before writing the JSON to a file at the end
17
+ end_vs = nil if type == 'json'
18
+
19
+ %w[2019 2020 2021].each do |version|
20
+ Loader.register_umls_db File.join(TEMP_DIR, version, 'umls.db')
21
+ Loader.load_value_sets_from_directory(PACKAGE_DIR, true)
22
+ vs = Repositories::ValueSets.new.find(value_set_url)
23
+ if type == 'json'
24
+ end_vs ||= vs
25
+ end_vs.value_set.merge vs.value_set
26
+ else
27
+ Loader.save_to_file(vs.valueset, filename, type.to_sym)
28
+ end
29
+ end
30
+
31
+ File.open("#{filename}.json", 'wb') { |f| f << end_vs.expansion_as_fhir_valueset.to_json } if type == 'json'
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,91 @@
1
+ require 'csv'
2
+ require 'find'
3
+ require_relative 'download_umls_notice'
4
+ require_relative 'temp_dir'
5
+
6
+ module Inferno
7
+ module Terminology
8
+ module Tasks
9
+ class ProcessUMLS
10
+ include TempDir
11
+ include DownloadUMLSNotice
12
+
13
+ attr_reader :version
14
+
15
+ def initialize(version:)
16
+ @version = version
17
+ end
18
+
19
+ def run # rubocop:disable Metrics/CyclomaticComplexity
20
+ Inferno.logger.info 'Looking for `./tmp/terminology/MRCONSO.RRF`...'
21
+ input_file = Find.find(versioned_temp_dir).find { |f| /MRCONSO.RRF$/ =~f }
22
+ if input_file
23
+ start = Time.now
24
+ output_filename = File.join(versioned_temp_dir, 'terminology_umls.txt')
25
+ output = File.open(output_filename, 'w:UTF-8')
26
+ line = 0
27
+ excluded = 0
28
+ excluded_systems = Hash.new(0)
29
+ begin
30
+ Inferno.logger.info "Writing to #{output_filename}..."
31
+ CSV.foreach(input_file, headers: false, col_sep: '|', quote_char: "\x00") do |row|
32
+ line += 1
33
+ include_code = false
34
+ code_system = row[11]
35
+ code = row[13]
36
+ description = row[14]
37
+ case code_system
38
+ when 'SNOMEDCT_US'
39
+ code_system = 'SNOMED'
40
+ include_code = (row[4] == 'PF' && ['FN', 'OAF'].include?(row[12]))
41
+ when 'LNC'
42
+ code_system = 'LOINC'
43
+ include_code = true
44
+ when 'ICD10CM', 'ICD10PCS'
45
+ code_system = 'ICD10'
46
+ include_code = (row[12] == 'PT')
47
+ when 'ICD9CM'
48
+ code_system = 'ICD9'
49
+ include_code = (row[12] == 'PT')
50
+ when 'CPT', 'HCPCS'
51
+ include_code = (row[12] == 'PT')
52
+ when 'MTHICD9'
53
+ code_system = 'ICD9'
54
+ include_code = true
55
+ when 'RXNORM'
56
+ include_code = true
57
+ when 'CVX'
58
+ include_code = ['PT', 'OP'].include?(row[12])
59
+ when 'SRC'
60
+ # 'SRC' rows define the data sources in the file
61
+ include_code = false
62
+ else
63
+ include_code = false
64
+ excluded_systems[code_system] += 1
65
+ end
66
+ if include_code
67
+ output.write("#{code_system}|#{code}|#{description}\n")
68
+ else
69
+ excluded += 1
70
+ end
71
+ end
72
+ rescue StandardError => e
73
+ Inferno.logger.info "Error at line #{line}"
74
+ Inferno.logger.info e.message
75
+ end
76
+ output.close
77
+ Inferno.logger.info "Processed #{line} lines, excluding #{excluded} redundant entries."
78
+ Inferno.logger.info "Excluded code systems: #{excluded_systems}" unless excluded_systems.empty?
79
+ finish = Time.now
80
+ minutes = ((finish - start) / 60)
81
+ seconds = (minutes - minutes.floor) * 60
82
+ Inferno.logger.info "Completed in #{minutes.floor} minute(s) #{seconds.floor} second(s)."
83
+ Inferno.logger.info 'Done.'
84
+ else
85
+ download_umls_notice
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,85 @@
1
+ require 'find'
2
+ require_relative 'download_umls_notice'
3
+ require_relative 'temp_dir'
4
+
5
+ module Inferno
6
+ module Terminology
7
+ module Tasks
8
+ class ProcessUMLSTranslations
9
+ include DownloadUMLSNotice
10
+
11
+ def run # rubocop:disable Metrics/CyclomaticComplexity
12
+ Inferno.logger.info 'Looking for `./tmp/terminology/MRCONSO.RRF`...'
13
+ input_file = Find.find(File.join(TEMP_DIR, 'terminology')).find { |f| /MRCONSO.RRF$/ =~f }
14
+ if input_file
15
+ start = Time.now
16
+ output_filename = File.join(TEMP_DIR, 'translations_umls.txt')
17
+ output = File.open(output_filename, 'w:UTF-8')
18
+ line = 0
19
+ excluded_systems = Hash.new(0)
20
+ begin
21
+ entire_file = File.read(input_file)
22
+ Inferno.logger.info "Writing to #{output_filename}..."
23
+ current_umls_concept = nil
24
+ translation = Array.new(10)
25
+ entire_file.split("\n").each do |l|
26
+ row = l.split('|')
27
+ line += 1
28
+ concept = row[0]
29
+ if concept != current_umls_concept && !current_umls_concept.nil?
30
+ output.write("#{translation.join('|')}\n") unless translation[1..-2].compact.length < 2
31
+ translation = Array.new(10)
32
+ current_umls_concept = concept
33
+ translation[0] = current_umls_concept
34
+ elsif current_umls_concept.nil?
35
+ current_umls_concept = concept
36
+ translation[0] = current_umls_concept
37
+ end
38
+ code_system = row[11]
39
+ code = row[13]
40
+ translation[9] = row[14]
41
+ case code_system
42
+ when 'SNOMEDCT_US'
43
+ translation[1] = code if row[4] == 'PF' && ['FN', 'OAF'].include?(row[12])
44
+ when 'LNC'
45
+ translation[2] = code
46
+ when 'ICD10CM', 'ICD10PCS'
47
+ translation[3] = code if row[12] == 'PT'
48
+ when 'ICD9CM'
49
+ translation[4] = code if row[12] == 'PT'
50
+ when 'MTHICD9'
51
+ translation[4] = code
52
+ when 'RXNORM'
53
+ translation[5] = code
54
+ when 'CVX'
55
+ translation[6] = code if ['PT', 'OP'].include?(row[12])
56
+ when 'CPT'
57
+ translation[7] = code if row[12] == 'PT'
58
+ when 'HCPCS'
59
+ translation[8] = code if row[12] == 'PT'
60
+ when 'SRC'
61
+ # 'SRC' rows define the data sources in the file
62
+ else
63
+ excluded_systems[code_system] += 1
64
+ end
65
+ end
66
+ rescue StandardError => e
67
+ Inferno.logger.info "Error at line #{line}"
68
+ Inferno.logger.info e.message
69
+ end
70
+ output.close
71
+ Inferno.logger.info "Processed #{line} lines."
72
+ Inferno.logger.info "Excluded code systems: #{excluded_systems}" unless excluded_systems.empty?
73
+ finish = Time.now
74
+ minutes = ((finish - start) / 60)
75
+ seconds = (minutes - minutes.floor) * 60
76
+ Inferno.logger.info "Completed in #{minutes.floor} minute(s) #{seconds.floor} second(s)."
77
+ Inferno.logger.info 'Done.'
78
+ else
79
+ download_umls_notice
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,75 @@
1
+ require_relative 'temp_dir'
2
+
3
+ module Inferno
4
+ module Terminology
5
+ module Tasks
6
+ # More information on batch running UMLS
7
+ # https://www.nlm.nih.gov/research/umls/implementation_resources/community/mmsys/BatchMetaMorphoSys.html
8
+ class RunUMLSJar
9
+ include TempDir
10
+
11
+ VERSIONED_PROPS = {
12
+ '2019' => 'inferno_2019.prop',
13
+ '2020' => 'inferno_2020.prop',
14
+ '2021' => 'inferno_2021.prop'
15
+ }.freeze
16
+
17
+ attr_reader :version
18
+
19
+ def initialize(version:)
20
+ @version = version
21
+ end
22
+
23
+ def run
24
+ Inferno.logger.info "#{platform} system detected"
25
+ config_file = File.join(Dir.pwd, 'resources', VERSIONED_PROPS[version])
26
+ output_dir = File.join(Dir.pwd, versioned_temp_dir, 'umls_subset')
27
+ FileUtils.mkdir(output_dir)
28
+
29
+ Inferno.logger.info "Using #{config_file}"
30
+ Dir.chdir(Dir[File.join(Dir.pwd, versioned_temp_dir, '/umls/20*')][0]) do
31
+ Inferno.logger.info Dir.pwd
32
+ Dir['lib/*.jar'].each do |jar|
33
+ File.chmod(0o555, jar)
34
+ end
35
+ Dir["jre/#{platform}/bin/*"].each do |file|
36
+ File.chmod(0o555, file)
37
+ end
38
+
39
+ Inferno.logger.info 'Running MetamorphoSys (this may take a while)...'
40
+ output = system("./jre/#{platform}/bin/java " \
41
+ '-Djava.awt.headless=true ' \
42
+ '-cp .:lib/jpf-boot.jar ' \
43
+ '-Djpf.boot.config=./etc/subset.boot.properties ' \
44
+ '-Dlog4j.configuration=./etc/log4j.properties ' \
45
+ '-Dinput.uri=. ' \
46
+ "-Doutput.uri=#{output_dir} " \
47
+ "-Dmmsys.config.uri=#{config_file} " \
48
+ '-Xms300M -Xmx8G ' \
49
+ 'org.java.plugin.boot.Boot')
50
+ unless output
51
+ Inferno.logger.info 'MetamorphoSys run failed'
52
+ # The cwd at this point is 2 directories above where umls_subset
53
+ # is, so we have to navigate up to it
54
+ umls_subset_directory = File.join(Dir.pwd, '..', '..', 'umls_subset')
55
+ FileUtils.remove_dir(umls_subset_directory) if File.directory?(umls_subset_directory)
56
+ exit 1
57
+ end
58
+ end
59
+
60
+ Inferno.logger.info 'done'
61
+ end
62
+
63
+ def platform
64
+ if !(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM).nil?
65
+ 'windows64'
66
+ elsif !(/darwin/ =~ RUBY_PLATFORM).nil?
67
+ 'macos'
68
+ else
69
+ 'linux'
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,27 @@
1
+ module Inferno
2
+ module Terminology
3
+ module Tasks
4
+ module TempDir
5
+ def versioned_temp_dir
6
+ File.join(TEMP_DIR, version)
7
+ end
8
+
9
+ def umls_zip_path
10
+ File.join(versioned_temp_dir, 'umls.zip')
11
+ end
12
+
13
+ def umls_dir
14
+ File.join(versioned_temp_dir, 'umls')
15
+ end
16
+
17
+ def umls_subset_dir
18
+ File.join(versioned_temp_dir, 'umls_subset')
19
+ end
20
+
21
+ def pipe_files
22
+ Dir.glob(File.join(versioned_temp_dir, '*.pipe'))
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ require 'zip'
2
+ require_relative 'temp_dir'
3
+
4
+ module Inferno
5
+ module Terminology
6
+ module Tasks
7
+ class UnzipUMLS
8
+ include TempDir
9
+
10
+ attr_reader :version
11
+
12
+ def initialize(version:)
13
+ @version = version
14
+ end
15
+
16
+ def run
17
+ # https://stackoverflow.com/questions/19754883/how-to-unzip-a-zip-file-containing-folders-and-files-in-rails-while-keeping-the
18
+ Zip::File.open(umls_zip_path) do |zip_file|
19
+ # Handle entries one by one
20
+ zip_file.each do |entry|
21
+ # Extract to file/directory/symlink
22
+ Inferno.logger.info "Extracting #{entry.name}"
23
+ f_path = File.join(umls_dir, entry.name)
24
+ FileUtils.mkdir_p(File.dirname(f_path))
25
+ zip_file.extract(entry, f_path) unless File.exist?(f_path)
26
+ end
27
+ end
28
+
29
+ wildcard_path = "#{umls_dir}/20*"
30
+ Zip::File.open(File.expand_path("#{Dir[wildcard_path][0]}/mmsys.zip")) do |zip_file|
31
+ zip_file.each do |entry|
32
+ Inferno.logger.info "Extracting #{entry.name}"
33
+ f_path = File.join((Dir[wildcard_path][0]).to_s, entry.name)
34
+ FileUtils.mkdir_p(File.dirname(f_path))
35
+ zip_file.extract(entry, f_path) unless File.exist?(f_path)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ require 'colorize'
2
+ require_relative '../terminology_validation'
3
+ require_relative '../loader'
4
+
5
+ module Inferno
6
+ module Terminology
7
+ module Tasks
8
+ class ValidateCode
9
+ include TerminologyValidation
10
+
11
+ attr_reader :system, :code, :value_set_url
12
+
13
+ def initialize(code:, system:, valueset:)
14
+ @code = code
15
+ @system = system
16
+ @value_set_url = valueset
17
+ end
18
+
19
+ def run
20
+ Inferno::Terminology::Loader.load_validators
21
+ code_display = self.system ? "#{self.system}|#{code}" : code.to_s
22
+ if validate_code(code: code, system: self.system, value_set_url: value_set_url)
23
+ in_system = 'is in'
24
+ symbol = "\u2713".encode('utf-8').to_s.green
25
+ else
26
+ in_system = 'is not in'
27
+ symbol = 'X'.red
28
+ end
29
+ system_checked = value_set_url || self.system
30
+
31
+ Inferno.logger.info "#{symbol} #{code_display} #{in_system} #{system_checked}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ Dir.glob(File.join(__dir__, 'tasks', '*.rb')).each do |path|
2
+ require_relative path.delete_prefix("#{__dir__}/")
3
+ end
4
+
5
+ module Inferno
6
+ module Terminology
7
+ module Tasks
8
+ TEMP_DIR = 'tmp/terminology'.freeze
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inferno
4
+ class TerminologyConfiguration
5
+ class << self
6
+ def config
7
+ @config =
8
+ if File.file? File.join('config', 'terminology_config.yml')
9
+ YAML.load_file(File.join('config', 'terminology_config.yml')).presence || {}
10
+ else
11
+ {}
12
+ end
13
+ end
14
+
15
+ def allowed_systems_metadata
16
+ @allowed_systems_metadata ||=
17
+ Terminology.code_system_metadata
18
+ .select { |url, _metadata| system_allowed?(url) }
19
+ end
20
+
21
+ def prohibited_systems
22
+ @prohibited_systems ||=
23
+ Terminology.code_system_metadata
24
+ .reject { |url, _metadata| system_allowed?(url) }
25
+ .keys
26
+ end
27
+
28
+ def prohibited_license_restriction_levels
29
+ config[:exclude_license_restriction_levels] || []
30
+ end
31
+
32
+ def explicitly_allowed_systems
33
+ config[:include] || []
34
+ end
35
+
36
+ def explicitly_prohibited_systems
37
+ config[:exclude] || []
38
+ end
39
+
40
+ def system_allowed?(url)
41
+ return true if explicitly_allowed_systems.include?(url)
42
+ return false if explicitly_prohibited_systems.include?(url)
43
+
44
+ !prohibited_license_restriction_levels.include?(Terminology.code_system_metadata.dig(url, :restriction_level))
45
+ end
46
+
47
+ def system_prohibited?(url)
48
+ !system_allowed?(url)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ require_relative '../repositiories/validators'
2
+ require_relative '../terminology/bcp_13'
3
+
4
+ module Inferno
5
+ module Terminology
6
+ module TerminologyValidation
7
+ # Code systems to "preprocess" prior to validation, and the function to use
8
+ PREPROCESS_FUNCS = {
9
+ 'urn:ietf:bcp:13' => BCP13.method(:preprocess_code)
10
+ }.freeze
11
+
12
+ def validators_repo
13
+ @validators_repo ||= Repositories::Validators.new
14
+ end
15
+
16
+ # This function accepts a valueset URL, code, and optional system, and returns true
17
+ # if the code or code/system combination is valid for the valueset
18
+ # represented by that URL
19
+ #
20
+ # @param String value_set_url the URL for the valueset to validate against
21
+ # @param String code the code to validate against the valueset
22
+ # @param String system an optional codesystem to validate against. Defaults to nil
23
+ # @return Boolean whether the code or code/system is in the valueset
24
+ def validate_code(code:, value_set_url: nil, system: nil)
25
+ # Before we validate the code, see if there's any preprocessing steps we have to do
26
+ # To get the code "ready" for validation
27
+ code = PREPROCESS_FUNCS[system].call(code) if PREPROCESS_FUNCS[system]
28
+
29
+ # Get the valueset from the url. Redundant if the 'system' is not nil,
30
+ # but allows us to throw a better error if the valueset isn't known by Inferno
31
+ validator =
32
+ if value_set_url
33
+ validators_repo.find(value_set_url) || raise(UnknownValueSetException, value_set_url)
34
+ else
35
+ validators_repo.find(system) || raise(UnknownCodeSystemException, system)
36
+ end
37
+
38
+ validator.validate(code: code, system: system)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,64 @@
1
+ require_relative 'terminology_configuration'
2
+
3
+ module Inferno
4
+ module Terminology
5
+ class Validator
6
+ attr_reader :url, :concept_count, :type, :code_systems, :file_name, :bloom_filter
7
+
8
+ def initialize(**params)
9
+ @url = params[:url]
10
+ @concept_count = params[:count]
11
+ @type = params[:type]
12
+ @code_systems = params[:code_systems]
13
+ @file_name = params[:file]
14
+ @bloom_filter = params[:bloom_filter]
15
+ end
16
+
17
+ def validate(code:, system: nil)
18
+ if system
19
+ raise ProhibitedSystemException, system if TerminologyConfiguration.system_prohibited?(system)
20
+
21
+ coding_in_filter?(code: code, system: system)
22
+ elsif contains_prohibited_systems?
23
+ raise ProhibitedSystemException, prohibited_systems.join(', ') unless code_in_allowed_system?(code)
24
+
25
+ true
26
+ else
27
+ code_in_any_system?(code)
28
+ end
29
+ end
30
+
31
+ def contains_prohibited_systems?
32
+ prohibited_systems.present?
33
+ end
34
+
35
+ def prohibited_systems
36
+ @prohibited_systems ||=
37
+ code_systems.select { |system| TerminologyConfiguration.system_prohibited?(system) }
38
+ end
39
+
40
+ def allowed_systems
41
+ @allowed_systems ||=
42
+ code_systems.select { |system| TerminologyConfiguration.system_allowed?(system) }
43
+ end
44
+
45
+ def code_in_allowed_system?(code)
46
+ code_in_systems?(code, allowed_systems)
47
+ end
48
+
49
+ def code_in_any_system?(code)
50
+ code_in_systems?(code, code_systems)
51
+ end
52
+
53
+ def code_in_systems?(code, possible_systems)
54
+ possible_systems.any? do |possible_system|
55
+ coding_in_filter?(code: code, system: possible_system)
56
+ end
57
+ end
58
+
59
+ def coding_in_filter?(code:, system:)
60
+ bloom_filter.include? "#{system}|#{code}"
61
+ end
62
+ end
63
+ end
64
+ end