Top Level Namespace

Defined Under Namespace

Modules: Formulary

Constant Summary collapse

FHIR_SERVER =

Read the const variables from options or set default value.

"https://davinci-drug-formulary-ri.logicahealth.org"
CONFORMANCE_DEFINITIONS_URL =
options.conformance_url || "https://build.fhir.org/ig/HL7/davinci-pdex-formulary/branches/master/definitions.json.zip"
FAILED_UPLOAD =
[]
FHIR_SERVER_BASE =
options.fhir_server || "https://davinci-drug-formulary-ri.logicahealth.org"
PAYERS =
[]
FORMULARIES =
[]
LOCATIONS =
[]
NDOUTS =
[]

Instance Method Summary collapse

Instance Method Details

#extract_location_id_from_reference(reference) ⇒ Object



61
62
63
64
# File 'scripts/plan_specific_ndjson.rb', line 61

def extract_location_id_from_reference(reference)
  reference = [] if reference.nil?
  reference.map { |area| area[:reference].split("/").last }
end

#generate_bulk_exportObject

Generates the grouped bulk data export



154
155
156
157
158
159
160
161
162
163
164
# File 'scripts/plan_specific_ndjson.rb', line 154

def generate_bulk_export
  # Delete the bulk_export directory if it exists.
  FileUtils.rm_rf("bulk_export")

  get_all_insurance_plans
  get_all_location_resources
  p "==============================================================="
  p "Creating the Bulk export folder output ..."
  generate_payer_bulk_data
  generate_formulary_bulk_data
end

#generate_export_json(output_directory, request, output) ⇒ Object

Constructs and write the export.json file to the specified destination



95
96
97
98
99
100
101
102
103
104
105
106
# File 'scripts/plan_specific_ndjson.rb', line 95

def generate_export_json(output_directory, request, output)
  export = {
    transactionTime: Time.now.strftime("%FT%T%:z"),
    request: request,
    requiresAccessToken: false,
    output: output,
  }
  file_path = File.join(output_directory, "export.json")
  File.open(file_path, "w") do |file|
    file.write(JSON.pretty_generate(export))
  end
end

#generate_formulary_bulk_dataObject

Group each formulary resource with its associated Location, Basic, and MedicationKnowledge resources.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'scripts/plan_specific_ndjson.rb', line 137

def generate_formulary_bulk_data
  p "There are no Formulary resources available to generate bulk data." if FORMULARIES.empty?
  FORMULARIES.each do |formulary|
    NDOUTS.clear
    request = "#{FHIR_SERVER}/fhir/InsurancePlan/#{formulary[:id]}/$export"
    output_directory = File.join("bulk_export", formulary[:id])
    FileUtils.mkdir_p(output_directory)
    generate_ndjson("InsurancePlan", [formulary], output_directory)
    get_related_basic_and_medicationknowledge(formulary[:id], output_directory)

    location_ids = extract_location_id_from_reference(formulary[:coverageArea])
    get_related_locations(location_ids, output_directory)
    generate_export_json(output_directory, request, NDOUTS)
  end
end

#generate_ndjson(resource_type, resource_list, output_directory) ⇒ Object

Writing ndjson files for a given resource type



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'scripts/plan_specific_ndjson.rb', line 73

def generate_ndjson(resource_type, resource_list, output_directory)
  p "==============================================================="
  p "Generating ndjson file of type #{resource_type}..."
  outfile = File.join(output_directory, "#{resource_type}.ndjson")
  ndout = {
    type: resource_type,
    url: "#{FHIR_SERVER}/resources/#{output_directory.split("/").last}/#{resource_type}.ndjson",
  }
  NDOUTS << ndout

  begin
    o = File.open(outfile, "w")
    p "Writing #{resource_list.size} #{resource_type} resource instances to #{outfile}..."
    resource_list.each { |instance| o.puts(JSON.generate(instance)) }
  rescue StandardError => e
    puts "An error occured when generating file #{outfile}: #{e.message}"
  ensure
    o.close
  end
end

#generate_payer_bulk_dataObject

Group payer insurance resources with their associated Formulary, Basic, MK, and Location resources



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'scripts/plan_specific_ndjson.rb', line 109

def generate_payer_bulk_data
  p "There are no payer resources available to generate bulk data." if PAYERS.empty?

  PAYERS.each do |payer|
    NDOUTS.clear
    request = "#{FHIR_SERVER}/fhir/InsurancePlan/#{payer[:id]}/$export"
    output_directory = File.join("bulk_export", payer[:id])
    FileUtils.mkdir_p(output_directory)
    related_formularies_id = []
    payer[:coverage].each do |coverage|
      formulary = coverage[:extension].find { |ext| ext[:url] == "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-FormularyReference-extension" }
      unless formulary.nil?
        related_formularies_id << formulary[:valueReference][:reference].split("/").last
      end
    end

    related_formularies = FORMULARIES.find_all { |formulary| related_formularies_id.include?(formulary[:id]) }
    related_formularies.prepend(payer)
    generate_ndjson("InsurancePlan", related_formularies, output_directory)

    related_formularies_id.each { |id| get_related_basic_and_medicationknowledge(id, output_directory) }
    location_ids = extract_location_id_from_reference(payer[:coverageArea])
    get_related_locations(location_ids, output_directory)
    generate_export_json(output_directory, request, NDOUTS)
  end
end

#get_all_insurance_plansObject



23
24
25
26
27
28
29
30
31
32
# File 'scripts/plan_specific_ndjson.rb', line 23

def get_all_insurance_plans
  p "==============================================================="
  p "Reading all InsurancePlan resources..."
  plan_files = get_resource_json_files("output/InsurancePlan")
  plan_files.each do |json_file|
    resource = JSON.parse(File.read(json_file), symbolize_names: true)
    json_file.include?("Payer") ? PAYERS << resource : FORMULARIES << resource
  end
  p "Found a total of #{PAYERS.size} PAYERS and #{FORMULARIES.size} Formulary plans."
end

#get_all_location_resourcesObject



34
35
36
37
38
39
# File 'scripts/plan_specific_ndjson.rb', line 34

def get_all_location_resources
  location_files = get_resource_json_files("location_resources")
  location_files.each do |json_file|
    LOCATIONS << JSON.parse(File.read(json_file), symbolize_names: true)
  end
end

Get all Basic and MK resources linked to the given InsurancePlan

Parameters:

  • formulary_id (String)

    , the plan id

  • output_directory (String)

    , the directory where the ndjson file should be written



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'scripts/plan_specific_ndjson.rb', line 44

def get_related_basic_and_medicationknowledge(formulary_id, output_directory)
  p "==============================================================="
  p "Getting all Basic and MedicationKnowledge related to Formulary with ID= #{formulary_id}..."
  id_digits = formulary_id.split("-").last
  basic_files = get_resource_json_files("output/Basic").select { |f_name| f_name.include?(id_digits) }
  basic_resources = basic_files.map { |json_f| JSON.parse(File.read(json_f), symbolize_names: true) }
  # Get the reference ids to retrieve related medication_knowledge_resources
  ref_ids = basic_resources.map { |resource| resource[:id].split("-").last }
  medication_knowledge_files = get_resource_json_files("output/MedicationKnowledge").select { |f_name| ref_ids.any? { |id| f_name.include?(id) } }
  medication_knowledge_resources = medication_knowledge_files.map { |json_f| JSON.parse(File.read(json_f), symbolize_names: true) }

  p "There are #{basic_resources.size} Basic resources and #{medication_knowledge_resources.size} MedicationKnowledge resources related to Formulary with ID = #{formulary_id}"

  generate_ndjson("Basic", basic_resources, output_directory)
  generate_ndjson("MedicationKnowledge", medication_knowledge_resources, output_directory)
end

Retrieve location resources with given ids



67
68
69
70
# File 'scripts/plan_specific_ndjson.rb', line 67

def get_related_locations(location_ids, output_directory)
  specific_locations = LOCATIONS.find_all { |loc| location_ids.include?(loc[:id]) }
  generate_ndjson("Location", specific_locations, output_directory)
end

#get_resource_json_files(resource_directory) ⇒ Array [String]

Returns a list of resource file's names from the resource directory.

Parameters:

  • resource_directory (String)

    , the directory name to search

Returns:

  • (Array [String])

    a list of resource file's names from the resource directory



18
19
20
21
# File 'scripts/plan_specific_ndjson.rb', line 18

def get_resource_json_files(resource_directory)
  file_path = File.join(resource_directory, "*.json")
  file_names = Dir.glob(file_path)
end

#retry_failed_uploadObject



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'scripts/upload.rb', line 87

def retry_failed_upload
  n = FAILED_UPLOAD.size
  while !FAILED_UPLOAD.empty? && n > 0
    puts "#{FAILED_UPLOAD.count} resource(s) failed to upload. Retrying..."
    failed_upload_copy = FAILED_UPLOAD.dup
    FAILED_UPLOAD.clear
    failed_upload_copy.each do |resource|
      response = upload_resource(resource)
      FAILED_UPLOAD << resource unless response&.success?
    end
    n -= 1
  end
  puts "Ending the program ..."
  if FAILED_UPLOAD.empty?
    puts "All resources were uploaded successfully."
  else
    puts "#{FAILED_UPLOAD.count} resource(s) failed to upload"
  end
end

#upload_all_resourcesObject

Runs all the upload methods



108
109
110
111
112
# File 'scripts/upload.rb', line 108

def upload_all_resources
  upload_conformance_resources
  upload_sample_resources
  retry_failed_upload
end

#upload_conformance_resourcesObject



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'scripts/upload.rb', line 21

def upload_conformance_resources
  definitions_file = Tempfile.new
  begin
    definitions_data = HTTParty.get(CONFORMANCE_DEFINITIONS_URL, verify: false)
    definitions_file.write(definitions_data)
  rescue => e
    puts "Unable to upload conformance definitions."
    puts "Error#upload_conformance_resources: #{e.message}"
    return
  ensure
    definitions_file.close
  end

  begin
    Zip::File.open(definitions_file.path) do |zip_file|
      conf_resources = zip_file.entries
        .select { |entry| entry.name.end_with? ".json" }
        .reject { |entry| entry.name.start_with? "ImplementationGuide" }
      puts "Uploading #{conf_resources.count} conformance resources..."
      conf_resources.each do |entry|
        resource = JSON.parse(entry.get_input_stream.read, symbolize_names: true)
        response = upload_resource(resource)
        FAILED_UPLOAD << resource unless response&.success?
      end
    end
  rescue => e
    puts "Error#upload_conformance_resources: #{e.message}"
    puts "Please provide a valid conformance URL"
  ensure
    definitions_file.unlink
  end
end

#upload_resource(resource) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'scripts/upload.rb', line 71

def upload_resource(resource)
  resource_type = resource[:resourceType]
  id = resource[:id]
  begin
    HTTParty.put(
      "#{FHIR_SERVER}/#{resource_type}/#{id}",
      body: resource.to_json,
      headers: { 'Content-Type': "application/json" },
    )
  rescue => e
    puts "An exception occured when trying to load the resource #{resource[:resource_type]}/#{resource[:id]}."
    puts "Error#upload_resource: #{e.message}"
    return
  end
end

#upload_sample_resourcesObject



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'scripts/upload.rb', line 54

def upload_sample_resources
  location_file_path = File.join("location_resources", "*.json")
  file_path = File.join("output", "**/*.json")
  filenames = Dir.glob([file_path, location_file_path])
  # .partition { |filename| filename.include? "InsurancePlan" }
  # .flatten
  puts "Uploading #{filenames.length} resources"
  filenames.each_with_index do |filename, index|
    resource = JSON.parse(File.read(filename), symbolize_names: true)
    response = upload_resource(resource)
    FAILED_UPLOAD << resource unless response&.success?
    if index % 100 == 0
      puts "#{FAILED_UPLOAD.count} out of #{index + 1} attempted resources failed to be uploaded successfully."
    end
  end
end