diff --git a/Gemfile b/Gemfile index b5aeef3..4a99c74 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ gem 'jbuilder', '~> 2.5' # Build JSON APIs with ease. Read more: https:// # gem 'redis', '~> 4.0' # Use Redis adapter to run Action Cable in production # gem 'bcrypt', '~> 3.1.7' # Use ActiveModel has_secure_password # gem 'mini_magick', '~> 4.8' # Use ActiveStorage variant - +gem 'dalli' # Memcache client gem 'zlib' , '>= 1.0.0' gem 'bootsnap', '>= 1.1.0', require: false # Reduces boot times through caching; required in config/boot.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a90813f..f977c60 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,11 +7,14 @@ ################################################################################ class ApplicationController < ActionController::Base + require 'dalli' + before_action :setup_dalli, :connect_to_server @@plansbyid = {} def self.plansbyid decompress_hash(session[:plansbyid]) + get_cache("plansbyid") end #----------------------------------------------------------------------------- @@ -20,13 +23,12 @@ def self.plansbyid def coverage_plans # Read all of the coverage plans from the server - cp_profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-CoveragePlan" - reply = @client.read(FHIR::List, nil, nil, cp_profile).resource + #cp_profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-CoveragePlan" + reply = @client.read(FHIR::List, nil, nil, nil).resource @plansbyid = build_coverage_plans(reply) - options = build_coverage_plan_options(reply) - session[:plansbyid] = compress_hash(@plansbyid.to_json) - session[:cp_options] = compress_hash(options) - options + @cp_options = build_coverage_plan_options(reply) + set_cache("plansbyid",@plansbyid) + set_cache("cp_options", @cp_options) rescue => exception puts "coverage_plans fails: not connected" options = [["N/A (Must connect first)", "-"]] @@ -35,11 +37,11 @@ def coverage_plans #----------------------------------------------------------------------------- def get_plansbyid - if session[:plansbyid] - @plansbyid = JSON.parse(decompress_hash(session[:plansbyid])).deep_symbolize_keys - @cp_options = decompress_hash(session[:cp_options]) + if get_cache("plansbyid") + @plansbyid = get_cache("plansbyid") + @cp_options = get_cache("cp_options") else - puts "get_plansbyid: session[:plansbyid] is nil, calling coverage_plans " + puts "get_plansbyid: get_cache(\"plansbyid\") is nil, calling coverage_plans " @plansbyid = nil @cp_options = [["N/A (Must connect first)", "-"]] coverage_plans @@ -48,18 +50,6 @@ def get_plansbyid #----------------------------------------------------------------------------- - def compress_hash(h) - zh = Base64.encode64(Zlib::Deflate.deflate(h.to_json)) - end - - #----------------------------------------------------------------------------- - - def decompress_hash(zh) - h = JSON.parse(Zlib::Inflate.inflate(Base64.decode64(zh))) - end - - #----------------------------------------------------------------------------- - def build_coverage_plan_options(fhir_list_reply) options = fhir_list_reply.entry.collect do |entry| [entry.resource.title, entry.resource.identifier.first.value] @@ -73,7 +63,80 @@ def build_coverage_plans (fhir_list_reply) coverageplans = fhir_list_reply.entry.each_with_object({}) do | entry, planhashbyid | planhashbyid[entry.resource.identifier.first.value] = CoveragePlan.new(entry.resource) end - coverageplans.deep_symbolize_keys + #binding.pry + #coverageplans.deep_symbolize_keys + end + + def setup_dalli + options = { :namespace => "formulary", :compress => true } + @dalli_client = Dalli::Client.new('localhost:11211', options) + end + + # Utility accessors that reference session data + + def set_cache(variable,value) + puts "dalli: set #{variable}-#{session.id.public_id}" + @dalli_client.set("#{variable}-#{session.id.public_id}",value) + end + + def get_cache(variable) + puts "dalli: get #{variable}-#{session.id.public_id}" + @dalli_client.get("#{variable}-#{session.id.public_id}") + end + + def get_iss_url + @dalli_client.get("iss_url-#{session.id.public_id}") + end + def set_iss_url(url) + @dalli_client.set("iss_url-#{session.id.public_id}",url) + end + + def get_client_id + @dalli_client.get("client_id-#{session.id.public_id}") + #session[:client_id] + end + def set_client_id(id) + @dalli_client.set("client_id-#{session.id.public_id}",id) + #session[:client_id] + end + + # Get the FHIR server url + def server_url + url = (params[:server_url] || session[:server_url]) + url = url.strip if url + end + + def connect_to_server + puts "==>connect_to_server" + # if client_id.length == 0 + # @client = FHIR::Client.new(iss_url) + # @client.use_r4 + # return # We do not have authentication + # end + if session.empty? + err = "Session Expired" + # binding.pry + redirect_to root_path, alert: err + end + if server_url.present? + @client = FHIR::Client.new(server_url) + @client.use_r4 + @client.use_r4 + @client.additional_headers = { 'Accept-Encoding' => 'identity' } # + @client.set_basic_auth("fhiruser","change-password") + # token_expires_in = token_expiration - Time.now.to_i + # if token_expires_in.to_i < 10 # if we are less than 10s from an expiration, refresh + # get_new_token + # end + # @client.set_bearer_token(access_token) + cookies[:server_url] = server_url + session[:server_url] = server_url + end + rescue StandardError => exception + reset_session + err = "Failed to connect: " + exception.message + redirect_to root_path, alert: err end + end \ No newline at end of file diff --git a/app/controllers/compare_controller.rb b/app/controllers/compare_controller.rb index aecd8aa..c95f1df 100644 --- a/app/controllers/compare_controller.rb +++ b/app/controllers/compare_controller.rb @@ -8,7 +8,7 @@ class CompareController < ApplicationController - before_action :check_server_connection, only: [ :index ] + before_action :connect_to_server, only: [ :index ] attr_accessor :drugname, :codes @@ -26,7 +26,7 @@ def index @codes = params[:code].strip.split(',').map(&:strip).join(',') set_cache set_table - @cache_nil = ClientConnections.cache_nil?(session.id.public_id) + #@cache_nil = ClientConnections.cache_nil?(session.id.public_id) else redirect_to root_path, flash: { error: "Please specify a (partial) drug name, or at least one rxnorm code" } end @@ -36,17 +36,6 @@ def index private #----------------------------------------------------------------------------- - # Check that this session has an established FHIR client connection. - # Specifically, sets @client and redirects home if nil. - - def check_server_connection - session[:foo] = "bar" unless session.id - raise "session.id is nil" unless session.id - - unless @client = ClientConnections.get(session.id.public_id) - redirect_to root_path, flash: { error: "Please connect to a formulary server" } - end - end #----------------------------------------------------------------------------- @@ -59,34 +48,38 @@ def set_cache searchParams = {:_count => 200} searchParams[:code] = @codes if @codes and @codes.length > 0 searchParams["DrugName:contains"] = @drugname if @drugname and @drugname.length>0 - profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-FormularyDrug" - searchParams[:_profile] = profile - + #profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-FormularyDrug" + #searchParams[:_profile] = profile @cache[:fds] = get_all(FHIR::MedicationKnowledge, searchParams) - ClientConnections.cache(session.id.public_id, @cache) unless params[:search].present? + #ClientConnections.cache(session.id.public_id, @cache) unless params[:search].present? # end end #----------------------------------------------------------------------------- # Gets all instances of klass from server + # first call get_all_bundles to get all pages of response + # Then iterate through the responses,and pull out the resources returned def get_all(klass = nil, search_params = {}) replies = get_all_bundles(klass, search_params) return nil unless replies resources = [] - replies.each do |reply| + # Create an array of resources from each bundle and insert it into resources + replies.each do |reply| resources.push(reply.entry.collect{ |singleEntry| singleEntry.resource }) end - - resources.compact! - resources.flatten(1) + # At this point resources is an array of arrays + resources.compact! + # Now resources is an array. + resources.flatten(1) end #----------------------------------------------------------------------------- # Gets all bundles from server when querying for klass + # replies is an array of all of the bundles def get_all_bundles(klass = nil, search_params = {}) return nil unless klass @@ -98,7 +91,7 @@ def get_all_bundles(klass = nil, search_params = {}) replies.push(replies.last.next_bundle) end - replies.compact! + replies.compact! # eliminates nulls replies.present? ? replies : nil end @@ -108,7 +101,8 @@ def get_all_bundles(klass = nil, search_params = {}) def set_table @table_header = @cache[:cps].collect{ |cp| CoveragePlanOld.new(cp.title , cp.identifier.first.value) } - chosen = sift_fds + #chosen = sift_fds # not convinced this is necessary + chosen = @cache[:fds].clone @table_rows = Hash.new chosen.collect!{ |fd| FormularyDrug.new(fd, @plansbyid) } @@ -124,7 +118,8 @@ def set_table # Sifts through formulary drugs based on search term, returns chosen fds def sift_fds - return @cache[:fds].clone if params[:search].blank? || ClientConnections.cache_nil?(session.id.public_id) + return @cache[:fds].clone if params[:search].blank? + binding.pry @cache[:fds].select{ |fd| fd.code.coding.first.display.upcase.include?(params[:search].upcase) } end diff --git a/app/controllers/coverageplans_controller.rb b/app/controllers/coverageplans_controller.rb index f8dd8c8..f74e0ba 100644 --- a/app/controllers/coverageplans_controller.rb +++ b/app/controllers/coverageplans_controller.rb @@ -10,7 +10,7 @@ class CoverageplansController < ApplicationController - before_action :check_server_connection, only: [ :index, :show ] + before_action :connect_to_server, only: [ :index, :show ] #----------------------------------------------------------------------------- @@ -19,6 +19,7 @@ class CoverageplansController < ApplicationController def index get_plansbyid @coverageplans = @plansbyid.values + end #----------------------------------------------------------------------------- @@ -27,7 +28,8 @@ def index def show get_plansbyid - @plandata = @plansbyid[params[:id].to_sym] + binding.pry + @plandata = @plansbyid[params[:id]] end #----------------------------------------------------------------------------- diff --git a/app/controllers/formularies_controller.rb b/app/controllers/formularies_controller.rb index 44e84dc..f12d044 100644 --- a/app/controllers/formularies_controller.rb +++ b/app/controllers/formularies_controller.rb @@ -10,7 +10,7 @@ class FormulariesController < ApplicationController - before_action :check_server_connection, only: [ :index, :show ] + before_action :connect_to_server #----------------------------------------------------------------------------- @@ -20,8 +20,8 @@ def index if params[:page].present? @@bundle = update_page(params[:page], @@bundle) else - profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-FormularyDrug" - search = { parameters: { _profile: profile } } + #profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-FormularyDrug" + search = { parameters: { } } search[:parameters][:DrugTier] = params[:drug_tier] if params[:drug_tier].present? search[:parameters][:DrugPlan] = params[:coverage] if params[:coverage].present? search[:parameters][:code] = params[:code] if params[:code].present? diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb index ac19463..9602604 100644 --- a/app/controllers/welcome_controller.rb +++ b/app/controllers/welcome_controller.rb @@ -13,12 +13,14 @@ class WelcomeController < ApplicationController def index # solution from https://stackoverflow.com/questions/30772737/rails-4-session-id-occasionally-nil session[:foo] = "bar" unless session.id + setup_dalli - @client = ClientConnections.get(session.id.public_id) + #@client = ClientConnections.get(session.id.public_id) + connect_to_server @count = formulary_count @cp_count = coverageplan_count @cp_options = coverage_plans - @cache_nil = ClientConnections.cache_nil?(session.id.public_id) + # @cache_nil = ClientConnections.cache_nil?(session.id.public_id) get_plansbyid end @@ -30,19 +32,27 @@ def index # Connect the FHIR client with the specified server and save the connection # for future requests. - def connect_to_server - session[:foo] = "bar" unless session.id - raise "session.id is nil" unless session.id - if params[:server_url].present? && !ClientConnections.set(session.id.public_id, params[:server_url]) - err = "Connection failed: Ensure provided url points to a valid FHIR server" - err += " that holds at least one Formulary" - redirect_to root_path, flash: { error: err } - session[:plansbyid] = nil - session[:cp_options] = [["N/A (Must connect first)", "-"]] - return nil - end - cookies[:server_url] = params[:server_url] if params[:server_url].present? - end + +# Connect the FHIR client with the specified server and save the connection + # for future requests. + # If token is expired or within 10s of expiration, refresh the token + + + + + # def connect_to_server + # session[:foo] = "bar" unless session.id + # raise "session.id is nil" unless session.id + # if params[:server_url].present? && !ClientConnections.set(session.id.public_id, params[:server_url]) + # err = "Connection failed: Ensure provided url points to a valid FHIR server" + # err += " that holds at least one Formulary" + # redirect_to root_path, flash: { error: err } + # session[:plansbyid] = nil + # session[:cp_options] = [["N/A (Must connect first)", "-"]] + # return nil + # end + # cookies[:server_url] = params[:server_url] if params[:server_url].present? + # end #----------------------------------------------------------------------------- @@ -51,8 +61,8 @@ def connect_to_server def formulary_count begin - profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-FormularyDrug" - search = { parameters: { _profile: profile, _summary: "count" } } + #profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-FormularyDrug" + search = { parameters: { _summary: "count" } } count = @client.search(FHIR::MedicationKnowledge, search: search ).resource.total rescue => exception count = 0 @@ -64,8 +74,8 @@ def formulary_count def coverageplan_count begin - profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-CoveragePlan" - search = { parameters: { _profile: profile, _summary: "count" } } + #profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-CoveragePlan" + search = { parameters: { _summary: "count" } } count = @client.search(FHIR::List, search: search ).resource.total rescue => exception count = 0 diff --git a/app/models/client_connections.rb b/app/models/client_connections.rb deleted file mode 100644 index ad8cb15..0000000 --- a/app/models/client_connections.rb +++ /dev/null @@ -1,55 +0,0 @@ -class ClientConnections - - @clients = Hash.new - - def self.set(id, url) - begin - puts "ClientConnections:set (#{id}, #{url})" - client = FHIR::Client.new(url) - client.use_r4 - client.additional_headers = { 'Accept-Encoding' => 'identity' } # - FHIR::Model.client = client - profile = "http://hl7.org/fhir/us/davinci-drug-formulary/StructureDefinition/usdf-FormularyDrug" - search = { parameters: { _profile: profile, _summary: "count" } } - count = client.search(FHIR::MedicationKnowledge, search: search ).resource.total - raise "No FormularyDrugs in server" unless count > 0 - rescue - puts "ClientConnections:set -- returning nil" - return nil - end - @clients[id] = Hash.new - prune(id) - @clients[id][:client] = client - end - - def self.get(id) - return nil unless @clients[id] - prune(id) - @clients[id][:client] - end - - # sets cache if input is provided, then returns current cache value - def self.cache(id, input = nil) - prune(id) - input ? @clients[id][:cache] = input : @clients[id][:cache] - end - - def self.cache_nil?(id) - prune(id) - @clients[id].nil? || @clients[id][:cache].nil? - end - - def self.prune(protectID = nil) - puts "ClientConnect:prune (protectID = #{protectID} clients = #{@clients.keys}" - @clients.each {|key, value| puts "key: ##{key} lastused: #{value[:lastUsed]}"} - @clients[protectID][:lastUsed] = Time.now if protectID && @clients[protectID] - safeHours = 5 - @clients.delete_if { |id, connection| (Time.now - connection[:lastUsed]) > (safeHours * 60 * 60) } - puts "After #{@clients.keys}" - @clients.each {|key, value| puts "key: ##{key} lastused: #{value[:lastUsed]}"} - rescue => exception - puts "failure in Client:Connection.prune" - - end - -end \ No newline at end of file diff --git a/app/models/formulary_drug.rb b/app/models/formulary_drug.rb index 11ee5e8..2c4d444 100644 --- a/app/models/formulary_drug.rb +++ b/app/models/formulary_drug.rb @@ -26,12 +26,12 @@ def initialize(fhir_formulary, plansbyid) @rxnorm_path = "https://mor.nlm.nih.gov/RxNav/search?searchBy=RXCUI&searchTerm=" + @rxnorm_code @formulary_id_path = "/formularies/#{@id}" parse_extensions(fhir_formulary) - @plan = plansbyid[@plan_id.to_sym] + @plan = plansbyid[@plan_id] # Test inclusion of drug tier info in formulary drug for display - @tier = @plan[:tiers][@drug_tier.to_sym] + @tier = @plan.tiers[@drug_tier] if @tier - @copay = @tier[:costshares]["1-month-in-retail".to_sym][:copay] - @coinsurancerate = @tier[:costshares]["1-month-in-retail".to_sym][:coinsurancerate] + @copay = @tier[:costshares]["1-month-in-retail"][:copay] + @coinsurancerate = @tier[:costshares]["1-month-in-retail"][:coinsurancerate] else @copay = "missing" @coinsurancerate = "missing" @@ -105,9 +105,9 @@ def parse_extensions(fhir_formulary) @quantity_limit = extension.valueBoolean elsif extension.url.include?("PlanID") @plan_id = extension.valueString - @plan = plansbyid[plan_id.to_sym] + @plan = plansbyid[plan_id] @plan_id_path = "/coverageplans/#{plan_id}" - @plan_id_name = plan[:name] + @plan_id_name = plan.name end end else diff --git a/app/views/coverageplans/index.html.erb b/app/views/coverageplans/index.html.erb index b5d272d..d55c500 100644 --- a/app/views/coverageplans/index.html.erb +++ b/app/views/coverageplans/index.html.erb @@ -19,24 +19,24 @@ <% @coverageplans.each do |coverageplan| %>