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.
- 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
|