his_emr_api_lab 1.1.9 → 1.1.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a13cacbcc7be1a44baac082c8ade406f20538dabe21c26b3b2ef249cd9ee22cd
4
- data.tar.gz: 9b0a9cfb4676982b299600078fa0421133af14a0b8d15dd49ac9ca1933fad7e0
3
+ metadata.gz: a0345aafa5a183730006a41e0451a129eea8b201622af42c9ae9b13fb23e410d
4
+ data.tar.gz: d2003b6e1872c4d3c16b1bc80edea36d4ac353d62b37aad71932119085c5aecb
5
5
  SHA512:
6
- metadata.gz: 93092265b1d1cde16121ff005c901387e357040e32b05b3dff508564cde3832f8451e6805c7ca47c772abf90a47757a6cfd9a95084962b148cbdf7819f3a3493
7
- data.tar.gz: d2d1dded33ca89b66ad8d99c477fa373f6cc8c33958cc17192fb1a7691abaf4802ebc800c63006ff46d64e527a630ce263c0edddc1db368d67b2848406fd997c
6
+ metadata.gz: afb1c33968cb197c30b9f702b04188e93c886982a2304dc21aefcfd46add7aeb62df2307d53c6f2794dba8895a049f131b82835046280de920996d70eb6f1b84
7
+ data.tar.gz: ad3514949b17d623983acd6c8f7c4af305da52e4eda81e2345d94d7390e2d35aa7796d91397d819f0f64efa73803a011b4e9bd46baa574b265b04346da30b1d2
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ ##
5
+ # Push an order to LIMS.
6
+ class PushOrderJob < ApplicationJob
7
+ def perform(order_id)
8
+ push_worker = Lab::Lims::PushWorker.new(Lab::Lims::ApiFactory.create_api)
9
+ push_worker.push_order_by_id(order_id)
10
+ end
11
+ end
12
+ end
@@ -20,8 +20,7 @@ module Lab
20
20
  break false
21
21
  end
22
22
 
23
- lims_api = Lab::Lims::Api::RestApi.new(Lab::Lims::Config.rest_api)
24
- worker = Lab::Lims::PullWorker.new(lims_api)
23
+ worker = Lab::Lims::PullWorker.new(Lab::Lims::ApiFactory.create_api)
25
24
  worker.pull_orders(patient_id: patient_id)
26
25
 
27
26
  true
@@ -10,8 +10,7 @@ module Lab
10
10
  User.current = Lab::Lims::Utils.lab_user
11
11
  Location.current = Location.find_by_name('ART clinic')
12
12
 
13
- lims_api = Lab::Lims::Api::RestApi.new
14
- worker = Lab::Lims::Worker.new(lims_api)
13
+ worker = Lab::Lims::PushWorker.new(Lab::Lims::ApiFactory.create_api)
15
14
  worker.push_order(Lab::LabOrder.unscoped.find(order_id))
16
15
  end
17
16
  end
@@ -42,14 +42,21 @@ module Lab
42
42
  end
43
43
 
44
44
  scope :drawn, -> { where.not(concept_id: ConceptName.where(name: 'Unknown').select(:concept_id)) }
45
-
46
45
  scope :not_drawn, -> { where(concept_id: ConceptName.where(name: 'Unknown').select(:concept_id)) }
47
46
 
47
+ after_commit :queue_lims_push
48
+
48
49
  def self.prefetch_relationships
49
50
  includes(:reason_for_test,
50
51
  :requesting_clinician,
51
52
  :target_lab,
52
53
  tests: [:result])
53
54
  end
55
+
56
+ private
57
+
58
+ def queue_lims_push
59
+ Lab::PushOrderJob.perform_later(order_id)
60
+ end
54
61
  end
55
62
  end
@@ -6,7 +6,7 @@ module Lab
6
6
  tests ||= order.voided == 1 ? voided_tests(order) : order.tests
7
7
  requesting_clinician ||= order.requesting_clinician
8
8
  reason_for_test ||= order.reason_for_test
9
- target_lab ||= order.target_lab
9
+ target_lab = target_lab&.value_text || order.target_lab&.value_text || Location.current_health_center&.name
10
10
 
11
11
  ActiveSupport::HashWithIndifferentAccess.new(
12
12
  {
@@ -21,7 +21,7 @@ module Lab
21
21
  name: concept_name(order.concept_id)
22
22
  },
23
23
  requesting_clinician: requesting_clinician&.value_text,
24
- target_lab: target_lab&.value_text,
24
+ target_lab: target_lab,
25
25
  reason_for_test: {
26
26
  concept_id: reason_for_test&.value_coded,
27
27
  name: concept_name(reason_for_test&.value_coded)
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ module Lims
5
+ module Api
6
+ ##
7
+ # A LIMS Api wrappper that does nothing really.
8
+ #
9
+ # Primarily meant as a dummy for testing environments.
10
+ class BlackholeApi
11
+ def create_order(order_dto); end
12
+
13
+ def update_order(order_dto); end
14
+
15
+ def void_order(order_dto); end
16
+
17
+ def consume_orders(&_block); end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -44,9 +44,6 @@ class Lab::Lims::Api::RestApi
44
44
 
45
45
  def consume_orders(*_args, patient_id: nil, **_kwargs)
46
46
  orders_pending_updates(patient_id).each do |order|
47
- mapping = Lab::LimsOrderMapping.find_by(order_id: order.order_id)
48
- next if mapping.nil? || check_and_fix_duplicate!(mapping, order)
49
-
50
47
  order_dto = Lab::Lims::OrderSerializer.serialize_order(order)
51
48
 
52
49
  if order_dto['priority'].nil? || order_dto['sample_type'].casecmp?('not_specified')
@@ -389,6 +386,7 @@ class Lab::Lims::Api::RestApi
389
386
  unknown_specimen = ConceptName.where(name: Lab::Metadata::UNKNOWN_SPECIMEN)
390
387
  .select(:concept_id)
391
388
  orders = Lab::LabOrder.where(concept_id: unknown_specimen)
389
+ .where.not(accession_number: Lab::LimsOrderMapping.select(:lims_id))
392
390
  orders = orders.where(patient_id: patient_id) if patient_id
393
391
 
394
392
  orders
@@ -397,6 +395,7 @@ class Lab::Lims::Api::RestApi
397
395
  def orders_without_results(patient_id = nil)
398
396
  Rails.logger.debug('Looking for orders without a result')
399
397
  Lab::OrdersSearchService.find_orders_without_results(patient_id: patient_id)
398
+ .where.not(accession_number: Lab::LimsOrderMapping.select(:lims_id))
400
399
  end
401
400
 
402
401
  def orders_without_reason(patient_id = nil)
@@ -404,28 +403,9 @@ class Lab::Lims::Api::RestApi
404
403
  orders = Lab::LabOrder.joins(:reason_for_test)
405
404
  .merge(Observation.where(value_coded: nil, value_text: nil))
406
405
  .limit(1000)
406
+ .where.not(accession_number: Lab::LimsOrderMapping.select(:lims_id))
407
407
  orders = orders.where(patient_id: patient_id) if patient_id
408
408
 
409
409
  orders
410
410
  end
411
-
412
- # Checks for duplicates previously created due to this proving orders that have
413
- # not been pushed to LIMS as orders awaiting updates.
414
- def check_and_fix_duplicate!(mapping, order)
415
- duplicate_orders = Lab::LabOrder.where(accession_number: mapping.lims_id)
416
- .where.not(order_id: mapping.order_id)
417
- return false if duplicate_orders.size.zero?
418
-
419
- unless order.discontinued
420
- order.void('Duplicate created due to bug in HIS-EMR-API-Lab v1.1.7')
421
- mapping.destroy
422
- return true
423
- end
424
-
425
- duplicate_orders.each do |duplicate_order|
426
- duplicate_order.void("Has duplicate that contains updates ##{order.order_id}: Duplicate was created by bug in HIS-EMR-API-Lab v1.1.7")
427
- end
428
-
429
- true
430
- end
431
411
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ module Lims
5
+ ##
6
+ # Creates LIMS Apis based on current configuration
7
+ module ApiFactory
8
+ def self.create_api
9
+ return Lab::Lims::Api::BlackholeApi.new if Rails.env.casecmp?('test')
10
+
11
+ case Lab::Lims::Config.preferred_api
12
+ when /rest/i then Lab::Lims::Api::RestApi.new(Lab::Lims::Config.rest_api)
13
+ when /couchdb/ then Lab::Lims::Api::CouchDbApi.new(config: Lab::Lims::Config.couchdb_api)
14
+ else raise "Invalid lims_api configuration: #{Lab::Lims::Config.preferred_api}"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -46,9 +46,20 @@ require_relative 'utils'
46
46
 
47
47
  module Lab
48
48
  module Lims
49
+ ##
50
+ # Tools for performing a bulk import of data from LIMS' databases to local OpenMRS database.
51
+ #
52
+ # Migration sources supported:
53
+ # - MySQL
54
+ # - CouchDB
55
+ #
56
+ # The sources above can be changed by setting the environment various MIGRATION_SOURCE to
57
+ # either mysql or couchdb.
49
58
  module Migrator
50
59
  MAX_THREADS = ENV.fetch('MIGRATION_WORKERS', 6).to_i
51
60
 
61
+ ##
62
+ # A Lab::Lims::Api object that supports crawling of a LIMS CouchDB instance.
52
63
  class CouchDbMigratorApi < Lab::Lims::Api::CouchDbApi
53
64
  def initialize(*args, processes: 1, on_merge_processes: nil, **kwargs)
54
65
  super(*args, **kwargs)
@@ -91,6 +102,12 @@ module Lab
91
102
  end
92
103
  end
93
104
 
105
+ ##
106
+ # Extends the PullWorker to provide pause/resume capabilities.
107
+ #
108
+ # Migrations can be take a long time to complete, in cases where something
109
+ # went wrong you wouldn't to start all over. This worker thus saves
110
+ # progress and allows for the process to continue from whether it stopped.
94
111
  class MigrationWorker < PullWorker
95
112
  LOG_FILE_PATH = Utils::LIMS_LOG_PATH.join('migration-last-id.dat')
96
113
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Lab
4
4
  module Lims
5
+ ##
6
+ # Pulls orders from a Lims API object and saves them to the local database.
5
7
  class PullWorker
6
8
  attr_reader :lims_api
7
9
 
@@ -100,9 +102,7 @@ module Lab
100
102
  .distinct(:patient_id)
101
103
  .all
102
104
 
103
- if patients.size > 1
104
- raise DuplicateNHID, "Duplicate National Health ID: #{nhid}"
105
- end
105
+ raise DuplicateNHID, "Duplicate National Health ID: #{nhid}" if patients.size > 1
106
106
 
107
107
  patients.first
108
108
  end
@@ -166,9 +166,7 @@ module Lab
166
166
  def create_order(patient, order_dto)
167
167
  logger.debug("Creating order ##{order_dto['_id']}")
168
168
  order = OrdersService.order_test(order_dto.to_order_service_params(patient_id: patient.patient_id))
169
- unless order_dto['test_results'].empty?
170
- update_results(order, order_dto['test_results'])
171
- end
169
+ update_results(order, order_dto['test_results']) unless order_dto['test_results'].empty?
172
170
 
173
171
  order
174
172
  end
@@ -177,9 +175,7 @@ module Lab
177
175
  logger.debug("Updating order ##{order_dto['_id']}")
178
176
  order = OrdersService.update_order(order_id, order_dto.to_order_service_params(patient_id: patient.patient_id)
179
177
  .merge(force_update: 'true'))
180
- unless order_dto['test_results'].empty?
181
- update_results(order, order_dto['test_results'])
182
- end
178
+ update_results(order, order_dto['test_results']) unless order_dto['test_results'].empty?
183
179
 
184
180
  order
185
181
  end
@@ -283,9 +279,7 @@ module Lab
283
279
  mapping = Lab::LimsOrderMapping.find_by(lims_id: lims_id)
284
280
  return nil unless mapping
285
281
 
286
- if Lab::LabOrder.where(order_id: mapping.order_id).exists?
287
- return mapping
288
- end
282
+ return mapping if Lab::LabOrder.where(order_id: mapping.order_id).exists?
289
283
 
290
284
  mapping.destroy
291
285
  nil
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Lab
4
4
  module Lims
5
+ ##
6
+ # Pushes all local orders to a LIMS Api object.
5
7
  class PushWorker
6
8
  attr_reader :lims_api
7
9
 
@@ -17,27 +19,25 @@ module Lab
17
19
  loop do
18
20
  logger.info('Looking for new orders to push to LIMS...')
19
21
  orders = orders_pending_sync(batch_size).all
22
+
23
+ logger.debug("Found #{orders.size} orders...")
20
24
  orders.each do |order|
21
25
  push_order(order)
22
26
  rescue GatewayError => e
23
27
  logger.error("Failed to push order ##{order.accession_number}: #{e.class} - #{e.message}")
24
- sleep(Lab::Lims::Config.updates_poll_frequency)
25
28
  end
26
29
 
27
- # Doing this after .each above to stop ActiveRecord from executing
28
- # an extra request to the database (ActiveRecord's lazy evaluation
29
- # sometimes leads to unnecessary database hits for checking counts).
30
- if orders.empty? && !wait
31
- logger.info('Finished processing orders; exiting...')
32
- break
33
- end
30
+ break unless wait
34
31
 
32
+ logger.info('Waiting for orders...')
35
33
  sleep(Lab::Lims::Config.updates_poll_frequency)
36
34
  end
37
35
  end
38
36
 
39
37
  def push_order_by_id(order_id)
40
- order = Lab::LabOrder.unscoped.find(order_id)
38
+ order = Lab::LabOrder.joins(order_type: { name: 'Lab' })
39
+ .unscoped
40
+ .find(order_id)
41
41
  push_order(order)
42
42
  end
43
43
 
@@ -51,18 +51,20 @@ module Lab
51
51
 
52
52
  ActiveRecord::Base.transaction do
53
53
  if mapping && !order.voided.zero?
54
- Rails.logger.info("Deleting order ##{order_dto['accession_number']} from LIMS")
54
+ Rails.logger.info("Deleting order ##{order_dto[:accession_number]} from LIMS")
55
55
  lims_api.delete_order(mapping.lims_id, order_dto)
56
56
  mapping.destroy
57
57
  elsif mapping
58
- Rails.logger.info("Updating order ##{order_dto['accession_number']} in LIMS")
58
+ Rails.logger.info("Updating order ##{order_dto[:accession_number]} in LIMS")
59
59
  lims_api.update_order(mapping.lims_id, order_dto)
60
60
  mapping.update(pushed_at: Time.now)
61
61
  elsif order_dto[:_id] && Lab::LimsOrderMapping.where(lims_id: order_dto[:_id]).exists?
62
+ # HACK: v1.1.7 had a bug where duplicates of recently created orders where being created by
63
+ # the pull worker. This here detects those duplicates and voids them.
62
64
  Rails.logger.warn("Duplicate accession number found: #{order_dto[:_id]}, skipping order...")
63
- nil
65
+ fix_duplicates!(order)
64
66
  else
65
- Rails.logger.info("Creating order ##{order_dto['accession_number']} in LIMS")
67
+ Rails.logger.info("Creating order ##{order_dto[:accession_number]} in LIMS")
66
68
  update = lims_api.create_order(order_dto)
67
69
  Lab::LimsOrderMapping.create!(order: order, lims_id: update['id'], revision: update['rev'],
68
70
  pushed_at: Time.now)
@@ -85,6 +87,7 @@ module Lab
85
87
  def new_orders
86
88
  Rails.logger.debug('Looking for new orders that need to be created in LIMS...')
87
89
  Lab::LabOrder.where.not(order_id: Lab::LimsOrderMapping.all.select(:order_id))
90
+ .order(date_created: :desc)
88
91
  end
89
92
 
90
93
  def updated_orders
@@ -98,6 +101,7 @@ module Lab
98
101
  OR obs.date_created > :last_updated',
99
102
  last_updated: last_updated)
100
103
  .group('orders.order_id')
104
+ .order(discontinued_date: :desc, date_created: :desc)
101
105
  end
102
106
 
103
107
  def voided_orders
@@ -106,6 +110,34 @@ module Lab
106
110
  .where(order_type: OrderType.where(name: Lab::Metadata::ORDER_TYPE_NAME),
107
111
  order_id: Lab::LimsOrderMapping.all.select(:order_id),
108
112
  voided: 1)
113
+ .order(date_voided: :desc)
114
+ end
115
+
116
+ ##
117
+ # HACK: Checks for duplicates previously created by version 1.1.7 pull worker bug due to this proving orders
118
+ # that have not been pushed to LIMS as orders awaiting updates.
119
+ def fix_duplicates!(order)
120
+ return order.void('Duplicate created by bug in HIS-EMR-API-Lab v1.1.7') unless order_has_specimen?(order)
121
+
122
+ duplicate_order = Lab::LabOrder.where(accession_number: order.accession_number)
123
+ .where.not(order_id: order.order_id)
124
+ .first
125
+ return unless duplicate_order
126
+
127
+ if !order_has_results?(order) && (order_has_results?(duplicate_order) || order_has_specimen?(duplicate_order))
128
+ order.void('DUplicate created by bug in HIS-EMR-API-Lab v1.1.7')
129
+ else
130
+ duplicate_order.void('Duplicate created by bug in HIS-EMR-API-Lab v1.1.7')
131
+ Lab::LimsOrderMapping.find_by_lims_id(order.accession_number)&.destroy
132
+ end
133
+ end
134
+
135
+ def order_has_results?(order)
136
+ order.results.exists?
137
+ end
138
+
139
+ def order_has_specimen?(order)
140
+ order.concept_id == ConceptName.find_by_name!('Unknown').concept_id
109
141
  end
110
142
  end
111
143
  end
@@ -43,9 +43,12 @@ module Lab
43
43
  end
44
44
  end
45
45
 
46
+ LOG_FILES_TO_KEEP = 5
47
+ LOG_FILE_SIZE = 500.megabytes
48
+
46
49
  def self.start_worker(worker_name)
47
- Rails.logger = LoggerMultiplexor.new(log_path("#{worker_name}.log"), $stdout)
48
- # ActiveRecord::Base.logger = Rails.logger
50
+ Rails.logger = LoggerMultiplexor.new(file_logger(worker_name), $stdout)
51
+ ActiveRecord::Base.logger = Rails.logger
49
52
  Rails.logger.level = :debug
50
53
 
51
54
  File.open(log_path("#{worker_name}.lock"), File::RDWR | File::CREAT, 0o644) do |fout|
@@ -60,6 +63,10 @@ module Lab
60
63
  end
61
64
  end
62
65
 
66
+ def self.file_logger(worker_name)
67
+ Logger.new(log_path("#{worker_name}.log"), LOG_FILES_TO_KEEP, LOG_FILE_SIZE)
68
+ end
69
+
63
70
  def self.log_path(filename)
64
71
  Lab::Lims::Utils::LIMS_LOG_PATH.join(filename)
65
72
  end
@@ -72,11 +79,7 @@ module Lab
72
79
  end
73
80
 
74
81
  def self.lims_api
75
- case Lims::Config.preferred_api
76
- when /couchdb/i then Api::CouchDbApi.new(config: Lab::Lims::Config.couchdb)
77
- when /rest/i then Api::RestApi.new(Lab::Lims::Config.rest_api)
78
- else raise "Invalid LIMS API in application.yml, expected 'rest' or 'couchdb'"
79
- end
82
+ Lab::Lims::ApiFactory.create_api
80
83
  end
81
84
  end
82
85
  end
@@ -169,6 +169,8 @@ module Lab
169
169
  ##
170
170
  # Attach the lab where the test is going to get carried out.
171
171
  def add_target_lab(order, params)
172
+ return nil unless params['target_lab']
173
+
172
174
  create_order_observation(
173
175
  order,
174
176
  Lab::Metadata::TARGET_LAB_CONCEPT_NAME,
data/lib/lab/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lab
4
- VERSION = '1.1.9'
4
+ VERSION = '1.1.15'
5
5
  end
@@ -68,6 +68,7 @@
68
68
  "Hepatitis B Test","Hepatitis B"
69
69
  "Hepatitis C Test","Hepatitis C"
70
70
  "Rheumatoid Factor Test","Rheumatoid Factor"
71
+ "CrAg","CrAg"
71
72
  "Cryptococcus Antigen Test","CrAg"
72
73
  "Anti Streptolysis O","ASO"
73
74
  "C-reactive protein","CRP"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: his_emr_api_lab
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.9
4
+ version: 1.1.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elizabeth Glaser Pediatric Foundation Malawi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-23 00:00:00.000000000 Z
11
+ date: 2021-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: couchrest
@@ -248,6 +248,7 @@ files:
248
248
  - app/controllers/lab/test_types_controller.rb
249
249
  - app/controllers/lab/tests_controller.rb
250
250
  - app/jobs/lab/application_job.rb
251
+ - app/jobs/lab/push_order_job.rb
251
252
  - app/jobs/lab/update_patient_orders_job.rb
252
253
  - app/jobs/lab/void_order_job.rb
253
254
  - app/mailers/lab/application_mailer.rb
@@ -265,10 +266,12 @@ files:
265
266
  - app/services/lab/accession_number_service.rb
266
267
  - app/services/lab/concepts_service.rb
267
268
  - app/services/lab/labelling_service/order_label.rb
269
+ - app/services/lab/lims/api/blackhole_api.rb
268
270
  - app/services/lab/lims/api/couchdb_api.rb
269
271
  - app/services/lab/lims/api/mysql_api.rb
270
272
  - app/services/lab/lims/api/rest_api.rb
271
273
  - app/services/lab/lims/api/ws_api.rb
274
+ - app/services/lab/lims/api_factory.rb
272
275
  - app/services/lab/lims/config.rb
273
276
  - app/services/lab/lims/exceptions.rb
274
277
  - app/services/lab/lims/migrator.rb
@@ -331,7 +334,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
331
334
  - !ruby/object:Gem::Version
332
335
  version: '0'
333
336
  requirements: []
334
- rubygems_version: 3.1.4
337
+ rubygems_version: 3.0.8
335
338
  signing_key:
336
339
  specification_version: 4
337
340
  summary: Lab extension for the HIS-EMR-API