his_emr_api_lab 1.2.0 → 2.0.0
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/Rakefile +3 -1
- data/app/controllers/lab/application_controller.rb +5 -0
- data/app/controllers/lab/orders_controller.rb +3 -3
- data/app/controllers/lab/specimen_types_controller.rb +1 -1
- data/app/controllers/lab/test_result_indicators_controller.rb +6 -4
- data/app/controllers/lab/test_types_controller.rb +1 -1
- data/app/controllers/lab/tests_controller.rb +20 -17
- data/app/jobs/lab/application_job.rb +2 -0
- data/app/jobs/lab/update_patient_orders_job.rb +1 -1
- data/app/mailers/lab/application_mailer.rb +2 -0
- data/app/models/lab/application_record.rb +2 -0
- data/app/models/lab/lab_order.rb +1 -1
- data/app/models/lab/lims_failed_import.rb +2 -0
- data/app/serializers/lab/lab_order_serializer.rb +2 -2
- data/app/serializers/lab/result_serializer.rb +2 -2
- data/app/services/lab/accession_number_service.rb +2 -2
- data/app/services/lab/concepts_service.rb +2 -2
- data/app/services/lab/labelling_service/order_label.rb +2 -2
- data/app/services/lab/lims/api/blackhole_api.rb +1 -1
- data/app/services/lab/lims/api/couchdb_api.rb +3 -3
- data/app/services/lab/lims/api/mysql_api.rb +6 -6
- data/app/services/lab/lims/api/rest_api.rb +378 -372
- data/app/services/lab/lims/api/ws_api.rb +1 -1
- data/app/services/lab/lims/api_factory.rb +1 -1
- data/app/services/lab/lims/config.rb +1 -1
- data/app/services/lab/lims/exceptions.rb +1 -0
- data/app/services/lab/lims/migrator.rb +11 -12
- data/app/services/lab/lims/order_dto.rb +4 -4
- data/app/services/lab/lims/order_serializer.rb +12 -12
- data/app/services/lab/lims/pull_worker.rb +14 -13
- data/app/services/lab/lims/push_worker.rb +5 -5
- data/app/services/lab/lims/utils.rb +4 -6
- data/app/services/lab/lims/worker.rb +1 -1
- data/app/services/lab/orders_search_service.rb +3 -3
- data/app/services/lab/orders_service.rb +5 -5
- data/app/services/lab/results_service.rb +3 -3
- data/app/services/lab/tests_service.rb +5 -5
- data/db/migrate/20210126092910_create_lab_lab_accession_number_counters.rb +2 -0
- data/db/migrate/20210310115457_create_lab_lims_order_mappings.rb +2 -0
- data/db/migrate/20210326195504_add_order_revision_to_lims_order_mapping.rb +2 -0
- data/db/migrate/20210610095024_fix_numeric_results_value_type.rb +2 -0
- data/db/migrate/20210807111531_add_default_to_lims_order_mapping.rb +2 -0
- data/lib/auto12epl.rb +55 -53
- data/lib/couch_bum/couch_bum.rb +4 -4
- data/lib/generators/lab/install/templates/rswag-ui-lab.rb +2 -0
- data/lib/his_emr_api_lab.rb +2 -0
- data/lib/lab/engine.rb +2 -0
- data/lib/lab/version.rb +1 -1
- data/lib/logger_multiplexor.rb +2 -2
- data/lib/tasks/lab_tasks.rake +2 -0
- data/lib/tasks/loaders/loader_mixin.rb +4 -4
- data/lib/tasks/loaders/reasons_for_test_loader.rb +1 -1
- data/lib/tasks/loaders/specimens_loader.rb +6 -7
- data/lib/tasks/loaders/test_result_indicators_loader.rb +5 -5
- metadata +12 -17
@@ -1,433 +1,439 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module Lab
|
4
|
+
module Lims
|
5
|
+
module Api
|
6
|
+
class RestApi
|
7
|
+
class LimsApiError < GatewayError; end
|
5
8
|
|
6
|
-
|
9
|
+
class AuthenticationTokenExpired < LimsApiError; end
|
7
10
|
|
8
|
-
|
11
|
+
class InvalidParameters < LimsApiError; end
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
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 acknowledge(acknowledgement_dto)
|
36
|
-
Rails.logger.info("Acknowledging order ##{acknowledgement_dto} in LIMS")
|
37
|
-
response = in_authenticated_session do |headers|
|
38
|
-
RestClient.post(expand_uri('/acknowledge/test/results/recipient'), acknowledgement_dto, headers)
|
39
|
-
end
|
40
|
-
Rails.logger.info("Acknowledged order ##{acknowledgement_dto} in LIMS. Response: #{response}")
|
41
|
-
JSON.parse(response)
|
42
|
-
end
|
43
|
-
|
44
|
-
def update_order(_id, order_dto)
|
45
|
-
in_authenticated_session do |headers|
|
46
|
-
RestClient.post(expand_uri('update_order'), make_update_params(order_dto), headers)
|
47
|
-
end
|
13
|
+
def initialize(config)
|
14
|
+
@config = config
|
15
|
+
end
|
48
16
|
|
49
|
-
|
17
|
+
def create_order(order_dto)
|
18
|
+
response = in_authenticated_session do |headers|
|
19
|
+
Rails.logger.info("Pushing order ##{order_dto[:tracking_number]} to LIMS")
|
20
|
+
|
21
|
+
if order_dto['sample_type'].casecmp?('not_specified')
|
22
|
+
RestClient.post(expand_uri('request_order', api_version: 'v2'), make_create_params(order_dto), headers)
|
23
|
+
else
|
24
|
+
RestClient.post(expand_uri('create_order'), make_create_params(order_dto), headers)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
data = JSON.parse(response.body)
|
29
|
+
update_order_results(order_dto) unless data['message'].casecmp?('Order already available')
|
30
|
+
|
31
|
+
ActiveSupport::HashWithIndifferentAccess.new(
|
32
|
+
id: order_dto.fetch(:_id, order_dto[:tracking_number]),
|
33
|
+
rev: 0,
|
34
|
+
tracking_number: order_dto[:tracking_number]
|
35
|
+
)
|
36
|
+
end
|
50
37
|
|
51
|
-
|
52
|
-
|
38
|
+
def acknowledge(acknowledgement_dto)
|
39
|
+
Rails.logger.info("Acknowledging order ##{acknowledgement_dto} in LIMS")
|
40
|
+
response = in_authenticated_session do |headers|
|
41
|
+
RestClient.post(expand_uri('/acknowledge/test/results/recipient'), acknowledgement_dto, headers)
|
42
|
+
end
|
43
|
+
Rails.logger.info("Acknowledged order ##{acknowledgement_dto} in LIMS. Response: #{response}")
|
44
|
+
JSON.parse(response)
|
45
|
+
end
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
|
47
|
+
def update_order(_id, order_dto)
|
48
|
+
in_authenticated_session do |headers|
|
49
|
+
RestClient.post(expand_uri('update_order'), make_update_params(order_dto), headers)
|
50
|
+
end
|
57
51
|
|
58
|
-
|
59
|
-
patch_order_dto_with_lims_order!(order_dto, find_lims_order(order.accession_number))
|
60
|
-
end
|
52
|
+
update_order_results(order_dto)
|
61
53
|
|
62
|
-
|
63
|
-
begin
|
64
|
-
patch_order_dto_with_lims_results!(order_dto, find_lims_results(order.accession_number))
|
65
|
-
rescue InvalidParameters => e # LIMS responds with a 401 when a result is not found :(
|
66
|
-
Rails.logger.error("Failed to fetch results for ##{order.accession_number}: #{e.message}")
|
54
|
+
{ tracking_number: order_dto[:tracking_number] }
|
67
55
|
end
|
68
|
-
end
|
69
|
-
|
70
|
-
yield order_dto, OpenStruct.new(last_seq: 0)
|
71
|
-
rescue LimsApiError => e
|
72
|
-
Rails.logger.error("Failed to fetch updates for ##{order.accession_number}: #{e.class} - #{e.message}")
|
73
|
-
sleep(1)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def delete_order(_id, order_dto)
|
78
|
-
tracking_number = order_dto.fetch('tracking_number')
|
79
56
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
57
|
+
def consume_orders(*_args, patient_id: nil, **_kwargs)
|
58
|
+
orders_pending_updates(patient_id).each do |order|
|
59
|
+
order_dto = Lab::Lims::OrderSerializer.serialize_order(order)
|
60
|
+
|
61
|
+
if order_dto['priority'].nil? || order_dto['sample_type'].casecmp?('not_specified')
|
62
|
+
patch_order_dto_with_lims_order!(order_dto, find_lims_order(order.accession_number))
|
63
|
+
end
|
64
|
+
|
65
|
+
if order_dto['test_results'].empty?
|
66
|
+
begin
|
67
|
+
patch_order_dto_with_lims_results!(order_dto, find_lims_results(order.accession_number))
|
68
|
+
rescue InvalidParameters => e # LIMS responds with a 401 when a result is not found :(
|
69
|
+
Rails.logger.error("Failed to fetch results for ##{order.accession_number}: #{e.message}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
yield order_dto, OpenStruct.new(last_seq: 0)
|
74
|
+
rescue LimsApiError => e
|
75
|
+
Rails.logger.error("Failed to fetch updates for ##{order.accession_number}: #{e.class} - #{e.message}")
|
76
|
+
sleep(1)
|
77
|
+
end
|
78
|
+
end
|
89
79
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
80
|
+
def delete_order(_id, order_dto)
|
81
|
+
tracking_number = order_dto.fetch('tracking_number')
|
82
|
+
|
83
|
+
order_dto['tests'].each do |test|
|
84
|
+
Rails.logger.info("Voiding test '#{test}' (#{tracking_number}) in LIMS")
|
85
|
+
in_authenticated_session do |headers|
|
86
|
+
date_voided, voided_status = find_test_status(order_dto, test, 'Voided')
|
87
|
+
params = make_void_test_params(tracking_number, test, voided_status['updated_by'], date_voided)
|
88
|
+
RestClient.post(expand_uri('update_test'), params, headers)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
96
92
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
##
|
104
|
-
# Execute LIMS API calls within an authenticated session.
|
105
|
-
#
|
106
|
-
# Method automatically checks authenticates with LIMS if necessary and passes
|
107
|
-
# down the necessary headers for authentication to the REST call being made.
|
108
|
-
#
|
109
|
-
# Example:
|
110
|
-
#
|
111
|
-
# response = in_authenticated_session do |headers|
|
112
|
-
# RestClient.get(expand_uri('query_results_by_tracking_number/XXXXXX'), headers)
|
113
|
-
# end
|
114
|
-
#
|
115
|
-
# pp JSON.parse(response.body) if response.code == 200
|
116
|
-
def in_authenticated_session
|
117
|
-
retries ||= MAX_LIMS_RETRIES
|
118
|
-
|
119
|
-
self.authentication_token = authenticate unless authentication_token
|
120
|
-
|
121
|
-
response = yield 'token' => authentication_token, 'Content-type' => 'application/json'
|
122
|
-
check_response!(response)
|
123
|
-
rescue AuthenticationTokenExpired => e
|
124
|
-
self.authentication_token = nil
|
125
|
-
retry if (retries -= 1).positive?
|
126
|
-
rescue RestClient::ExceptionWithResponse => e
|
127
|
-
Rails.logger.error("LIMS Error: #{e.response&.code} - #{e.response&.body}")
|
128
|
-
raise e unless e.response&.code == 401
|
129
|
-
|
130
|
-
self.authentication_token = nil
|
131
|
-
retry if (retries -= 1).positive?
|
132
|
-
end
|
93
|
+
def verify_tracking_number(tracking_number)
|
94
|
+
find_lims_order(tracking_number)
|
95
|
+
rescue InvalidParameters => e
|
96
|
+
Rails.logger.error("Failed to verify tracking number #{tracking_number}: #{e.message}")
|
97
|
+
false
|
98
|
+
end
|
133
99
|
|
134
|
-
|
135
|
-
|
136
|
-
|
100
|
+
private
|
101
|
+
|
102
|
+
attr_reader :config
|
103
|
+
|
104
|
+
MAX_LIMS_RETRIES = 5 # LIMS API Calls can only fail this number of times before we give up on it
|
105
|
+
|
106
|
+
##
|
107
|
+
# Execute LIMS API calls within an authenticated session.
|
108
|
+
#
|
109
|
+
# Method automatically checks authenticates with LIMS if necessary and passes
|
110
|
+
# down the necessary headers for authentication to the REST call being made.
|
111
|
+
#
|
112
|
+
# Example:
|
113
|
+
#
|
114
|
+
# response = in_authenticated_session do |headers|
|
115
|
+
# RestClient.get(expand_uri('query_results_by_tracking_number/XXXXXX'), headers)
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# pp JSON.parse(response.body) if response.code == 200
|
119
|
+
def in_authenticated_session
|
120
|
+
retries ||= MAX_LIMS_RETRIES
|
121
|
+
|
122
|
+
self.authentication_token = authenticate unless authentication_token
|
123
|
+
|
124
|
+
response = yield 'token' => authentication_token, 'Content-type' => 'application/json'
|
125
|
+
check_response!(response)
|
126
|
+
rescue AuthenticationTokenExpired => e
|
127
|
+
self.authentication_token = nil
|
128
|
+
retry if (retries -= 1).positive?
|
129
|
+
rescue RestClient::ExceptionWithResponse => e
|
130
|
+
Rails.logger.error("LIMS Error: #{e.response&.code} - #{e.response&.body}")
|
131
|
+
raise e unless e.response&.code == 401
|
132
|
+
|
133
|
+
self.authentication_token = nil
|
134
|
+
retry if (retries -= 1).positive?
|
135
|
+
end
|
137
136
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
response_body = JSON.parse(response.body)
|
137
|
+
def authenticate
|
138
|
+
username = config.fetch(:username)
|
139
|
+
password = config.fetch(:password)
|
142
140
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
141
|
+
Rails.logger.debug("Authenticating with LIMS as: #{username}")
|
142
|
+
response = RestClient.get(expand_uri("re_authenticate/#{username}/#{password}"),
|
143
|
+
headers: { 'Content-type' => 'application/json' })
|
144
|
+
response_body = JSON.parse(response.body)
|
147
145
|
|
148
|
-
|
149
|
-
|
146
|
+
if response_body['status'] == 401
|
147
|
+
Rails.logger.error("Failed to authenticate with LIMS as #{config.fetch(:username)}: #{response_body['message']}")
|
148
|
+
raise LimsApiError, 'LIMS authentication failed'
|
149
|
+
end
|
150
150
|
|
151
|
-
|
152
|
-
|
153
|
-
end
|
151
|
+
response_body['data']['token']
|
152
|
+
end
|
154
153
|
|
155
|
-
|
156
|
-
|
157
|
-
|
154
|
+
def authentication_token=(token)
|
155
|
+
Thread.current[:lims_authentication_token] = token
|
156
|
+
end
|
158
157
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
# LIMS' doesn't properly use HTTP status codes; the codes are embedded in the
|
163
|
-
# response body. 200 is used for success responses and 401 for everything else.
|
164
|
-
# We have this work around to examine the response body and
|
165
|
-
# throw errors accordingly. The following are the errors thrown:
|
166
|
-
#
|
167
|
-
# Lims::AuthenticationTokenExpired
|
168
|
-
# Lims::InvalidParameters
|
169
|
-
# Lims::ApiError - Thrown when we couldn't make sense of the error
|
170
|
-
def check_response!(response)
|
171
|
-
body = JSON.parse(response.body)
|
172
|
-
return response if body['status'] == 200
|
173
|
-
|
174
|
-
Rails.logger.error("Lims Api Error: #{response.body}")
|
175
|
-
|
176
|
-
raise LimsApiError, "#{body['status']} - #{body['message']}" if body['status'] != 401
|
177
|
-
|
178
|
-
if body['message'].match?(/token expired/i)
|
179
|
-
raise AuthenticationTokenExpired, "Authentication token expired: #{body['message']}"
|
180
|
-
end
|
158
|
+
def authentication_token
|
159
|
+
Thread.current[:lims_authentication_token]
|
160
|
+
end
|
181
161
|
|
182
|
-
|
183
|
-
|
162
|
+
##
|
163
|
+
# Examines a response from LIMS to check if token has expired.
|
164
|
+
#
|
165
|
+
# LIMS' doesn't properly use HTTP status codes; the codes are embedded in the
|
166
|
+
# response body. 200 is used for success responses and 401 for everything else.
|
167
|
+
# We have this work around to examine the response body and
|
168
|
+
# throw errors accordingly. The following are the errors thrown:
|
169
|
+
#
|
170
|
+
# Lims::AuthenticationTokenExpired
|
171
|
+
# Lims::InvalidParameters
|
172
|
+
# Lims::ApiError - Thrown when we couldn't make sense of the error
|
173
|
+
def check_response!(response)
|
174
|
+
body = JSON.parse(response.body)
|
175
|
+
return response if body['status'] == 200
|
176
|
+
|
177
|
+
Rails.logger.error("Lims Api Error: #{response.body}")
|
178
|
+
|
179
|
+
raise LimsApiError, "#{body['status']} - #{body['message']}" if body['status'] != 401
|
180
|
+
|
181
|
+
if body['message'].match?(/token expired/i)
|
182
|
+
raise AuthenticationTokenExpired, "Authentication token expired: #{body['message']}"
|
183
|
+
end
|
184
|
+
|
185
|
+
raise InvalidParameters, body['message']
|
186
|
+
end
|
184
187
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
188
|
+
##
|
189
|
+
# Takes a LIMS API relative URI and converts it to a full URL.
|
190
|
+
def expand_uri(uri, api_version: 'v1')
|
191
|
+
protocol = config.fetch(:protocol)
|
192
|
+
host = config.fetch(:host)
|
193
|
+
port = config.fetch(:port)
|
194
|
+
uri = uri.gsub(%r{^/+}, '')
|
192
195
|
|
193
|
-
|
194
|
-
|
196
|
+
"#{protocol}://#{host}:#{port}/api/#{api_version}/#{uri}"
|
197
|
+
end
|
195
198
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
199
|
+
##
|
200
|
+
# Converts an OrderDto to parameters for POST /create_order
|
201
|
+
def make_create_params(order_dto)
|
202
|
+
{
|
203
|
+
tracking_number: order_dto.fetch(:tracking_number),
|
204
|
+
district: current_district,
|
205
|
+
health_facility_name: order_dto.fetch(:sending_facility),
|
206
|
+
first_name: order_dto.fetch(:patient).fetch(:first_name),
|
207
|
+
last_name: order_dto.fetch(:patient).fetch(:last_name),
|
208
|
+
phone_number: order_dto.fetch(:patient).fetch(:phone_number),
|
209
|
+
gender: order_dto.fetch(:patient).fetch(:gender),
|
210
|
+
arv_number: order_dto.fetch(:patient).fetch(:arv_number),
|
211
|
+
art_regimen: order_dto.fetch(:patient).fetch(:art_regimen),
|
212
|
+
art_start_date: order_dto.fetch(:patient).fetch(:art_start_date),
|
213
|
+
date_of_birth: order_dto.fetch(:patient).fetch(:dob),
|
214
|
+
national_patient_id: order_dto.fetch(:patient).fetch(:id),
|
215
|
+
requesting_clinician: requesting_clinician(order_dto),
|
216
|
+
sample_type: order_dto.fetch(:sample_type),
|
217
|
+
tests: order_dto.fetch(:tests),
|
218
|
+
date_sample_drawn: sample_drawn_date(order_dto),
|
219
|
+
sample_priority: order_dto.fetch(:priority) || 'Routine',
|
220
|
+
sample_status: order_dto.fetch(:sample_status),
|
221
|
+
target_lab: order_dto.fetch(:receiving_facility),
|
222
|
+
order_location: order_dto.fetch(:order_location) || 'Unknown',
|
223
|
+
who_order_test_first_name: order_dto.fetch(:who_order_test).fetch(:first_name),
|
224
|
+
who_order_test_last_name: order_dto.fetch(:who_order_test).fetch(:last_name)
|
225
|
+
}
|
226
|
+
end
|
224
227
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
228
|
+
##
|
229
|
+
# Converts an OrderDto to parameters for POST /update_order
|
230
|
+
def make_update_params(order_dto)
|
231
|
+
date_updated, status = sample_drawn_status(order_dto)
|
232
|
+
|
233
|
+
{
|
234
|
+
tracking_number: order_dto.fetch(:tracking_number),
|
235
|
+
who_updated: status.fetch(:updated_by),
|
236
|
+
date_updated:,
|
237
|
+
specimen_type: order_dto.fetch(:sample_type),
|
238
|
+
status: 'specimen_collected'
|
239
|
+
}
|
240
|
+
end
|
238
241
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
+
def current_district
|
243
|
+
health_centre = Location.current_health_center
|
244
|
+
raise 'Current health centre not set' unless health_centre
|
242
245
|
|
243
|
-
|
246
|
+
district = health_centre.district || Lab::Lims::Config.application['district']
|
244
247
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
248
|
+
unless district
|
249
|
+
health_centre_name = "##{health_centre.id} - #{health_centre.name}"
|
250
|
+
raise "Current health centre district not set: #{health_centre_name}"
|
251
|
+
end
|
249
252
|
|
250
|
-
|
251
|
-
|
253
|
+
district
|
254
|
+
end
|
252
255
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
256
|
+
##
|
257
|
+
# Extracts sample drawn status from an OrderDto
|
258
|
+
def sample_drawn_status(order_dto)
|
259
|
+
order_dto[:sample_statuses].each do |trail_entry|
|
260
|
+
date, status = trail_entry.each_pair.find { |_date, status| status['status'].casecmp?('Drawn') }
|
261
|
+
next unless date
|
259
262
|
|
260
|
-
|
261
|
-
|
263
|
+
return Date.strptime(date, '%Y%m%d%H%M%S').strftime('%Y-%m-%d'), status
|
264
|
+
end
|
262
265
|
|
263
|
-
|
264
|
-
|
266
|
+
[order_dto['date_created'], nil]
|
267
|
+
end
|
265
268
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
269
|
+
##
|
270
|
+
# Extracts a sample drawn date from a LIMS OrderDto.
|
271
|
+
def sample_drawn_date(order_dto)
|
272
|
+
sample_drawn_status(order_dto).first
|
273
|
+
end
|
271
274
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
275
|
+
##
|
276
|
+
# Extracts the requesting clinician from a LIMS OrderDto
|
277
|
+
def requesting_clinician(order_dto)
|
278
|
+
orderer = order_dto[:who_order_test]
|
276
279
|
|
277
|
-
|
278
|
-
|
280
|
+
"#{orderer[:first_name]} #{orderer[:last_name]}"
|
281
|
+
end
|
279
282
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
283
|
+
def find_lims_order(tracking_number)
|
284
|
+
response = in_authenticated_session do |headers|
|
285
|
+
Rails.logger.info("Fetching order ##{tracking_number}")
|
286
|
+
RestClient.get(expand_uri("query_order_by_tracking_number/#{tracking_number}"), headers)
|
287
|
+
end
|
285
288
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
+
Rails.logger.info("Order ##{tracking_number} found... Parsing...")
|
290
|
+
JSON.parse(response).fetch('data')
|
291
|
+
end
|
289
292
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
293
|
+
def find_lims_results(tracking_number)
|
294
|
+
response = in_authenticated_session do |headers|
|
295
|
+
Rails.logger.info("Fetching results for order ##{tracking_number}")
|
296
|
+
RestClient.get(expand_uri("query_results_by_tracking_number/#{tracking_number}"), headers)
|
297
|
+
end
|
295
298
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
+
Rails.logger.info("Result for order ##{tracking_number} found... Parsing...")
|
300
|
+
JSON.parse(response).fetch('data').fetch('results')
|
301
|
+
end
|
299
302
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
303
|
+
##
|
304
|
+
# Make a copy of the order_dto with the results from LIMS parsed
|
305
|
+
# and appended to it.
|
306
|
+
def patch_order_dto_with_lims_results!(order_dto, results)
|
307
|
+
order_dto.merge!(
|
308
|
+
'_id' => order_dto[:tracking_number],
|
309
|
+
'_rev' => 0,
|
310
|
+
'test_results' => results.each_with_object({}) do |result, formatted_results|
|
311
|
+
test_name, measures = result
|
312
|
+
result_date = measures.delete('result_date')
|
313
|
+
|
314
|
+
formatted_results[test_name] = {
|
315
|
+
results: measures.each_with_object({}) do |measure, processed_measures|
|
316
|
+
processed_measures[measure[0]] = { 'result_value' => measure[1] }
|
317
|
+
end,
|
318
|
+
result_date:,
|
319
|
+
result_entered_by: {}
|
320
|
+
}
|
321
|
+
end
|
322
|
+
)
|
323
|
+
end
|
321
324
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
325
|
+
def patch_order_dto_with_lims_order!(order_dto, lims_order)
|
326
|
+
order_dto.merge!(
|
327
|
+
'sample_type' => lims_order['other']['sample_type'],
|
328
|
+
'sample_status' => lims_order['other']['specimen_status'],
|
329
|
+
'priority' => lims_order['other']['priority']
|
330
|
+
)
|
331
|
+
end
|
329
332
|
|
330
|
-
|
331
|
-
|
333
|
+
def update_order_results(order_dto)
|
334
|
+
return nil if order_dto['test_results'].nil? || order_dto['test_results'].empty?
|
332
335
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
336
|
+
order_dto['test_results'].each do |test_name, results|
|
337
|
+
Rails.logger.info("Pushing result for order ##{order_dto['tracking_number']}")
|
338
|
+
in_authenticated_session do |headers|
|
339
|
+
params = make_update_test_params(order_dto['tracking_number'], test_name, results)
|
337
340
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
341
|
+
RestClient.post(expand_uri('update_test'), params, headers)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
342
345
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
346
|
+
def make_update_test_params(tracking_number, test_name, results, test_status = 'Drawn')
|
347
|
+
{
|
348
|
+
tracking_number:,
|
349
|
+
test_name:,
|
350
|
+
result_date: results['result_date'],
|
351
|
+
time_updated: results['result_date'],
|
352
|
+
who_updated: {
|
353
|
+
first_name: results[:result_entered_by][:first_name],
|
354
|
+
last_name: results[:result_entered_by][:last_name],
|
355
|
+
id_number: results[:result_entered_by][:id]
|
356
|
+
},
|
357
|
+
test_status:,
|
358
|
+
results: results['results']&.each_with_object({}) do |measure, formatted_results|
|
359
|
+
measure_name, measure_value = measure
|
360
|
+
|
361
|
+
formatted_results[measure_name] = measure_value['result_value']
|
362
|
+
end
|
363
|
+
}
|
364
|
+
end
|
362
365
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
+
def find_test_status(order_dto, target_test, target_status)
|
367
|
+
order_dto['test_statuses'].each do |test, statuses|
|
368
|
+
next unless test.casecmp?(target_test)
|
366
369
|
|
367
|
-
|
368
|
-
|
370
|
+
statuses.each do |date, status|
|
371
|
+
next unless status['status'].casecmp?(target_status)
|
369
372
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
+
return [Date.strptime(date, '%Y%m%d%H%M%S'), status]
|
374
|
+
end
|
375
|
+
end
|
373
376
|
|
374
|
-
|
375
|
-
|
377
|
+
nil
|
378
|
+
end
|
376
379
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
380
|
+
def make_void_test_params(tracking_number, test_name, voided_by, void_date = nil)
|
381
|
+
void_date ||= Time.now
|
382
|
+
|
383
|
+
{
|
384
|
+
tracking_number:,
|
385
|
+
test_name:,
|
386
|
+
time_updated: void_date,
|
387
|
+
who_updated: {
|
388
|
+
first_name: voided_by[:first_name],
|
389
|
+
last_name: voided_by[:last_name],
|
390
|
+
id_number: voided_by[:id]
|
391
|
+
},
|
392
|
+
test_status: 'voided'
|
393
|
+
}
|
394
|
+
end
|
392
395
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
+
def orders_pending_updates(patient_id = nil)
|
397
|
+
Rails.logger.info('Looking for orders that need to be updated...')
|
398
|
+
orders = {}
|
396
399
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
+
orders_without_specimen(patient_id).each { |order| orders[order.order_id] = order }
|
401
|
+
orders_without_results(patient_id).each { |order| orders[order.order_id] = order }
|
402
|
+
orders_without_reason(patient_id).each { |order| orders[order.order_id] = order }
|
400
403
|
|
401
|
-
|
402
|
-
|
404
|
+
orders.values
|
405
|
+
end
|
403
406
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
407
|
+
def orders_without_specimen(patient_id = nil)
|
408
|
+
Rails.logger.debug('Looking for orders without a specimen')
|
409
|
+
unknown_specimen = ConceptName.where(name: Lab::Metadata::UNKNOWN_SPECIMEN)
|
410
|
+
.select(:concept_id)
|
411
|
+
orders = Lab::LabOrder.where(concept_id: unknown_specimen)
|
412
|
+
.where.not(accession_number: Lab::LimsOrderMapping.select(:lims_id))
|
413
|
+
orders = orders.where(patient_id:) if patient_id
|
411
414
|
|
412
|
-
|
413
|
-
|
415
|
+
orders
|
416
|
+
end
|
414
417
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
418
|
+
def orders_without_results(patient_id = nil)
|
419
|
+
Rails.logger.debug('Looking for orders without a result')
|
420
|
+
# Lab::OrdersSearchService.find_orders_without_results(patient_id: patient_id)
|
421
|
+
# .where.not(accession_number: Lab::LimsOrderMapping.select(:lims_id).where("pulled_at IS NULL"))
|
422
|
+
Lab::OrdersSearchService.find_orders_without_results(patient_id:)
|
423
|
+
.where(order_id: Lab::LimsOrderMapping.select(:order_id))
|
424
|
+
end
|
422
425
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
426
|
+
def orders_without_reason(patient_id = nil)
|
427
|
+
Rails.logger.debug('Looking for orders without a reason for test')
|
428
|
+
orders = Lab::LabOrder.joins(:reason_for_test)
|
429
|
+
.merge(Observation.where(value_coded: nil, value_text: nil))
|
430
|
+
.limit(1000)
|
431
|
+
.where.not(accession_number: Lab::LimsOrderMapping.select(:lims_id))
|
432
|
+
orders = orders.where(patient_id:) if patient_id
|
430
433
|
|
431
|
-
|
434
|
+
orders
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
432
438
|
end
|
433
439
|
end
|