Class: Inferno::Terminology::ValueSet

Inherits:
Object
  • Object
show all
Defined in:
lib/inferno/terminology/value_set.rb

Constant Summary collapse

SAB =
{
  'http://unitsofmeasure.org' => {
    abbreviation: 'NCI_UCUM',
    name: 'Unified Code for Units of Measure (UCUM)'
  }.freeze,
  'http://loinc.org' => {
    abbreviation: 'LNC',
    name: 'Logical Observation Identifiers Names and Codes terminology (LOINC)'
  }.freeze,
  'http://snomed.info/sct' => {
    abbreviation: 'SNOMEDCT_US',
    name: 'Systematized Nomenclature of Medicine-Clinical Terms (SNOMED CT), US Edition'
  }.freeze
  # The systems below are needed if building all terminology rather than
  # only the required bindings.
  # 'http://www.nlm.nih.gov/research/umls/rxnorm' => {
  #   abbreviation: 'RXNORM',
  #   name: 'RxNorm Vocabulary'
  # }.freeze,
  # 'http://www.cms.gov/Medicare/Coding/ICD10' => {
  #   abbreviation: 'ICD10PCS',
  #   name: 'ICD-10 Procedure Coding System (ICD-10-PCS)'
  # }.freeze,
  # 'http://hl7.org/fhir/sid/cvx' => {
  #   abbreviation: 'CVX',
  #   name: 'Vaccines Administered (CVX)'
  # }.freeze,
  # 'http://hl7.org/fhir/sid/icd-10-cm' => {
  #   abbreviation: 'ICD10CM',
  #   name: 'International Classification of Diseases, Tenth Revision, Clinical Modification (ICD-10-CM)'
  # }.freeze,
  # 'http://hl7.org/fhir/sid/icd-9-cm' => {
  #   abbreviation: 'ICD9CM',
  #   name: 'International Classification of Diseases, Ninth Revision, Clinical Modification (ICD-9-CM)'
  # }.freeze,
  # 'urn:oid:2.16.840.1.113883.6.101' => {
  #   abbreviation: 'NUCCHCPT',
  #   name: 'National Uniform Claim Committee - Health Care Provider Taxonomy (NUCCHCPT)'
  # },
  # 'http://nucc.org/provider-taxonomy' => {
  #   abbreviation: 'NUCCHCPT',
  #   name: 'National Uniform Claim Committee - Health Care Provider Taxonomy (NUCCHCPT)'
  # }.freeze,
  # 'http://www.ama-assn.org/go/cpt' => {
  #   abbreviation: 'CPT',
  #   name: 'Current Procedural Terminology (CPT)'
  # }.freeze,
  # 'http://www.cms.gov/Medicare/Coding/HCPCSReleaseCodeSets' => {
  #   abbreviation: 'HCPCS',
  #   name: 'Healthcare Common Procedure Coding System (HCPCS)'
  # }.freeze,
  # 'urn:oid:2.16.840.1.113883.6.285' => {
  #   abbreviation: 'HCPCS',
  #   name: 'Healthcare Common Procedure Coding System (HCPCS)'
  # }.freeze,
  # 'urn:oid:2.16.840.1.113883.6.13' => {
  #   abbreviation: 'CDT',
  #   name: 'Code on Dental Procedures and Nomenclature (CDT)'
  # }.freeze,
  # 'http://ada.org/cdt' => {
  #   abbreviation: 'CDT',
  #   name: 'Code on Dental Procedures and Nomenclature (CDT)'
  # },
  # 'http://www.ada.org/cdt' => {
  #   abbreviation: 'CDT',
  #   name: 'Code on Dental Procedures and Nomenclature (CDT)'
  # }
}.freeze
CODE_SYS =
{
  'urn:ietf:bcp:13' => -> { BCP13.code_set },
  'urn:ietf:bcp:47' => ->(filter = nil) { BCP47.code_set(filter) },
  'http://ihe.net/fhir/ValueSet/IHE.FormatCode.codesystem' =>
    -> { value_sets_repo.find('http://hl7.org/fhir/ValueSet/formatcodes').value_set },
  'https://www.usps.com/' =>
    lambda do
      codes = [
        'AL', 'AK', 'AS', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FM',
        'FL', 'GA', 'GU', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA',
        'ME', 'MH', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV',
        'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'MP', 'OH', 'OK', 'OR', 'PW',
        'PA', 'PR', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VI', 'VA',
        'WA', 'WV', 'WI', 'WY', 'AE', 'AP', 'AA'
      ]
      codes.each_with_object(Set.new) do |code, set|
        set.add(system: 'https://www.usps.com/', code:)
      end
    end
}.freeze
FILTER_PROP =
{
  'CLASSTYPE' => 'LCN',
  'DOC' => 'Doc',
  'SCALE_TYP' => 'LOINC_SCALE_TYP'
}.freeze
TOO_COSTLY_URL =
'http://hl7.org/fhir/StructureDefinition/valueset-toocostly'.freeze
UNCLOSED_URL =
'http://hl7.org/fhir/StructureDefinition/valueset-unclosed'.freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(database, use_expansions = true) ⇒ ValueSet

rubocop:disable Style/OptionalBooleanParameter



131
132
133
134
# File 'lib/inferno/terminology/value_set.rb', line 131

def initialize(database, use_expansions = true) # rubocop:disable Style/OptionalBooleanParameter
  @db = database
  @use_expansions = use_expansions
end

Class Attribute Details

.value_sets_repoObject (readonly)

Returns the value of attribute value_sets_repo.



30
31
32
# File 'lib/inferno/terminology/value_set.rb', line 30

def value_sets_repo
  @value_sets_repo
end

Instance Attribute Details

#dbObject

The UMLS Database



20
21
22
# File 'lib/inferno/terminology/value_set.rb', line 20

def db
  @db
end

#use_expansionsObject

Flag to say “use the provided expansion” when processing the valueset



25
26
27
# File 'lib/inferno/terminology/value_set.rb', line 25

def use_expansions
  @use_expansions
end

#value_set_modelObject

The FHIR::Model Representation of the ValueSet



22
23
24
# File 'lib/inferno/terminology/value_set.rb', line 22

def value_set_model
  @value_set_model
end

Class Method Details

.load_system(filename) ⇒ Object

Load a code system from a file

Parameters:

  • filename (String)

    the file containing the code system JSON



323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/inferno/terminology/value_set.rb', line 323

def self.load_system(filename)
  # TODO: Generalize this
  cs = FHIR::Json.from_json(File.read(filename))
  cs_set = Set.new
  load_codes = lambda do |concept|
    concept.each do |concept_code|
      cs_set.add(system: cs.url, code: concept_code.code)
      load_codes.call(concept_code.concept) unless concept_code.concept.empty?
    end
  end
  load_codes.call(cs.concept)
  cs_set
end

Instance Method Details

#all_included_code_systemsObject



201
202
203
# File 'lib/inferno/terminology/value_set.rb', line 201

def all_included_code_systems
  (included_code_systems + code_systems_in_codings).uniq
end

#code_system_metadata(system) ⇒ Object



149
150
151
# File 'lib/inferno/terminology/value_set.rb', line 149

def (system)
  SAB[system]
end

#code_system_set(code_system) ⇒ Object



175
176
177
# File 'lib/inferno/terminology/value_set.rb', line 175

def code_system_set(code_system)
  filter_code_set(code_system)
end

#code_systems_in_codingsObject



209
210
211
212
213
214
215
216
# File 'lib/inferno/terminology/value_set.rb', line 209

def code_systems_in_codings
  return [] if value_set.blank?

  value_set
    .map { |coding| coding[:system] }
    .compact
    .uniq
end

#contains_code?(code) ⇒ Boolean

Checks if the provided code is in the valueset

Codes should be provided as a [Hash] type object

e.g. ‘loinc.org’, code: ‘1234’

Parameters:

  • code (Hash)

    the code to evaluate

Returns:

  • (Boolean)


283
284
285
# File 'lib/inferno/terminology/value_set.rb', line 283

def contains_code?(code)
  @value_set.include? code
end

#countObject

Return the number of codes in the valueset



197
198
199
# File 'lib/inferno/terminology/value_set.rb', line 197

def count
  @value_set.length
end

#expansion_as_fhir_value_setObject



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/inferno/terminology/value_set.rb', line 179

def expansion_as_fhir_value_set
  expansion_backbone = FHIR::ValueSet::Expansion.new
  expansion_backbone.timestamp = DateTime.now.strftime('%Y-%m-%dT%H:%M:%S%:z')
  expansion_backbone.contains = value_set.map do |code|
    FHIR::ValueSet::Expansion::Contains.new({ system: code[:system], code: code[:code] })
  end
  expansion_backbone.total = expansion_backbone.contains.length
  expansion_value_set = @value_set_model.deep_dup # Make a copy so that the original definition is left intact
  expansion_value_set.expansion = expansion_backbone
  expansion_value_set
end

#expansion_present?Boolean

Returns:

  • (Boolean)


228
229
230
# File 'lib/inferno/terminology/value_set.rb', line 228

def expansion_present?
  !!@value_set_model&.expansion&.contains
end

#generate_bloomObject



287
288
289
290
291
292
293
294
295
# File 'lib/inferno/terminology/value_set.rb', line 287

def generate_bloom
  require 'bloomer'

  @bf = Bloomer::Scalable.create_with_sufficient_size(value_set.length)
  value_set.each do |cc|
    @bf.add_without_duplication("#{cc[:system]}|#{cc[:code]}")
  end
  @bf
end

#included_code_systemsObject



205
206
207
# File 'lib/inferno/terminology/value_set.rb', line 205

def included_code_systems
  @value_set_model.compose.include.map(&:system).compact.uniq
end

#process_expanded_value_setObject



267
268
269
270
271
272
273
# File 'lib/inferno/terminology/value_set.rb', line 267

def process_expanded_value_set
  include_set = Set.new
  @value_set_model.expansion.contains.each do |contain|
    include_set.add(system: contain.system, code: contain.code)
  end
  @value_set = include_set
end

#process_value_setObject

Creates the whole valueset

Creates a [Set] representing the valueset



253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/inferno/terminology/value_set.rb', line 253

def process_value_set
  Inferno.logger.debug "Processing #{@value_set_model.url}"
  include_set = Set.new
  @value_set_model.compose.include.each do |include|
    # Cumulative of each include
    include_set.merge(get_code_sets(include))
  end
  @value_set_model.compose.exclude.each do |exclude|
    # Remove excluded codes
    include_set.subtract(get_code_sets(exclude))
  end
  @value_set = include_set
end

#process_with_expansionsObject

Delegates to process_expanded_valueset if there’s already an expansion Otherwise it delegates to process_valueset to do the expansion



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/inferno/terminology/value_set.rb', line 234

def process_with_expansions
  if expansion_present?
    # This is moved into a nested clause so we can tell in the debug statements which path we're taking
    if too_costly? || unclosed?
      Inferno.logger.debug("ValueSet too costly or unclosed: #{url}")
      process_value_set
    else
      Inferno.logger.debug("Processing expanded valueset: #{url}")
      process_expanded_value_set
    end
  else
    Inferno.logger.debug("Processing composed valueset: #{url}")
    process_value_set
  end
end

#read_value_set(filename) ⇒ Object

Read the desired valueset from a JSON file

Parameters:

  • filename (String)

    the name of the file



171
172
173
# File 'lib/inferno/terminology/value_set.rb', line 171

def read_value_set(filename)
  @value_set_model = FHIR::Json.from_json(File.read(filename))
end

#save_bloom_to_file(filename = "resources/validators/bloom/#{(URI(url).host + URI(url).path).gsub(%r{[./]}, '_')}.msgpack") ⇒ Object

Saves the valueset bloomfilter to a msgpack file

Parameters:

  • filename (String) (defaults to: "resources/validators/bloom/#{(URI(url).host + URI(url).path).gsub(%r{[./]}, '_')}.msgpack")

    the name of the file



300
301
302
303
304
305
306
307
# File 'lib/inferno/terminology/value_set.rb', line 300

def save_bloom_to_file(
  filename = "resources/validators/bloom/#{(URI(url).host + URI(url).path).gsub(%r{[./]}, '_')}.msgpack"
)
  generate_bloom unless @bf
  bloom_file = File.new(filename, 'wb')
  bloom_file.write(@bf.to_msgpack) unless @bf.nil?
  filename
end

#save_csv_to_file(filename = "resources/validators/csv/#{(URI(url).host + URI(url).path).gsub(%r{[./]}, '_')}.csv") ⇒ Object

Saves the valueset to a csv

Parameters:

  • filename (String) (defaults to: "resources/validators/csv/#{(URI(url).host + URI(url).path).gsub(%r{[./]}, '_')}.csv")

    the name of the file



311
312
313
314
315
316
317
318
# File 'lib/inferno/terminology/value_set.rb', line 311

def save_csv_to_file(filename = "resources/validators/csv/#{(URI(url).host + URI(url).path).gsub(%r{[./]},
                                                                                                 '_')}.csv")
  CSV.open(filename, 'wb') do |csv|
    value_set.each do |code|
      csv << [code[:system], code[:code]]
    end
  end
end

#too_costly?Boolean

Returns:

  • (Boolean)


219
220
221
# File 'lib/inferno/terminology/value_set.rb', line 219

def too_costly?
  @value_set_model&.expansion&.extension&.find { |vs| vs.url == TOO_COSTLY_URL }&.value
end

#umls_abbreviation(system) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/inferno/terminology/value_set.rb', line 136

def umls_abbreviation(system)
  if system != 'http://nucc.org/provider-taxonomy' && system != 'urn:oid:2.16.840.1.113883.6.101'
    return SAB.dig(system, :abbreviation)
  end

  @nucc_system ||= # rubocop:disable Naming/MemoizedInstanceVariableName
    if @db.execute("SELECT COUNT(*) FROM mrconso WHERE SAB = 'NUCCPT'").flatten.first.positive?
      'NUCCPT'
    else
      'NUCCHCPT'
    end
end

#unclosed?Boolean

Returns:

  • (Boolean)


224
225
226
# File 'lib/inferno/terminology/value_set.rb', line 224

def unclosed?
  @value_set_model&.expansion&.extension&.find { |vs| vs.url == UNCLOSED_URL }&.value
end

#urlObject

Return the url of the valueset



192
193
194
# File 'lib/inferno/terminology/value_set.rb', line 192

def url
  @value_set_model.url
end

#value_setObject

The ValueSet [Set]



158
159
160
161
162
163
164
165
166
# File 'lib/inferno/terminology/value_set.rb', line 158

def value_set
  return @value_set if @value_set

  if @use_expansions
    process_with_expansions
  else
    process_value_set
  end
end

#value_sets_repoObject



153
154
155
# File 'lib/inferno/terminology/value_set.rb', line 153

def value_sets_repo
  self.class.value_sets_repo
end