his_emr_api_lab 1.1.1 → 1.1.2

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: 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