his_emr_api_lab 1.1.1 → 1.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 360c214f3f26b5b18d01d1857d5ade3fa0dda1ddb54c846c44fcb354f6d47971
4
- data.tar.gz: 7b74dc551624446991404886012f19260411d90f1aa12e092fc89418e607e81f
3
+ metadata.gz: e2202cc31522b4ae92847b3102841cfcf0c65a9e389fde480e3e787f1ef49ec7
4
+ data.tar.gz: a82291c66cdd6fb651e0917dc68e5b28b6dcedeb124973ab57f9f60187b4c920
5
5
  SHA512:
6
- metadata.gz: 389813d90f57dca1542f698dcf6e10a396a8d5ad25f0aa29fc2f1cf668d5a2d38fb24f0810fc79ca88dab08b19ee98048ffa068f88745b40965f1e1e710a428f
7
- data.tar.gz: 743cb12464310c16dfde6e37ce931253479d7394961a48bf86a9350e3254bfcb2ab608f2f84076c6739936c6b31642453997e6cf3b101d1607da01f17456b89e
6
+ metadata.gz: 7da2c231dba9fa4b013890becd128b0a8694fe797b510e9baa37638d1437133cc11e27e2479938eda921ccfe6c23583544092cb477372b5db298922913035c72
7
+ data.tar.gz: cb8fbf9e9c71d187e0085b42f0fe5299976dc4309d672c815c231cdb096548d9b53b9b0f222f453b66a41a8d18b759f2d8d295c46c899206d7c7ffcee500bb6c
@@ -12,9 +12,22 @@ module Lab
12
12
  User.current = Lab::Lims::Utils.lab_user
13
13
  Location.current = Location.find_by_name('ART clinic')
14
14
 
15
- lims_api = Lab::Lims::Api::RestApi.new(Lab::Lims::Config.rest_api)
16
- worker = Lab::Lims::PullWorker.new(lims_api)
17
- worker.pull_orders(patient_id: patient_id)
15
+ lockfile = Rails.root.join('tmp', "update-patient-orders-#{patient_id}.lock")
16
+
17
+ done = File.open(lockfile, File::RDWR | File::CREAT) do |lock|
18
+ unless lock.flock(File::LOCK_NB | File::LOCK_EX)
19
+ Rails.logger.info('Another update patient job is already running...')
20
+ break false
21
+ end
22
+
23
+ lims_api = Lab::Lims::Api::RestApi.new(Lab::Lims::Config.rest_api)
24
+ worker = Lab::Lims::PullWorker.new(lims_api)
25
+ worker.pull_orders(patient_id: patient_id)
26
+
27
+ true
28
+ end
29
+
30
+ File.unlink(lockfile) if done
18
31
  end
19
32
  end
20
33
  end
@@ -22,14 +22,13 @@ class Lab::Lims::Api::RestApi
22
22
  end
23
23
  end
24
24
 
25
- update_order_results(order_dto)
26
-
27
- data = JSON.parse(response.body)['data']
25
+ data = JSON.parse(response.body)
26
+ update_order_results(order_dto) unless data['message'].casecmp?('Order already available')
28
27
 
29
28
  ActiveSupport::HashWithIndifferentAccess.new(
30
- id: data['tracking_number'],
29
+ id: order_dto.fetch(:_id, order_dto[:tracking_number]),
31
30
  rev: 0,
32
- tracking_number: data['tracking_number']
31
+ tracking_number: order_dto[:tracking_number]
33
32
  )
34
33
  end
35
34
 
@@ -44,21 +43,22 @@ class Lab::Lims::Api::RestApi
44
43
  end
45
44
 
46
45
  def consume_orders(*_args, patient_id: nil, **_kwargs)
47
- Rails.logger.info('Looking for orders without results...')
48
- orders = Lab::OrdersSearchService.find_orders_without_results(patient_id: patient_id)
46
+ orders_pending_updates(patient_id).each do |order|
47
+ order_dto = Lab::Lims::OrderSerializer.serialize_order(order)
49
48
 
50
- orders.each do |order|
51
- response = in_authenticated_session do |headers|
52
- Rails.logger.info("Fetching results for order ##{order.accession_number}")
53
- RestClient.get(expand_uri("query_results_by_tracking_number/#{order.accession_number}"), headers)
49
+ if order_dto['priority'].nil? || order_dto['sample_type'].casecmp?('not_specified')
50
+ patch_order_dto_with_lims_order!(order_dto, find_lims_order(order.accession_number))
54
51
  end
55
52
 
56
- Rails.logger.info("Result for order ##{order.accession_number} found... Parsing...")
57
- results = JSON.parse(response).fetch('data').fetch('results')
58
- order_dto = Lab::Lims::OrderSerializer.serialize_order(order)
59
- yield lims_results_to_order_dto(results, order_dto), OpenStruct.new(last_seq: 0)
60
- rescue InvalidParameters => e # LIMS responds with a 401 when a result is not found :(
61
- Rails.logger.error("Failed to fetch results for ##{order.accession_number}: #{e.message}")
53
+ if order_dto['test_results'].empty?
54
+ begin
55
+ patch_order_dto_with_lims_results!(order_dto, find_lims_results(order.accession_number))
56
+ rescue InvalidParameters => e # LIMS responds with a 401 when a result is not found :(
57
+ Rails.logger.error("Failed to fetch results for ##{order.accession_number}: #{e.message}")
58
+ end
59
+ end
60
+
61
+ yield order_dto, OpenStruct.new(last_seq: 0)
62
62
  end
63
63
  end
64
64
 
@@ -105,8 +105,8 @@ class Lab::Lims::Api::RestApi
105
105
  self.authentication_token = nil
106
106
  retry if (retries -= 1).positive?
107
107
  rescue RestClient::ExceptionWithResponse => e
108
- Rails.logger.error("LIMS Error: #{e.response.code} - #{e.response.body}")
109
- raise e unless e.response.code == 401
108
+ Rails.logger.error("LIMS Error: #{e.response&.code} - #{e.response&.body}")
109
+ raise e unless e.response&.code == 401
110
110
 
111
111
  self.authentication_token = nil
112
112
  retry if (retries -= 1).positive?
@@ -117,7 +117,8 @@ class Lab::Lims::Api::RestApi
117
117
  password = config.fetch(:password)
118
118
 
119
119
  Rails.logger.debug("Authenticating with LIMS as: #{username}")
120
- response = RestClient.get(expand_uri("re_authenticate/#{username}/#{password}"), headers: { 'Content-type' => 'application/json' })
120
+ response = RestClient.get(expand_uri("re_authenticate/#{username}/#{password}"),
121
+ headers: { 'Content-type' => 'application/json' })
121
122
  response_body = JSON.parse(response.body)
122
123
 
123
124
  if response_body['status'] == 401
@@ -153,9 +154,7 @@ class Lab::Lims::Api::RestApi
153
154
 
154
155
  Rails.logger.error("Lims Api Error: #{response.body}")
155
156
 
156
- if body['status'] != 401
157
- raise LimsApiError, "#{body['status']} - #{body['message']}"
158
- end
157
+ raise LimsApiError, "#{body['status']} - #{body['message']}" if body['status'] != 401
159
158
 
160
159
  if body['message'].match?(/token expired/i)
161
160
  raise AuthenticationTokenExpired, "Authentication token expired: #{body['message']}"
@@ -255,11 +254,31 @@ class Lab::Lims::Api::RestApi
255
254
  "#{orderer[:first_name]} #{orderer[:last_name]}"
256
255
  end
257
256
 
257
+ def find_lims_order(tracking_number)
258
+ response = in_authenticated_session do |headers|
259
+ Rails.logger.info("Fetching order ##{tracking_number}")
260
+ RestClient.get(expand_uri("query_order_by_tracking_number/#{tracking_number}"), headers)
261
+ end
262
+
263
+ Rails.logger.info("Order ##{tracking_number} found... Parsing...")
264
+ JSON.parse(response).fetch('data')
265
+ end
266
+
267
+ def find_lims_results(tracking_number)
268
+ response = in_authenticated_session do |headers|
269
+ Rails.logger.info("Fetching results for order ##{tracking_number}")
270
+ RestClient.get(expand_uri("query_results_by_tracking_number/#{tracking_number}"), headers)
271
+ end
272
+
273
+ Rails.logger.info("Result for order ##{tracking_number} found... Parsing...")
274
+ JSON.parse(response).fetch('data').fetch('results')
275
+ end
276
+
258
277
  ##
259
278
  # Make a copy of the order_dto with the results from LIMS parsed
260
279
  # and appended to it.
261
- def lims_results_to_order_dto(results, order_dto)
262
- order_dto.merge(
280
+ def patch_order_dto_with_lims_results!(order_dto, results)
281
+ order_dto.merge!(
263
282
  '_id' => order_dto[:tracking_number],
264
283
  '_rev' => 0,
265
284
  'test_results' => results.each_with_object({}) do |result, formatted_results|
@@ -277,10 +296,16 @@ class Lab::Lims::Api::RestApi
277
296
  )
278
297
  end
279
298
 
299
+ def patch_order_dto_with_lims_order!(order_dto, lims_order)
300
+ order_dto.merge!(
301
+ 'sample_type' => lims_order['other']['sample_type'],
302
+ 'sample_status' => lims_order['other']['specimen_status'],
303
+ 'priority' => lims_order['other']['priority']
304
+ )
305
+ end
306
+
280
307
  def update_order_results(order_dto)
281
- if order_dto['test_results'].nil? || order_dto['test_results'].empty?
282
- return nil
283
- end
308
+ return nil if order_dto['test_results'].nil? || order_dto['test_results'].empty?
284
309
 
285
310
  order_dto['test_results'].each do |test_name, results|
286
311
  Rails.logger.info("Pushing result for order ##{order_dto['tracking_number']}")
@@ -341,4 +366,40 @@ class Lab::Lims::Api::RestApi
341
366
  test_status: 'voided'
342
367
  }
343
368
  end
369
+
370
+ def orders_pending_updates(patient_id = nil)
371
+ Rails.logger.info('Looking for orders that need to be updated...')
372
+ orders = {}
373
+
374
+ orders_without_specimen(patient_id).each { |order| orders[order.order_id] = order }
375
+ orders_without_results(patient_id).each { |order| orders[order.order_id] = order }
376
+ orders_without_reason(patient_id).each { |order| orders[order.order_id] = order }
377
+
378
+ orders.values
379
+ end
380
+
381
+ def orders_without_specimen(patient_id = nil)
382
+ Rails.logger.debug('Looking for orders without a specimen')
383
+ unknown_specimen = ConceptName.where(name: Lab::Metadata::UNKNOWN_SPECIMEN)
384
+ .select(:concept_id)
385
+ orders = Lab::LabOrder.where(concept_id: unknown_specimen)
386
+ orders = orders.where(patient_id: patient_id) if patient_id
387
+
388
+ orders
389
+ end
390
+
391
+ def orders_without_results(patient_id = nil)
392
+ Rails.logger.debug('Looking for orders without a result')
393
+ Lab::OrdersSearchService.find_orders_without_results(patient_id: patient_id)
394
+ end
395
+
396
+ def orders_without_reason(patient_id = nil)
397
+ Rails.logger.debug('Looking for orders without a reason for test')
398
+ orders = Lab::LabOrder.joins(:reason_for_test)
399
+ .merge(Observation.where(value_coded: nil, value_text: nil))
400
+ .limit(1000)
401
+ orders = orders.where(patient_id: patient_id) if patient_id
402
+
403
+ orders
404
+ end
344
405
  end
@@ -22,7 +22,7 @@ module Lab
22
22
  date: start_date,
23
23
  target_lab: facility_name(self['receiving_facility']),
24
24
  order_location: facility_name(self['sending_facility']),
25
- reason_for_test: reason_for_test
25
+ reason_for_test_id: reason_for_test
26
26
  )
27
27
  end
28
28
 
@@ -69,7 +69,9 @@ module Lab
69
69
  end
70
70
 
71
71
  def start_date
72
- raise LimsException, 'Order missing created date' if self['date_created'].blank?
72
+ if self['date_created'].blank?
73
+ raise LimsException, 'Order missing created date'
74
+ end
73
75
 
74
76
  Utils.parse_date(self['date_created'])
75
77
  end
@@ -85,7 +87,12 @@ module Lab
85
87
  def reason_for_test
86
88
  return unknown_concept.concept_id unless self['priority']
87
89
 
88
- ConceptName.find_by_name!(self['priority']).concept_id
90
+ name = case self['priority']
91
+ when %r{Reapet / Missing}i then 'Repeat / Missing'
92
+ else self['priority']
93
+ end
94
+
95
+ ConceptName.find_by_name!(name).concept_id
89
96
  end
90
97
 
91
98
  def lab_program
@@ -16,6 +16,7 @@ module Lab
16
16
  serialized_order = Lims::Utils.structify(Lab::LabOrderSerializer.serialize_order(order))
17
17
 
18
18
  Lims::OrderDTO.new(
19
+ _id: Lab::LimsOrderMapping.find_by(order: order)&.lims_id || serialized_order.accession_number,
19
20
  tracking_number: serialized_order.accession_number,
20
21
  sending_facility: current_facility_name,
21
22
  receiving_facility: serialized_order.target_lab,
@@ -176,7 +176,7 @@ module Lab
176
176
  def update_order(patient, order_id, order_dto)
177
177
  logger.debug("Updating order ##{order_dto['_id']}")
178
178
  order = OrdersService.update_order(order_id, order_dto.to_order_service_params(patient_id: patient.patient_id)
179
- .merge(force_update: true))
179
+ .merge(force_update: 'true'))
180
180
  unless order_dto['test_results'].empty?
181
181
  update_results(order, order_dto['test_results'])
182
182
  end
@@ -194,7 +194,7 @@ module Lab
194
194
  next
195
195
  end
196
196
 
197
- next unless test_results['results']
197
+ next if test.result || test_results['results'].blank?
198
198
 
199
199
  measures = test_results['results'].map do |indicator, value|
200
200
  measure = find_measure(order, indicator, value)
@@ -56,7 +56,8 @@ module Lab
56
56
  else
57
57
  Rails.logger.info("Creating order ##{order_dto['accession_number']} in LIMS")
58
58
  update = lims_api.create_order(order_dto)
59
- Lab::LimsOrderMapping.create!(order: order, lims_id: update['id'], revision: update['rev'], pushed_at: Time.now)
59
+ Lab::LimsOrderMapping.create!(order: order, lims_id: update['id'], revision: update['rev'],
60
+ pushed_at: Time.now)
60
61
  end
61
62
  end
62
63
 
@@ -10,6 +10,7 @@ module Lab
10
10
  TEST_RESULT_CONCEPT_NAME = 'Lab test result'
11
11
  TEST_RESULT_INDICATOR_CONCEPT_NAME = 'Lab test result indicator'
12
12
  TEST_TYPE_CONCEPT_NAME = 'Test type'
13
+ UNKNOWN_SPECIMEN = 'Unknown'
13
14
 
14
15
  # Encounter
15
16
  ENCOUNTER_TYPE_NAME = 'Lab'
@@ -26,8 +26,10 @@ module Lab
26
26
  end
27
27
 
28
28
  def find_orders_without_results(patient_id: nil)
29
- query = Lab::LabOrder.where
30
- .not(order_id: Lab::LabResult.all.select(:order_id))
29
+ results_query = Lab::LabResult.all
30
+ results_query = results_query.where(person_id: patient_id) if patient_id
31
+
32
+ query = Lab::LabOrder.where.not(order_id: results_query.select(:order_id))
31
33
  query = query.where(patient_id: patient_id) if patient_id
32
34
 
33
35
  query
@@ -71,15 +71,24 @@ module Lab
71
71
  end
72
72
 
73
73
  order = Lab::LabOrder.find(order_id)
74
- unless order.concept_id == unknown_concept_id || params[:force_update]&.to_s&.casecmp?('true')
75
- raise ::UnprocessableEntityError
74
+ if order.concept_id != unknown_concept_id && !params[:force_update]&.casecmp?('true')
75
+ raise ::UnprocessableEntityError, "Can't change order specimen once set"
76
+ end
77
+
78
+ if specimen_id.to_i != order.concept_id
79
+ Rails.logger.debug("Updating order ##{order.order_id}")
80
+ order.update!(concept_id: specimen_id,
81
+ discontinued: true,
82
+ discontinued_by: User.current.user_id,
83
+ discontinued_date: params[:date]&.to_date || Time.now,
84
+ discontinued_reason_non_coded: 'Sample drawn/updated')
85
+ end
86
+
87
+ if params.key?(:reason_for_test_id)
88
+ Rails.logger.debug("Updating reason for test on order ##{order.order_id}")
89
+ update_reason_for_test(order, params[:reason_for_test_id])
76
90
  end
77
91
 
78
- order.update!(concept_id: specimen_id,
79
- discontinued: true,
80
- discontinued_by: User.current.user_id,
81
- discontinued_date: params[:date]&.to_date || Time.now,
82
- discontinued_reason_non_coded: 'Sample drawn/updated')
83
92
  Lab::LabOrderSerializer.serialize_order(order)
84
93
  end
85
94
 
@@ -159,7 +168,7 @@ module Lab
159
168
  order,
160
169
  Lab::Metadata::REASON_FOR_TEST_CONCEPT_NAME,
161
170
  params[:date],
162
- value_coded: params['reason_for_test_id']
171
+ value_coded: params[:reason_for_test_id]
163
172
  )
164
173
  end
165
174
 
@@ -192,6 +201,21 @@ module Lab
192
201
  def unknown_concept_id
193
202
  ConceptName.find_by_name!('Unknown').concept_id
194
203
  end
204
+
205
+ def update_reason_for_test(order, concept_id)
206
+ if concept_id.blank?
207
+ raise InvalidParameterError, "Reason for test can't be blank"
208
+ end
209
+
210
+ return if order.reason_for_test&.value_coded == concept_id
211
+
212
+ unless order.reason_for_test&.value_coded.nil?
213
+ raise InvalidParameterError, "Can't change reason for test once set"
214
+ end
215
+
216
+ order.reason_for_test.delete
217
+ add_reason_for_test(order, date: order.start_date, reason_for_test_id: concept_id)
218
+ end
195
219
  end
196
220
  end
197
221
  end
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.1'
4
+ VERSION = '1.1.2'
5
5
  end
@@ -3,4 +3,5 @@
3
3
  "Repeat / Missing"
4
4
  "Targeted"
5
5
  "Confirmatory"
6
+ "Stat"
6
7
  "Other"
@@ -86,8 +86,6 @@ Blood,HbA1c,"2019-11-19 14:05:31"
86
86
  Blood,Microalbumin,"2019-11-19 14:05:31"
87
87
  Blood,Microprotein,"2019-11-19 14:05:31"
88
88
  Blood,"Von Willebrand Factor","2019-11-19 14:05:31"
89
- Blood,"HIV_viral_load","2021-04-13 00:00:00"
90
- Blood,"Viral laod","2021-04-11 00:00:00"
91
89
  Blood,"Viral Load","2019-11-19 14:05:31"
92
90
  Blood,"Urine Lam","2019-11-19 14:05:31"
93
91
  Blood,"Protein and Sugar","2021-04-16"
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.1
4
+ version: 1.1.2
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-07-12 00:00:00.000000000 Z
11
+ date: 2021-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: couchrest