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.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/lib/inferno/exceptions.rb +31 -0
- data/lib/inferno/ext/bloomer.rb +24 -0
- data/lib/inferno/repositiories/validators.rb +17 -0
- data/lib/inferno/repositiories/value_sets.rb +26 -0
- data/lib/inferno/terminology/bcp47.rb +95 -0
- data/lib/inferno/terminology/bcp_13.rb +26 -0
- data/lib/inferno/terminology/codesystem.rb +49 -0
- data/lib/inferno/terminology/expected_manifest.yml +1123 -0
- data/lib/inferno/terminology/fhir_package_manager.rb +69 -0
- data/lib/inferno/terminology/loader.rb +298 -0
- data/lib/inferno/terminology/tasks/check_built_terminology.rb +77 -0
- data/lib/inferno/terminology/tasks/cleanup.rb +13 -0
- data/lib/inferno/terminology/tasks/cleanup_precursors.rb +23 -0
- data/lib/inferno/terminology/tasks/count_codes_in_value_set.rb +20 -0
- data/lib/inferno/terminology/tasks/create_value_set_validators.rb +34 -0
- data/lib/inferno/terminology/tasks/download_fhir_terminology.rb +27 -0
- data/lib/inferno/terminology/tasks/download_umls.rb +109 -0
- data/lib/inferno/terminology/tasks/download_umls_notice.rb +20 -0
- data/lib/inferno/terminology/tasks/expand_value_set_to_file.rb +36 -0
- data/lib/inferno/terminology/tasks/process_umls.rb +91 -0
- data/lib/inferno/terminology/tasks/process_umls_translations.rb +85 -0
- data/lib/inferno/terminology/tasks/run_umls_jar.rb +75 -0
- data/lib/inferno/terminology/tasks/temp_dir.rb +27 -0
- data/lib/inferno/terminology/tasks/unzip_umls.rb +42 -0
- data/lib/inferno/terminology/tasks/validate_code.rb +36 -0
- data/lib/inferno/terminology/tasks.rb +11 -0
- data/lib/inferno/terminology/terminology_configuration.rb +52 -0
- data/lib/inferno/terminology/terminology_validation.rb +42 -0
- data/lib/inferno/terminology/validator.rb +64 -0
- data/lib/inferno/terminology/value_set.rb +462 -0
- data/lib/inferno/terminology.rb +16 -0
- data/lib/onc_certification_g10_test_kit/authorization_request_builder.rb +87 -0
- data/lib/onc_certification_g10_test_kit/base_token_refresh_group.rb +48 -0
- data/lib/onc_certification_g10_test_kit/bulk_data_authorization.rb +235 -0
- data/lib/onc_certification_g10_test_kit/bulk_data_group_export.rb +255 -0
- data/lib/onc_certification_g10_test_kit/bulk_data_group_export_validation.rb +474 -0
- data/lib/onc_certification_g10_test_kit/bulk_data_jwks.json +58 -0
- data/lib/onc_certification_g10_test_kit/bulk_export_validation_tester.rb +171 -0
- data/lib/onc_certification_g10_test_kit/configuration_checker.rb +104 -0
- data/lib/onc_certification_g10_test_kit/export_kick_off_performer.rb +12 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyheight.json +3772 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodytemp.json +3772 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyweight.json +3772 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bp.json +6034 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-heartrate.json +3756 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-resprate.json +3756 -0
- data/lib/onc_certification_g10_test_kit/limited_scope_grant_test.rb +66 -0
- data/lib/onc_certification_g10_test_kit/multi_patient_api.rb +43 -0
- data/lib/onc_certification_g10_test_kit/patient_context_test.rb +30 -0
- data/lib/onc_certification_g10_test_kit/profile_guesser.rb +69 -0
- data/lib/onc_certification_g10_test_kit/resource_access_test.rb +96 -0
- data/lib/onc_certification_g10_test_kit/restricted_access_test.rb +12 -0
- data/lib/onc_certification_g10_test_kit/restricted_resource_type_access_group.rb +303 -0
- data/lib/onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group.rb +136 -0
- data/lib/onc_certification_g10_test_kit/smart_ehr_practitioner_app_group.rb +209 -0
- data/lib/onc_certification_g10_test_kit/smart_invalid_token_group.rb +197 -0
- data/lib/onc_certification_g10_test_kit/smart_limited_app_group.rb +123 -0
- data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group.rb +113 -0
- data/lib/onc_certification_g10_test_kit/smart_scopes_test.rb +153 -0
- data/lib/onc_certification_g10_test_kit/smart_standalone_patient_app_group.rb +177 -0
- data/lib/onc_certification_g10_test_kit/terminology_binding_validator.rb +140 -0
- data/lib/onc_certification_g10_test_kit/token_revocation_group.rb +133 -0
- data/lib/onc_certification_g10_test_kit/unauthorized_access_test.rb +25 -0
- data/lib/onc_certification_g10_test_kit/unrestricted_resource_type_access_group.rb +375 -0
- data/lib/onc_certification_g10_test_kit/version.rb +3 -0
- data/lib/onc_certification_g10_test_kit/visual_inspection_and_attestations_group.rb +470 -0
- data/lib/onc_certification_g10_test_kit/well_known_capabilities_test.rb +37 -0
- data/lib/onc_certification_g10_test_kit.rb +223 -0
- 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,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
|