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 +4 -4
- data/app/jobs/lab/update_patient_orders_job.rb +16 -3
- data/app/services/lab/lims/api/rest_api.rb +89 -28
- data/app/services/lab/lims/order_dto.rb +10 -3
- data/app/services/lab/lims/order_serializer.rb +1 -0
- data/app/services/lab/lims/pull_worker.rb +2 -2
- data/app/services/lab/lims/push_worker.rb +2 -1
- data/app/services/lab/metadata.rb +1 -0
- data/app/services/lab/orders_search_service.rb +4 -2
- data/app/services/lab/orders_service.rb +32 -8
- data/lib/lab/version.rb +1 -1
- data/lib/tasks/loaders/data/reasons-for-test.csv +1 -0
- data/lib/tasks/loaders/data/tests.csv +0 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2202cc31522b4ae92847b3102841cfcf0c65a9e389fde480e3e787f1ef49ec7
|
|
4
|
+
data.tar.gz: a82291c66cdd6fb651e0917dc68e5b28b6dcedeb124973ab57f9f60187b4c920
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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:
|
|
29
|
+
id: order_dto.fetch(:_id, order_dto[:tracking_number]),
|
|
31
30
|
rev: 0,
|
|
32
|
-
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
|
-
|
|
48
|
-
|
|
46
|
+
orders_pending_updates(patient_id).each do |order|
|
|
47
|
+
order_dto = Lab::Lims::OrderSerializer.serialize_order(order)
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
109
|
-
raise e unless e.response
|
|
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}"),
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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'],
|
|
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
|
|
|
@@ -26,8 +26,10 @@ module Lab
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def find_orders_without_results(patient_id: nil)
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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[
|
|
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
|
@@ -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.
|
|
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-
|
|
11
|
+
date: 2021-07-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: couchrest
|