his_emr_api_lab 1.0.4 → 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/README.md +20 -1
- data/app/jobs/lab/update_patient_orders_job.rb +33 -0
- data/app/jobs/lab/void_order_job.rb +18 -0
- data/app/models/lab/lab_order.rb +4 -0
- data/app/serializers/lab/lab_order_serializer.rb +7 -1
- data/app/services/lab/lims/api/rest_api.rb +405 -0
- data/app/services/lab/lims/api/ws_api.rb +121 -0
- data/app/services/lab/lims/config.rb +45 -8
- data/app/services/lab/lims/migrator.rb +12 -9
- data/app/services/lab/lims/order_dto.rb +10 -3
- data/app/services/lab/lims/order_serializer.rb +32 -8
- data/app/services/lab/lims/pull_worker.rb +295 -0
- data/app/services/lab/lims/push_worker.rb +104 -0
- data/app/services/lab/lims/utils.rb +6 -1
- data/app/services/lab/lims/worker.rb +40 -317
- data/app/services/lab/metadata.rb +1 -0
- data/app/services/lab/orders_search_service.rb +20 -0
- data/app/services/lab/orders_service.rb +35 -9
- 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 +22 -3
- data/app/services/lab/lims/failed_imports.rb +0 -34
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
|
data/README.md
CHANGED
@@ -43,9 +43,28 @@ Finally run:
|
|
43
43
|
$ bundle exec rails lab:install
|
44
44
|
```
|
45
45
|
|
46
|
+
## Configuration
|
47
|
+
|
48
|
+
This module in most cases should work without any configuration, however to enable
|
49
|
+
certain features some configuration may be required. Visit the
|
50
|
+
[configuration](./docs/configuration.md) page to learn how to configure the
|
51
|
+
application.
|
52
|
+
|
46
53
|
## Contributing
|
47
54
|
|
48
|
-
|
55
|
+
Fork this application create a branch for the contribution you want to make,
|
56
|
+
push your changes to the branch and then issue a pull request. You may want
|
57
|
+
to create a new first on our repository, so that your pull request references
|
58
|
+
this issue.
|
59
|
+
|
60
|
+
If you are fixing a bug, it will be nice to add a unit test that exposes
|
61
|
+
the bug. Although this is not a requirement in most cases.
|
62
|
+
|
63
|
+
Be sure to follow [this](https://github.com/rubocop/ruby-style-guide) Ruby
|
64
|
+
style guide. We don't necessarily look for strict adherence to the guidelines
|
65
|
+
but too much a departure from it is frowned upon. For example, you will be forgiven
|
66
|
+
for writing a method with 15 to 20 lines if you clearly justify why you couldn't
|
67
|
+
break that method into multiple smaller methods.
|
49
68
|
|
50
69
|
## License
|
51
70
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
##
|
5
|
+
# Fetches updates on a patient's orders from external sources.
|
6
|
+
class UpdatePatientOrdersJob < ApplicationJob
|
7
|
+
queue_as :default
|
8
|
+
|
9
|
+
def perform(patient_id)
|
10
|
+
Rails.logger.info('Initialising LIMS REST API...')
|
11
|
+
|
12
|
+
User.current = Lab::Lims::Utils.lab_user
|
13
|
+
Location.current = Location.find_by_name('ART clinic')
|
14
|
+
|
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
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
class VoidOrderJob < ApplicationJob
|
5
|
+
queue_as :default
|
6
|
+
|
7
|
+
def perform(order_id)
|
8
|
+
Rails.logger.info("Voiding order ##{order_id} in LIMS")
|
9
|
+
|
10
|
+
User.current = Lab::Lims::Utils.lab_user
|
11
|
+
Location.current = Location.find_by_name('ART clinic')
|
12
|
+
|
13
|
+
lims_api = Lab::Lims::Api::RestApi.new
|
14
|
+
worker = Lab::Lims::Worker.new(lims_api)
|
15
|
+
worker.push_order(Lab::LabOrder.unscoped.find(order_id))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/app/models/lab/lab_order.rb
CHANGED
@@ -41,6 +41,10 @@ module Lab
|
|
41
41
|
.where.not(concept_id: ConceptName.where(name: 'Tests ordered').select(:concept_id))
|
42
42
|
end
|
43
43
|
|
44
|
+
scope :drawn, -> { where.not(concept_id: ConceptName.where(name: 'Unknown').select(:concept_id)) }
|
45
|
+
|
46
|
+
scope :not_drawn, -> { where(concept_id: ConceptName.where(name: 'Unknown').select(:concept_id)) }
|
47
|
+
|
44
48
|
def self.prefetch_relationships
|
45
49
|
includes(:reason_for_test,
|
46
50
|
:requesting_clinician,
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Lab
|
4
4
|
module LabOrderSerializer
|
5
5
|
def self.serialize_order(order, tests: nil, requesting_clinician: nil, reason_for_test: nil, target_lab: nil)
|
6
|
-
tests ||= order.tests
|
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
9
|
target_lab ||= order.target_lab
|
@@ -45,5 +45,11 @@ module Lab
|
|
45
45
|
|
46
46
|
ConceptName.select(:name).find_by_concept_id(concept_id)&.name
|
47
47
|
end
|
48
|
+
|
49
|
+
def self.voided_tests(order)
|
50
|
+
concept = ConceptName.where(name: Lab::Metadata::TEST_TYPE_CONCEPT_NAME)
|
51
|
+
.select(:concept_id)
|
52
|
+
LabTest.unscoped.where(concept: concept, order: order, voided: true)
|
53
|
+
end
|
48
54
|
end
|
49
55
|
end
|
@@ -0,0 +1,405 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Lab::Lims::Api::RestApi
|
4
|
+
class LimsApiError < GatewayError; end
|
5
|
+
|
6
|
+
class AuthenticationTokenExpired < LimsApiError; end
|
7
|
+
|
8
|
+
class InvalidParameters < LimsApiError; end
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_order(order_dto)
|
15
|
+
response = in_authenticated_session do |headers|
|
16
|
+
Rails.logger.info("Pushing order ##{order_dto[:tracking_number]} to LIMS")
|
17
|
+
|
18
|
+
if order_dto['sample_type'].casecmp?('not_specified')
|
19
|
+
RestClient.post(expand_uri('request_order', api_version: 'v2'), make_create_params(order_dto), headers)
|
20
|
+
else
|
21
|
+
RestClient.post(expand_uri('create_order'), make_create_params(order_dto), headers)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
data = JSON.parse(response.body)
|
26
|
+
update_order_results(order_dto) unless data['message'].casecmp?('Order already available')
|
27
|
+
|
28
|
+
ActiveSupport::HashWithIndifferentAccess.new(
|
29
|
+
id: order_dto.fetch(:_id, order_dto[:tracking_number]),
|
30
|
+
rev: 0,
|
31
|
+
tracking_number: order_dto[:tracking_number]
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_order(_id, order_dto)
|
36
|
+
in_authenticated_session do |headers|
|
37
|
+
RestClient.post(expand_uri('update_order'), make_update_params(order_dto), headers)
|
38
|
+
end
|
39
|
+
|
40
|
+
update_order_results(order_dto)
|
41
|
+
|
42
|
+
{ tracking_number: order_dto[:tracking_number] }
|
43
|
+
end
|
44
|
+
|
45
|
+
def consume_orders(*_args, patient_id: nil, **_kwargs)
|
46
|
+
orders_pending_updates(patient_id).each do |order|
|
47
|
+
order_dto = Lab::Lims::OrderSerializer.serialize_order(order)
|
48
|
+
|
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))
|
51
|
+
end
|
52
|
+
|
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
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete_order(_id, order_dto)
|
66
|
+
tracking_number = order_dto.fetch('tracking_number')
|
67
|
+
|
68
|
+
order_dto['tests'].each do |test|
|
69
|
+
Rails.logger.info("Voiding test '#{test}' (#{tracking_number}) in LIMS")
|
70
|
+
in_authenticated_session do |headers|
|
71
|
+
date_voided, voided_status = find_test_status(order_dto, test, 'Voided')
|
72
|
+
params = make_void_test_params(tracking_number, test, voided_status['updated_by'], date_voided)
|
73
|
+
RestClient.post(expand_uri('update_test'), params, headers)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
attr_reader :config
|
81
|
+
|
82
|
+
MAX_LIMS_RETRIES = 5 # LIMS API Calls can only fail this number of times before we give up on it
|
83
|
+
|
84
|
+
##
|
85
|
+
# Execute LIMS API calls within an authenticated session.
|
86
|
+
#
|
87
|
+
# Method automatically checks authenticates with LIMS if necessary and passes
|
88
|
+
# down the necessary headers for authentication to the REST call being made.
|
89
|
+
#
|
90
|
+
# Example:
|
91
|
+
#
|
92
|
+
# response = in_authenticated_session do |headers|
|
93
|
+
# RestClient.get(expand_uri('query_results_by_tracking_number/XXXXXX'), headers)
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# pp JSON.parse(response.body) if response.code == 200
|
97
|
+
def in_authenticated_session
|
98
|
+
retries ||= MAX_LIMS_RETRIES
|
99
|
+
|
100
|
+
self.authentication_token = authenticate unless authentication_token
|
101
|
+
|
102
|
+
response = yield 'token' => authentication_token, 'Content-type' => 'application/json'
|
103
|
+
check_response!(response)
|
104
|
+
rescue AuthenticationTokenExpired => e
|
105
|
+
self.authentication_token = nil
|
106
|
+
retry if (retries -= 1).positive?
|
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
|
110
|
+
|
111
|
+
self.authentication_token = nil
|
112
|
+
retry if (retries -= 1).positive?
|
113
|
+
end
|
114
|
+
|
115
|
+
def authenticate
|
116
|
+
username = config.fetch(:username)
|
117
|
+
password = config.fetch(:password)
|
118
|
+
|
119
|
+
Rails.logger.debug("Authenticating with LIMS as: #{username}")
|
120
|
+
response = RestClient.get(expand_uri("re_authenticate/#{username}/#{password}"),
|
121
|
+
headers: { 'Content-type' => 'application/json' })
|
122
|
+
response_body = JSON.parse(response.body)
|
123
|
+
|
124
|
+
if response_body['status'] == 401
|
125
|
+
Rails.logger.error("Failed to authenticate with LIMS as #{config.fetch(:username)}: #{response_body['message']}")
|
126
|
+
raise LimsApiError, 'LIMS authentication failed'
|
127
|
+
end
|
128
|
+
|
129
|
+
response_body['data']['token']
|
130
|
+
end
|
131
|
+
|
132
|
+
def authentication_token=(token)
|
133
|
+
Thread.current[:lims_authentication_token] = token
|
134
|
+
end
|
135
|
+
|
136
|
+
def authentication_token
|
137
|
+
Thread.current[:lims_authentication_token]
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Examines a response from LIMS to check if token has expired.
|
142
|
+
#
|
143
|
+
# LIMS' doesn't properly use HTTP status codes; the codes are embedded in the
|
144
|
+
# response body. 200 is used for success responses and 401 for everything else.
|
145
|
+
# We have this work around to examine the response body and
|
146
|
+
# throw errors accordingly. The following are the errors thrown:
|
147
|
+
#
|
148
|
+
# Lims::AuthenticationTokenExpired
|
149
|
+
# Lims::InvalidParameters
|
150
|
+
# Lims::ApiError - Thrown when we couldn't make sense of the error
|
151
|
+
def check_response!(response)
|
152
|
+
body = JSON.parse(response.body)
|
153
|
+
return response if body['status'] == 200
|
154
|
+
|
155
|
+
Rails.logger.error("Lims Api Error: #{response.body}")
|
156
|
+
|
157
|
+
raise LimsApiError, "#{body['status']} - #{body['message']}" if body['status'] != 401
|
158
|
+
|
159
|
+
if body['message'].match?(/token expired/i)
|
160
|
+
raise AuthenticationTokenExpired, "Authentication token expired: #{body['message']}"
|
161
|
+
end
|
162
|
+
|
163
|
+
raise InvalidParameters, body['message']
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Takes a LIMS API relative URI and converts it to a full URL.
|
168
|
+
def expand_uri(uri, api_version: 'v1')
|
169
|
+
protocol = config.fetch(:protocol)
|
170
|
+
host = config.fetch(:host)
|
171
|
+
port = config.fetch(:port)
|
172
|
+
uri = uri.gsub(%r{^/+}, '')
|
173
|
+
|
174
|
+
"#{protocol}://#{host}:#{port}/api/#{api_version}/#{uri}"
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Converts an OrderDTO to parameters for POST /create_order
|
179
|
+
def make_create_params(order_dto)
|
180
|
+
{
|
181
|
+
tracking_number: order_dto.fetch(:tracking_number),
|
182
|
+
district: current_district,
|
183
|
+
health_facility_name: order_dto.fetch(:sending_facility),
|
184
|
+
first_name: order_dto.fetch(:patient).fetch(:first_name),
|
185
|
+
last_name: order_dto.fetch(:patient).fetch(:last_name),
|
186
|
+
phone_number: order_dto.fetch(:patient).fetch(:phone_number),
|
187
|
+
gender: order_dto.fetch(:patient).fetch(:gender),
|
188
|
+
national_patient_id: order_dto.fetch(:patient).fetch(:id),
|
189
|
+
requesting_clinician: requesting_clinician(order_dto),
|
190
|
+
sample_type: order_dto.fetch(:sample_type),
|
191
|
+
tests: order_dto.fetch(:tests),
|
192
|
+
date_sample_drawn: sample_drawn_date(order_dto),
|
193
|
+
sample_priority: order_dto.fetch(:priority) || 'Routine',
|
194
|
+
sample_status: order_dto.fetch(:sample_status),
|
195
|
+
target_lab: order_dto.fetch(:receiving_facility),
|
196
|
+
order_location: order_dto.fetch(:order_location) || 'Unknown',
|
197
|
+
who_order_test_first_name: order_dto.fetch(:who_order_test).fetch(:first_name),
|
198
|
+
who_order_test_last_name: order_dto.fetch(:who_order_test).fetch(:last_name)
|
199
|
+
}
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Converts an OrderDTO to parameters for POST /update_order
|
204
|
+
def make_update_params(order_dto)
|
205
|
+
date_updated, status = sample_drawn_status(order_dto)
|
206
|
+
|
207
|
+
{
|
208
|
+
tracking_number: order_dto.fetch(:tracking_number),
|
209
|
+
who_updated: status.fetch(:updated_by),
|
210
|
+
date_updated: date_updated,
|
211
|
+
specimen_type: order_dto.fetch(:sample_type),
|
212
|
+
status: 'specimen_collected'
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
def current_district
|
217
|
+
health_centre = Location.current_health_center
|
218
|
+
raise 'Current health centre not set' unless health_centre
|
219
|
+
|
220
|
+
district = health_centre.district || Lab::Lims::Config.application['district']
|
221
|
+
|
222
|
+
unless district
|
223
|
+
health_centre_name = "##{health_centre.id} - #{health_centre.name}"
|
224
|
+
raise "Current health centre district not set: #{health_centre_name}"
|
225
|
+
end
|
226
|
+
|
227
|
+
district
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# Extracts sample drawn status from an OrderDTO
|
232
|
+
def sample_drawn_status(order_dto)
|
233
|
+
order_dto[:sample_statuses].each do |trail_entry|
|
234
|
+
date, status = trail_entry.each_pair.find { |_date, status| status['status'].casecmp?('Drawn') }
|
235
|
+
next unless date
|
236
|
+
|
237
|
+
return Date.strptime(date, '%Y%m%d%H%M%S').strftime('%Y-%m-%d'), status
|
238
|
+
end
|
239
|
+
|
240
|
+
[order_dto['date_created'], nil]
|
241
|
+
end
|
242
|
+
|
243
|
+
##
|
244
|
+
# Extracts a sample drawn date from a LIMS OrderDTO.
|
245
|
+
def sample_drawn_date(order_dto)
|
246
|
+
sample_drawn_status(order_dto).first
|
247
|
+
end
|
248
|
+
|
249
|
+
##
|
250
|
+
# Extracts the requesting clinician from a LIMS OrderDTO
|
251
|
+
def requesting_clinician(order_dto)
|
252
|
+
orderer = order_dto[:who_order_test]
|
253
|
+
|
254
|
+
"#{orderer[:first_name]} #{orderer[:last_name]}"
|
255
|
+
end
|
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
|
+
|
277
|
+
##
|
278
|
+
# Make a copy of the order_dto with the results from LIMS parsed
|
279
|
+
# and appended to it.
|
280
|
+
def patch_order_dto_with_lims_results!(order_dto, results)
|
281
|
+
order_dto.merge!(
|
282
|
+
'_id' => order_dto[:tracking_number],
|
283
|
+
'_rev' => 0,
|
284
|
+
'test_results' => results.each_with_object({}) do |result, formatted_results|
|
285
|
+
test_name, measures = result
|
286
|
+
result_date = measures.delete('result_date')
|
287
|
+
|
288
|
+
formatted_results[test_name] = {
|
289
|
+
results: measures.each_with_object({}) do |measure, processed_measures|
|
290
|
+
processed_measures[measure[0]] = { 'result_value' => measure[1] }
|
291
|
+
end,
|
292
|
+
result_date: result_date,
|
293
|
+
result_entered_by: {}
|
294
|
+
}
|
295
|
+
end
|
296
|
+
)
|
297
|
+
end
|
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
|
+
|
307
|
+
def update_order_results(order_dto)
|
308
|
+
return nil if order_dto['test_results'].nil? || order_dto['test_results'].empty?
|
309
|
+
|
310
|
+
order_dto['test_results'].each do |test_name, results|
|
311
|
+
Rails.logger.info("Pushing result for order ##{order_dto['tracking_number']}")
|
312
|
+
in_authenticated_session do |headers|
|
313
|
+
params = make_update_test_params(order_dto['tracking_number'], test_name, results)
|
314
|
+
|
315
|
+
RestClient.post(expand_uri('update_test'), params, headers)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def make_update_test_params(tracking_number, test_name, results, test_status = 'Drawn')
|
321
|
+
{
|
322
|
+
tracking_number: tracking_number,
|
323
|
+
test_name: test_name,
|
324
|
+
result_date: results['result_date'],
|
325
|
+
time_updated: results['result_date'],
|
326
|
+
who_updated: {
|
327
|
+
first_name: results[:result_entered_by][:first_name],
|
328
|
+
last_name: results[:result_entered_by][:last_name],
|
329
|
+
id_number: results[:result_entered_by][:id]
|
330
|
+
},
|
331
|
+
test_status: test_status,
|
332
|
+
results: results['results']&.each_with_object({}) do |measure, formatted_results|
|
333
|
+
measure_name, measure_value = measure
|
334
|
+
|
335
|
+
formatted_results[measure_name] = measure_value['result_value']
|
336
|
+
end
|
337
|
+
}
|
338
|
+
end
|
339
|
+
|
340
|
+
def find_test_status(order_dto, target_test, target_status)
|
341
|
+
order_dto['test_statuses'].each do |test, statuses|
|
342
|
+
next unless test.casecmp?(target_test)
|
343
|
+
|
344
|
+
statuses.each do |date, status|
|
345
|
+
next unless status['status'].casecmp?(target_status)
|
346
|
+
|
347
|
+
return [Date.strptime(date, '%Y%m%d%H%M%S'), status]
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
nil
|
352
|
+
end
|
353
|
+
|
354
|
+
def make_void_test_params(tracking_number, test_name, voided_by, void_date = nil)
|
355
|
+
void_date ||= Time.now
|
356
|
+
|
357
|
+
{
|
358
|
+
tracking_number: tracking_number,
|
359
|
+
test_name: test_name,
|
360
|
+
time_updated: void_date,
|
361
|
+
who_updated: {
|
362
|
+
first_name: voided_by[:first_name],
|
363
|
+
last_name: voided_by[:last_name],
|
364
|
+
id_number: voided_by[:id]
|
365
|
+
},
|
366
|
+
test_status: 'voided'
|
367
|
+
}
|
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
|
405
|
+
end
|