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,69 @@
1
+ require 'rubygems/package'
2
+ require 'tempfile'
3
+ require 'zlib'
4
+ require 'json'
5
+
6
+ require_relative '../exceptions'
7
+
8
+ module Inferno
9
+ module Terminology
10
+ module FHIRPackageManager
11
+ class << self
12
+ REGISTRY_SERVER_URL = 'https://packages.fhir.org'.freeze
13
+ # Get the FHIR Package from the registry.
14
+ #
15
+ # e.g. get_package('hl7.fhir.us.core#3.1.1')
16
+ #
17
+ # @param [String] package The FHIR Package
18
+ def get_package(package, destination, desired_types = [])
19
+ package_url = package
20
+ .split('#')
21
+ .prepend(REGISTRY_SERVER_URL)
22
+ .join('/')
23
+
24
+ tar_file_name = "tmp/#{package.split('#').join('-')}.tgz"
25
+
26
+ File.open(tar_file_name, 'w') do |output_file|
27
+ output_file.binmode
28
+ block = proc do |response|
29
+ response.read_body do |chunk|
30
+ output_file.write chunk
31
+ end
32
+ end
33
+ RestClient::Request.execute(method: :get, url: package_url, block_response: block)
34
+ end
35
+
36
+ tar = Gem::Package::TarReader.new(Zlib::GzipReader.open("tmp/#{package.split('#').join('-')}.tgz"))
37
+
38
+ path = File.join destination.split('/')
39
+ FileUtils.mkdir_p(path)
40
+
41
+ tar.each do |entry|
42
+ next if entry.directory?
43
+
44
+ next unless entry.full_name.start_with? 'package/'
45
+
46
+ file_name = entry.full_name.split('/').last
47
+ next if desired_types.present? && !file_name.start_with?(*desired_types)
48
+
49
+ resource = JSON.parse(entry.read) if file_name.end_with? '.json'
50
+ next unless resource&.[]('url')
51
+
52
+ encoded_name = "#{encode_name(resource['url'])}.json"
53
+ encoded_file_name = File.join(path, encoded_name)
54
+ if File.exist?(encoded_file_name) && !resource['url'] == JSON.parse(File.read(encoded_file_name))['url']
55
+ raise FileExistsException, "#{encoded_name} already exists for #{resource['url']}"
56
+ end
57
+
58
+ File.write(encoded_file_name, resource.to_json)
59
+ end
60
+ File.delete(tar_file_name)
61
+ end
62
+
63
+ def encode_name(name)
64
+ Zlib.crc32(name)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,298 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/all'
5
+ require 'bloomer'
6
+ require 'bloomer/msgpackable'
7
+ require 'fileutils'
8
+ require 'pry'
9
+
10
+ require_relative '../exceptions'
11
+ require_relative '../ext/bloomer'
12
+ require_relative '../repositiories/validators'
13
+ require_relative '../repositiories/value_sets'
14
+ require_relative 'fhir_package_manager'
15
+ require_relative 'value_set'
16
+ require_relative 'validator'
17
+
18
+ module Inferno
19
+ module Terminology
20
+ class Loader
21
+ SKIP_SYS = [
22
+ 'http://hl7.org/fhir/ValueSet/message-events', # has 0 codes
23
+ 'http://hl7.org/fhir/ValueSet/care-team-category', # has 0 codes
24
+ 'http://hl7.org/fhir/ValueSet/action-participant-role', # has 0 codes
25
+ 'http://hl7.org/fhir/ValueSet/example-filter', # has fake property acme-plasma
26
+ 'http://hl7.org/fhir/ValueSet/all-distance-units', # UCUM filter "canonical"
27
+ 'http://hl7.org/fhir/ValueSet/all-time-units', # UCUM filter "canonical"
28
+ 'http://hl7.org/fhir/ValueSet/example-intensional', # Unhandled filter parent =
29
+ 'http://hl7.org/fhir/ValueSet/use-context', # ValueSet contains an unknown ValueSet
30
+ 'http://hl7.org/fhir/ValueSet/media-modality', # ValueSet contains an unknown ValueSet
31
+ 'http://hl7.org/fhir/ValueSet/example-hierarchical' # Example valueset with fake codes
32
+ ].freeze
33
+
34
+ @value_sets_repo = Inferno::Repositories::ValueSets.new
35
+ @validators_repo = Inferno::Repositories::Validators.new
36
+
37
+ @missing_validators = nil
38
+
39
+ class << self
40
+ attr_reader :validators_repo, :value_sets_repo
41
+
42
+ def load_value_sets_from_directory(directory, include_subdirectories = false) # rubocop:disable Style/OptionalBooleanParameter
43
+ directory += '/**/' if include_subdirectories
44
+ value_set_files = Dir["#{directory}/*.json"]
45
+ value_set_files.each do |vs_file|
46
+ next unless JSON.parse(File.read(vs_file))['resourceType'] == 'ValueSet'
47
+
48
+ add_value_set_from_file(vs_file)
49
+ end
50
+ end
51
+
52
+ # Creates the valueset validators, based on the passed in parameters and
53
+ # the value_sets_repo
54
+ #
55
+ # @param type [Symbol] the type of validators to create, either :bloom or
56
+ # :csv
57
+ # @param [String] minimum_binding_strength the lowest binding strength for
58
+ # which we should build validators
59
+ # @param [Boolean] include_umls a flag to determine if we should build
60
+ # validators that require UMLS
61
+ # @param [Boolean] delete_existing a flag to determine whether any
62
+ # existing validators of `type` should be deleted before the creation
63
+ # tasks run. Default to `true`. If `false`, the existing validators will
64
+ # be read in and combined with the validators created in this step.
65
+ def create_validators(
66
+ type: :bloom,
67
+ minimum_binding_strength: 'example',
68
+ include_umls: true,
69
+ delete_existing: true
70
+ )
71
+ strengths = ['example', 'preferred', 'extensible', 'required'].drop_while do |s|
72
+ s != minimum_binding_strength
73
+ end
74
+ umls_code_systems = Set.new(ValueSet::SAB.keys)
75
+ root_dir = "resources/terminology/validators/#{type}"
76
+
77
+ FileUtils.rm_r(root_dir, force: true) if delete_existing
78
+ FileUtils.mkdir_p(root_dir)
79
+
80
+ vs_validators = get_value_sets(strengths).map do |vs_url, vs|
81
+ next if SKIP_SYS.include? vs_url
82
+ next if !include_umls && !umls_code_systems.disjoint?(Set.new(vs.included_code_systems))
83
+
84
+ Inferno.logger.debug "Processing #{vs_url}"
85
+ filename = "#{root_dir}/#{(URI(vs.url).host + URI(vs.url).path).gsub(%r{[./]}, '_')}"
86
+ begin
87
+ # Save the validator to file, and get the "new" count of number of codes
88
+ new_count = save_to_file(vs.value_set, filename, type)
89
+
90
+ {
91
+ url: vs_url,
92
+ file: name_by_type(File.basename(filename), type),
93
+ count: new_count,
94
+ type: type.to_s,
95
+ code_systems: vs.included_code_systems
96
+ }
97
+ rescue UnknownCodeSystemException,
98
+ FilterOperationException,
99
+ UnknownValueSetException,
100
+ URI::InvalidURIError => e
101
+ Inferno.logger.warn "#{e.message} for ValueSet: #{vs_url}"
102
+ next
103
+ end
104
+ end
105
+ vs_validators.compact!
106
+
107
+ code_systems = vs_validators.flat_map { |validator| validator[:code_systems] }.uniq
108
+ vs = ValueSet.new(@db)
109
+
110
+ cs_validators = code_systems.map do |cs_name|
111
+ next if SKIP_SYS.include? cs_name
112
+ next if !include_umls && umls_code_systems.include?(cs_name)
113
+
114
+ Inferno.logger.debug "Processing #{cs_name}"
115
+ begin
116
+ cs = vs.code_system_set(cs_name)
117
+ filename = "#{root_dir}/#{bloom_file_name(cs_name)}"
118
+ new_count = save_to_file(cs, filename, type)
119
+
120
+ {
121
+ url: cs_name,
122
+ file: name_by_type(File.basename(filename), type),
123
+ count: new_count,
124
+ type: type.to_s,
125
+ code_systems: cs_name
126
+ }
127
+ rescue UnknownCodeSystemException,
128
+ FilterOperationException,
129
+ UnknownValueSetException,
130
+ URI::InvalidURIError => e
131
+ Inferno.logger.warn "#{e.message} for CodeSystem #{cs_name}"
132
+ next
133
+ end
134
+ end
135
+ validators = (vs_validators + cs_validators).compact
136
+
137
+ # Write manifest for loading later
138
+ File.write("#{root_dir}/manifest.yml", validators.to_yaml)
139
+
140
+ create_code_system_metadata(cs_validators.map { |validator| validator[:url] }, root_dir)
141
+ end
142
+
143
+ def create_code_system_metadata(system_urls, root_dir)
144
+ vs = ValueSet.new(@db)
145
+ metadata_path = "#{root_dir}/metadata.yml"
146
+ metadata =
147
+ if File.file? metadata_path
148
+ YAML.load_file(metadata_path)
149
+ else
150
+ {}
151
+ end
152
+ system_urls.each do |url|
153
+ abbreviation = vs.umls_abbreviation(url)
154
+ next if abbreviation.nil?
155
+
156
+ versions = @db.execute("SELECT SVER FROM mrsab WHERE RSAB='#{abbreviation}' AND SABIN='Y'").flatten
157
+ restriction_level = @db.execute(
158
+ "SELECT SRL FROM mrsab WHERE RSAB='#{abbreviation}' AND SABIN='Y'"
159
+ ).flatten.first
160
+ system_metadata = metadata[url] || vs.code_system_metadata(url).dup || {}
161
+ system_metadata[:versions] ||= []
162
+ system_metadata[:versions].concat(versions).uniq!
163
+ system_metadata[:restriction_level] = restriction_level
164
+
165
+ metadata[url] = system_metadata
166
+ end
167
+
168
+ File.write(metadata_path, metadata.to_yaml)
169
+ end
170
+
171
+ # NOTE: resources/value_sets.yml controls which value sets get loaded.
172
+ # It is currently manually generated from the US Core metadata.
173
+ def get_value_sets(strengths)
174
+ expected_vs_urls =
175
+ YAML.load_file(File.join('resources', 'value_sets.yml'))
176
+ .select { |vs| strengths.include? vs[:strength] }
177
+ .map! { |vs| vs[:system] }
178
+ .compact
179
+ .uniq
180
+
181
+ available_value_sets = value_sets_repo.select_by_url(expected_vs_urls)
182
+
183
+ # Throw an error message for each missing valueset
184
+ # But don't halt the rake task
185
+ (expected_vs_urls - available_value_sets.keys).each do |missing_vs_url|
186
+ Inferno.logger.error "Inferno doesn't know about valueset #{missing_vs_url}"
187
+ end
188
+ available_value_sets
189
+ end
190
+
191
+ # Chooses which filetype to save the validator as, based on the type variable passed in
192
+ def save_to_file(codeset, filename, type)
193
+ case type
194
+ when :bloom
195
+ save_bloom_to_file(codeset, name_by_type(filename, type))
196
+ when :csv
197
+ save_csv_to_file(codeset, name_by_type(filename, type))
198
+ else
199
+ raise 'Unknown Validator Type!'
200
+ end
201
+ end
202
+
203
+ def name_by_type(filename, type)
204
+ case type
205
+ when :bloom
206
+ "#{filename}.msgpack"
207
+ when :csv
208
+ "#{filename}.csv"
209
+ else
210
+ raise 'Unknown Validator Type!'
211
+ end
212
+ end
213
+
214
+ # Saves the valueset bloomfilter to a msgpack file
215
+ #
216
+ # @param [String] filename the name of the file
217
+ def save_bloom_to_file(codings, filename)
218
+ # If the file already exists, load it in
219
+ bloom_filter =
220
+ if File.file? filename
221
+ Bloomer::Scalable.from_msgpack(File.read(filename))
222
+ else
223
+ Bloomer::Scalable.create_with_sufficient_size(codings.length)
224
+ end
225
+ codings.each do |coding|
226
+ bloom_filter.add_without_duplication("#{coding[:system]}|#{coding[:code]}")
227
+ end
228
+ bloom_file = File.new(filename, 'wb')
229
+ bloom_file.write(bloom_filter.to_msgpack) unless bloom_filter.nil?
230
+
231
+ bloom_filter.count
232
+ end
233
+
234
+ # Saves the valueset to a csv
235
+ # @param [String] filename the name of the file
236
+ def save_csv_to_file(codings, filename)
237
+ # If the file already exists, add it to the Set
238
+ csv_set = Set.new
239
+ if File.file? filename
240
+ CSV.read(filename).each do |code_array|
241
+ csv_set.add({ code: code_array[1], system: code_array[0] })
242
+ end
243
+ end
244
+ codings.merge csv_set
245
+
246
+ CSV.open(filename, 'wb') do |csv|
247
+ codings.each do |coding|
248
+ csv << [coding[:system], coding[:code]]
249
+ end
250
+ end
251
+ codings.length
252
+ end
253
+
254
+ def register_umls_db(database)
255
+ FileUtils.mkdir_p File.dirname(database)
256
+ @db = SQLite3::Database.new database
257
+ end
258
+
259
+ def add_value_set_from_file(vs_file)
260
+ vs = ValueSet.new(@db)
261
+ vs.read_value_set(vs_file)
262
+ value_sets_repo.insert(vs)
263
+ vs
264
+ end
265
+
266
+ def load_validators(directory = 'resources/terminology/validators/bloom')
267
+ manifest_file = "#{directory}/manifest.yml"
268
+ return unless File.file? manifest_file
269
+
270
+ validator_metadata = YAML.load_file(manifest_file)
271
+ validator_metadata.each do |metadata|
272
+ metadata[:bloom_filter] =
273
+ Bloomer::Scalable.from_msgpack(File.read("#{directory}/#{metadata[:file]}"))
274
+ validators_repo.insert(Validator.new(metadata))
275
+ end
276
+ end
277
+
278
+ def bloom_file_name(codesystem)
279
+ system = codesystem.tr('|', '_')
280
+ uri = URI(system)
281
+ return (uri.host + uri.path).gsub(%r{[./]}, '_') if uri.host && uri.port
282
+
283
+ system.gsub(/\W/, '_')
284
+ end
285
+
286
+ def missing_validators
287
+ return @missing_validators if @missing_validators
288
+
289
+ required_value_sets =
290
+ value_sets_repo
291
+ .select_by_binding_strength(['required', 'extensible', 'preferred'])
292
+ .map(&:value_set_url)
293
+ @missing_validators = required_value_sets.compact - validators_repo.all_urls
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,77 @@
1
+ require 'yaml'
2
+
3
+ module Inferno
4
+ module Terminology
5
+ module Tasks
6
+ class CheckBuiltTerminology
7
+ MIME_TYPE_SYSTEMS = [
8
+ 'http://hl7.org/fhir/ValueSet/mimetypes',
9
+ 'urn:ietf:bcp:13'
10
+ ].freeze
11
+
12
+ def run
13
+ if mismatched_value_sets.blank?
14
+ Inferno.logger.info 'Terminology built successfully.'
15
+ return
16
+ end
17
+
18
+ if only_mime_types_mismatch?
19
+ Inferno.logger.info <<~MIME
20
+ Terminology built successfully.
21
+
22
+ Mime-type based terminology did not match, but this can be a
23
+ result of using a newer version of the `mime-types-data` gem and
24
+ does not necessarily reflect a problem with the terminology build.
25
+ The expected mime-types codes were generated with version
26
+ `mime-types-data` version `3.2021.0901`.
27
+ MIME
28
+ else
29
+ Inferno.logger.info 'Terminology build results different than expected.'
30
+ end
31
+
32
+ mismatched_value_sets.each do |value_set|
33
+ Inferno.logger.info mismatched_value_set_message(value_set)
34
+ end
35
+ end
36
+
37
+ def expected_manifest
38
+ YAML.load_file(File.join(__dir__, '..', 'expected_manifest.yml'))
39
+ end
40
+
41
+ def new_manifest_path
42
+ @new_manifest_path ||=
43
+ File.join(Dir.pwd, 'resources', 'terminology', 'validators', 'bloom', 'manifest.yml')
44
+ end
45
+
46
+ def new_manifest
47
+ return [] unless File.exist? new_manifest_path
48
+
49
+ YAML.load_file(new_manifest_path)
50
+ end
51
+
52
+ def mismatched_value_sets
53
+ @mismatched_value_sets ||=
54
+ expected_manifest.reject do |expected_value_set|
55
+ url = expected_value_set[:url]
56
+ new_value_set(url) == expected_value_set
57
+ end
58
+ end
59
+
60
+ def new_value_set(url)
61
+ new_manifest.find { |value_set| value_set[:url] == url }
62
+ end
63
+
64
+ def only_mime_types_mismatch?
65
+ mismatched_value_sets.all? { |value_set| MIME_TYPE_SYSTEMS.include? value_set[:url] }
66
+ end
67
+
68
+ def mismatched_value_set_message(expected_value_set)
69
+ url = expected_value_set[:url]
70
+ actual_value_set = new_value_set(url)
71
+
72
+ "#{url}: Expected codes: #{expected_value_set[:count]} Actual codes: #{actual_value_set&.dig(:count) || 0}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,13 @@
1
+ module Inferno
2
+ module Terminology
3
+ module Tasks
4
+ class Cleanup
5
+ def run
6
+ Inferno.logger.info "removing all terminology build files in #{TEMP_DIR}"
7
+
8
+ FileUtils.remove_dir File.join(TEMP_DIR)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module Inferno
2
+ module Terminology
3
+ module Tasks
4
+ class CleanupPrecursors
5
+ include TempDir
6
+
7
+ attr_reader :version
8
+
9
+ def initialize(version:)
10
+ @version = version
11
+ end
12
+
13
+ def run
14
+ Inferno.logger.info "removing terminology precursor files in #{versioned_temp_dir}"
15
+ FileUtils.remove_dir(umls_dir, true)
16
+ FileUtils.remove_dir(umls_subset_dir, true)
17
+ FileUtils.rm(umls_zip_path, force: true)
18
+ FileUtils.rm(pipe_files, force: true)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module Inferno
2
+ module Terminology
3
+ module Tasks
4
+ class CountCodesInValueSet
5
+ attr_reader :value_set_url
6
+
7
+ def initialize(vs:) # rubocop:disable Naming/MethodParameterName
8
+ @value_set_url = vs
9
+ end
10
+
11
+ def run
12
+ Loader.register_umls_db File.join(TEMP_DIR, 'umls.db')
13
+ Loader.load_value_sets_from_directory(PACKAGE_DIR, true)
14
+ vs = Repositories::ValueSets.new.find(value_set_url)
15
+ Inferno.logger.info vs&.valueset&.count
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'temp_dir'
2
+
3
+ module Inferno
4
+ module Terminology
5
+ module Tasks
6
+ class CreateValueSetValidators
7
+ include TempDir
8
+
9
+ attr_reader :minimum_binding_strength, :version, :delete_existing, :type
10
+
11
+ def initialize(minimum_binding_strength:, version:, delete_existing:, type:)
12
+ @minimum_binding_strength = minimum_binding_strength
13
+ @version = version
14
+ @delete_existing = delete_existing != 'false'
15
+ @type = type.to_sym
16
+ end
17
+
18
+ def run
19
+ Loader.register_umls_db db_for_version
20
+ Loader.load_value_sets_from_directory(Inferno::Terminology::PACKAGE_DIR, true)
21
+ Loader.create_validators(
22
+ type: type,
23
+ minimum_binding_strength: minimum_binding_strength,
24
+ delete_existing: delete_existing
25
+ )
26
+ end
27
+
28
+ def db_for_version
29
+ File.join(versioned_temp_dir, 'umls.db')
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ module Inferno
2
+ module Terminology
3
+ module Tasks
4
+ class DownloadFHIRTerminology
5
+ def run
6
+ FileUtils.mkdir_p PACKAGE_DIR
7
+
8
+ download_fhir_r4
9
+ download_fhir_expansions
10
+ download_us_core
11
+ end
12
+
13
+ def download_fhir_r4
14
+ FHIRPackageManager.get_package('hl7.fhir.r4.core#4.0.1', PACKAGE_DIR, ['ValueSet', 'CodeSystem'])
15
+ end
16
+
17
+ def download_us_core
18
+ FHIRPackageManager.get_package('hl7.fhir.us.core#3.1.1', PACKAGE_DIR, ['ValueSet', 'CodeSystem'])
19
+ end
20
+
21
+ def download_fhir_expansions
22
+ FHIRPackageManager.get_package('hl7.fhir.r4.expansions#4.0.1', PACKAGE_DIR, ['ValueSet', 'CodeSystem'])
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,109 @@
1
+ require 'nokogiri'
2
+ require 'rest-client'
3
+ require_relative 'temp_dir'
4
+
5
+ module Inferno
6
+ module Terminology
7
+ module Tasks
8
+ class DownloadUMLS
9
+ include TempDir
10
+
11
+ UMLS_FILE_URLS = {
12
+ '2019' => 'https://download.nlm.nih.gov/umls/kss/2019AB/umls-2019AB-full.zip',
13
+ '2020' => 'https://download.nlm.nih.gov/umls/kss/2020AB/umls-2020AB-full.zip',
14
+ '2021' => 'https://download.nlm.nih.gov/umls/kss/2021AA/umls-2021AA-full.zip'
15
+ }.freeze
16
+ TICKET_GRANTING_TICKET_URL = 'https://utslogin.nlm.nih.gov/cas/v1/api-key'.freeze
17
+
18
+ attr_reader :version, :api_key
19
+
20
+ def initialize(version:, apikey:)
21
+ @version = version
22
+ @api_key = apikey
23
+ end
24
+
25
+ def run
26
+ # Adapted from https://documentation.uts.nlm.nih.gov/automating-downloads.html
27
+
28
+ FileUtils.mkdir_p(versioned_temp_dir)
29
+
30
+ target_file = UMLS_FILE_URLS[version]
31
+
32
+ Inferno.logger.info 'Getting Ticket Granting Ticket'
33
+ ticket_granting_ticket_html = RestClient::Request.execute(
34
+ method: :post,
35
+ url: TICKET_GRANTING_TICKET_URL,
36
+ payload: {
37
+ apikey: api_key
38
+ }
39
+ )
40
+ ticket_granting_ticket =
41
+ Nokogiri::HTML(ticket_granting_ticket_html.body).at_css('form').attributes['action'].value
42
+
43
+ Inferno.logger.info 'Getting ticket'
44
+ ticket = RestClient::Request.execute(
45
+ method: :post,
46
+ url: ticket_granting_ticket,
47
+ payload: {
48
+ service: target_file
49
+ }
50
+ ).body
51
+
52
+ begin
53
+ Inferno.logger.info 'Downloading'
54
+ RestClient::Request.execute(
55
+ method: :get,
56
+ url: "#{target_file}?ticket=#{ticket}",
57
+ max_redirects: 0
58
+ )
59
+ rescue RestClient::ExceptionWithResponse => e
60
+ ticket = RestClient::Request.execute(
61
+ method: :post,
62
+ url: ticket_granting_ticket,
63
+ payload: {
64
+ service: e.response.headers[:location]
65
+ }
66
+ ).body
67
+ target_location = umls_zip_path
68
+ follow_redirect(e.response.headers[:location], target_location, ticket, e.response.headers[:set_cookie])
69
+ end
70
+ Inferno.logger.info 'Finished Downloading!'
71
+ end
72
+
73
+ def follow_redirect(location, file_location, ticket, cookie = nil)
74
+ return unless location
75
+
76
+ size = 0
77
+ percent = 0
78
+ current_percent = 0
79
+ File.open(file_location, 'w') do |f|
80
+ f.binmode
81
+ block = proc do |response|
82
+ Inferno.logger.info response.header['content-type']
83
+ if response.header['content-type'] == 'application/zip'
84
+ total = response.header['content-length'].to_i
85
+ response.read_body do |chunk|
86
+ f.write chunk
87
+ size += chunk.size
88
+ percent = ((size * 100) / total).round unless total.zero?
89
+ if current_percent != percent
90
+ current_percent = percent
91
+ Inferno.logger.info "#{percent}% complete"
92
+ end
93
+ end
94
+ else
95
+ follow_redirect(response.header['location'], file_location, ticket, response.header['set-cookie'])
96
+ end
97
+ end
98
+ RestClient::Request.execute(
99
+ method: :get,
100
+ url: "#{location}?ticket=#{ticket}",
101
+ headers: { cookie: cookie },
102
+ block_response: block
103
+ )
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end