onc_certification_g10_test_kit 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/inferno/exceptions.rb +31 -0
  4. data/lib/inferno/ext/bloomer.rb +24 -0
  5. data/lib/inferno/repositiories/validators.rb +17 -0
  6. data/lib/inferno/repositiories/value_sets.rb +26 -0
  7. data/lib/inferno/terminology/bcp47.rb +95 -0
  8. data/lib/inferno/terminology/bcp_13.rb +26 -0
  9. data/lib/inferno/terminology/codesystem.rb +49 -0
  10. data/lib/inferno/terminology/expected_manifest.yml +1123 -0
  11. data/lib/inferno/terminology/fhir_package_manager.rb +69 -0
  12. data/lib/inferno/terminology/loader.rb +298 -0
  13. data/lib/inferno/terminology/tasks/check_built_terminology.rb +77 -0
  14. data/lib/inferno/terminology/tasks/cleanup.rb +13 -0
  15. data/lib/inferno/terminology/tasks/cleanup_precursors.rb +23 -0
  16. data/lib/inferno/terminology/tasks/count_codes_in_value_set.rb +20 -0
  17. data/lib/inferno/terminology/tasks/create_value_set_validators.rb +34 -0
  18. data/lib/inferno/terminology/tasks/download_fhir_terminology.rb +27 -0
  19. data/lib/inferno/terminology/tasks/download_umls.rb +109 -0
  20. data/lib/inferno/terminology/tasks/download_umls_notice.rb +20 -0
  21. data/lib/inferno/terminology/tasks/expand_value_set_to_file.rb +36 -0
  22. data/lib/inferno/terminology/tasks/process_umls.rb +91 -0
  23. data/lib/inferno/terminology/tasks/process_umls_translations.rb +85 -0
  24. data/lib/inferno/terminology/tasks/run_umls_jar.rb +75 -0
  25. data/lib/inferno/terminology/tasks/temp_dir.rb +27 -0
  26. data/lib/inferno/terminology/tasks/unzip_umls.rb +42 -0
  27. data/lib/inferno/terminology/tasks/validate_code.rb +36 -0
  28. data/lib/inferno/terminology/tasks.rb +11 -0
  29. data/lib/inferno/terminology/terminology_configuration.rb +52 -0
  30. data/lib/inferno/terminology/terminology_validation.rb +42 -0
  31. data/lib/inferno/terminology/validator.rb +64 -0
  32. data/lib/inferno/terminology/value_set.rb +462 -0
  33. data/lib/inferno/terminology.rb +16 -0
  34. data/lib/onc_certification_g10_test_kit/authorization_request_builder.rb +87 -0
  35. data/lib/onc_certification_g10_test_kit/base_token_refresh_group.rb +48 -0
  36. data/lib/onc_certification_g10_test_kit/bulk_data_authorization.rb +235 -0
  37. data/lib/onc_certification_g10_test_kit/bulk_data_group_export.rb +255 -0
  38. data/lib/onc_certification_g10_test_kit/bulk_data_group_export_validation.rb +474 -0
  39. data/lib/onc_certification_g10_test_kit/bulk_data_jwks.json +58 -0
  40. data/lib/onc_certification_g10_test_kit/bulk_export_validation_tester.rb +171 -0
  41. data/lib/onc_certification_g10_test_kit/configuration_checker.rb +104 -0
  42. data/lib/onc_certification_g10_test_kit/export_kick_off_performer.rb +12 -0
  43. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyheight.json +3772 -0
  44. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodytemp.json +3772 -0
  45. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyweight.json +3772 -0
  46. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bp.json +6034 -0
  47. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-heartrate.json +3756 -0
  48. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-resprate.json +3756 -0
  49. data/lib/onc_certification_g10_test_kit/limited_scope_grant_test.rb +66 -0
  50. data/lib/onc_certification_g10_test_kit/multi_patient_api.rb +43 -0
  51. data/lib/onc_certification_g10_test_kit/patient_context_test.rb +30 -0
  52. data/lib/onc_certification_g10_test_kit/profile_guesser.rb +69 -0
  53. data/lib/onc_certification_g10_test_kit/resource_access_test.rb +96 -0
  54. data/lib/onc_certification_g10_test_kit/restricted_access_test.rb +12 -0
  55. data/lib/onc_certification_g10_test_kit/restricted_resource_type_access_group.rb +303 -0
  56. data/lib/onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group.rb +136 -0
  57. data/lib/onc_certification_g10_test_kit/smart_ehr_practitioner_app_group.rb +209 -0
  58. data/lib/onc_certification_g10_test_kit/smart_invalid_token_group.rb +197 -0
  59. data/lib/onc_certification_g10_test_kit/smart_limited_app_group.rb +123 -0
  60. data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group.rb +113 -0
  61. data/lib/onc_certification_g10_test_kit/smart_scopes_test.rb +153 -0
  62. data/lib/onc_certification_g10_test_kit/smart_standalone_patient_app_group.rb +177 -0
  63. data/lib/onc_certification_g10_test_kit/terminology_binding_validator.rb +140 -0
  64. data/lib/onc_certification_g10_test_kit/token_revocation_group.rb +133 -0
  65. data/lib/onc_certification_g10_test_kit/unauthorized_access_test.rb +25 -0
  66. data/lib/onc_certification_g10_test_kit/unrestricted_resource_type_access_group.rb +375 -0
  67. data/lib/onc_certification_g10_test_kit/version.rb +3 -0
  68. data/lib/onc_certification_g10_test_kit/visual_inspection_and_attestations_group.rb +470 -0
  69. data/lib/onc_certification_g10_test_kit/well_known_capabilities_test.rb +37 -0
  70. data/lib/onc_certification_g10_test_kit.rb +223 -0
  71. metadata +310 -0
@@ -0,0 +1,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