onc_certification_g10_test_kit 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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