his_emr_api_lab 2.0.9 → 2.1.1
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/process_lab_result_job.rb +13 -0
- data/app/models/lab/lab_order.rb +4 -1
- data/app/serializers/lab/lab_order_serializer.rb +7 -0
- data/app/serializers/lab/result_serializer.rb +9 -1
- data/app/services/lab/accession_number_service.rb +2 -2
- data/app/services/lab/labelling_service/order_label.rb +9 -12
- data/app/services/lab/lims/pull_worker.rb +6 -6
- data/app/services/lab/lims/worker.rb +1 -1
- data/app/services/lab/metadata.rb +1 -0
- data/app/services/lab/orders_service.rb +6 -1
- data/app/services/lab/results_service.rb +9 -2
- data/lib/auto12epl.rb +19 -41
- data/lib/lab/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c80aacd232547335da6ecc016e3dd846f4f10f5490653db6ad877c21fc4eb3e
|
|
4
|
+
data.tar.gz: 4ddb8bf35b0c011f9e85c7167c1b3f6e551933d0f697123771ce007ea728a74e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 414b6d9dbe2f808869ce6a2bc17818b8c8da6de35514dbc418aec03bdee86e934367569e15139ddc1d38a432a32a47c55955b276384203449e90ba99c2f84acc
|
|
7
|
+
data.tar.gz: bdbda16a58d3a67cf89aeeee958eb9975bf4d06af53b0cfda460f8b5fb31c09354695c7a26e8ee4c71dfcfa6c7910d2c864c56e35be6db0cfa74736b864933b8
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lab
|
|
4
|
+
##
|
|
5
|
+
# Push an order to LIMS.
|
|
6
|
+
class ProcessLabResultJob < ApplicationJob
|
|
7
|
+
queue_as :default
|
|
8
|
+
def perform(results_obs_id, serializer, result_enter_by)
|
|
9
|
+
results_obs = Lab::LabResult.find(results_obs_id)
|
|
10
|
+
Lab::ResultsService.process_result_completion(results_obs, serializer, result_enter_by)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/app/models/lab/lab_order.rb
CHANGED
|
@@ -41,7 +41,10 @@ module Lab
|
|
|
41
41
|
|
|
42
42
|
default_scope do
|
|
43
43
|
joins(:order_type)
|
|
44
|
-
.merge(OrderType.where(name:
|
|
44
|
+
.merge(OrderType.where(name: [
|
|
45
|
+
Lab::Metadata::ORDER_TYPE_NAME,
|
|
46
|
+
Lab::Metadata::HTS_ORDER_TYPE_NAME
|
|
47
|
+
]))
|
|
45
48
|
.where.not(concept_id: ConceptName.where(name: 'Tests ordered').select(:concept_id))
|
|
46
49
|
end
|
|
47
50
|
|
|
@@ -8,11 +8,18 @@ module Lab
|
|
|
8
8
|
reason_for_test ||= order.reason_for_test
|
|
9
9
|
target_lab = target_lab&.value_text || order.target_lab&.value_text || Location.current_health_center&.name
|
|
10
10
|
|
|
11
|
+
encounter = Encounter.find_by_encounter_id(order.encounter_id)
|
|
12
|
+
program = Program.find_by_program_id(encounter.program_id)
|
|
13
|
+
|
|
11
14
|
ActiveSupport::HashWithIndifferentAccess.new(
|
|
12
15
|
{
|
|
13
16
|
id: order.order_id,
|
|
17
|
+
order_type_id: order.order_type_id,
|
|
14
18
|
order_id: order.order_id, # Deprecated: Link to :id
|
|
15
19
|
encounter_id: order.encounter_id,
|
|
20
|
+
location_id: encounter.location_id,
|
|
21
|
+
program_id: encounter.program_id,
|
|
22
|
+
program_name: program.name,
|
|
16
23
|
order_date: order.start_date,
|
|
17
24
|
patient_id: order.patient_id,
|
|
18
25
|
accession_number: order.accession_number,
|
|
@@ -8,6 +8,13 @@ module Lab
|
|
|
8
8
|
result.children.map do |measure|
|
|
9
9
|
value, value_type = read_value(measure)
|
|
10
10
|
concept_name = ConceptName.find_by_concept_id(measure.concept_id)
|
|
11
|
+
|
|
12
|
+
program_id = ""
|
|
13
|
+
if measure.obs_id.present?
|
|
14
|
+
obs = Observation.find(measure.obs_id)
|
|
15
|
+
encounter = Encounter.find(obs.encounter_id)
|
|
16
|
+
program_id = encounter.program_id
|
|
17
|
+
end
|
|
11
18
|
|
|
12
19
|
{
|
|
13
20
|
id: measure.obs_id,
|
|
@@ -18,7 +25,8 @@ module Lab
|
|
|
18
25
|
date: measure.obs_datetime,
|
|
19
26
|
value:,
|
|
20
27
|
value_type:,
|
|
21
|
-
value_modifier: measure.value_modifier
|
|
28
|
+
value_modifier: measure.value_modifier,
|
|
29
|
+
program_id: program_id
|
|
22
30
|
}
|
|
23
31
|
end
|
|
24
32
|
end
|
|
@@ -41,8 +41,8 @@ module Lab
|
|
|
41
41
|
year = format_year(date.year)
|
|
42
42
|
month = format_month(date.month)
|
|
43
43
|
day = format_day(date.day)
|
|
44
|
-
|
|
45
|
-
"X#{site_code}#{year}#{month}#{day}#{counter}"
|
|
44
|
+
time_acc_generated = Time.now.strftime('%H%M')
|
|
45
|
+
"X#{site_code}#{year}#{month}#{day}#{counter}#{time_acc_generated}"
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def format_year(year)
|
|
@@ -23,9 +23,10 @@ module Lab
|
|
|
23
23
|
patient.family_name,
|
|
24
24
|
patient.given_name,
|
|
25
25
|
patient.gender,
|
|
26
|
-
|
|
26
|
+
drawer,
|
|
27
27
|
tests,
|
|
28
28
|
order.accession_number,
|
|
29
|
+
patient.arv,
|
|
29
30
|
number_of_copies
|
|
30
31
|
)
|
|
31
32
|
else
|
|
@@ -43,6 +44,7 @@ module Lab
|
|
|
43
44
|
reason_for_test,
|
|
44
45
|
order.accession_number,
|
|
45
46
|
order.accession_number,
|
|
47
|
+
patient.arv,
|
|
46
48
|
number_of_copies
|
|
47
49
|
)
|
|
48
50
|
end
|
|
@@ -76,27 +78,22 @@ module Lab
|
|
|
76
78
|
patient_identifier = PatientIdentifier.where(type: PatientIdentifierType.where(name: 'National id'),
|
|
77
79
|
patient_id: order.patient_id)
|
|
78
80
|
.first
|
|
79
|
-
|
|
81
|
+
arv_identifier = PatientIdentifier.where(type: PatientIdentifierType.where(name: 'ARV Number'),
|
|
82
|
+
patient_id: order.patient_id)
|
|
83
|
+
.first
|
|
80
84
|
@patient = OpenStruct.new(
|
|
81
85
|
given_name: person_name.given_name,
|
|
82
86
|
family_name: person_name.family_name,
|
|
83
87
|
birthdate: person.birthdate,
|
|
84
88
|
gender: person.gender,
|
|
85
|
-
nhid: patient_identifier&.identifier || 'Unknown'
|
|
89
|
+
nhid: patient_identifier&.identifier || 'Unknown',
|
|
90
|
+
arv: arv_identifier&.identifier || ''
|
|
86
91
|
)
|
|
87
92
|
end
|
|
88
93
|
|
|
89
94
|
def drawer
|
|
90
95
|
return 'N/A' if order.concept_id == unknown_concept.concept_id
|
|
91
|
-
|
|
92
|
-
drawer_id = User.find(order.discontinued_by || order.creator).person_id
|
|
93
|
-
draw_date = (order.discontinued_date || order.start_date).strftime('%d/%^b/%Y %H:%M:%S')
|
|
94
|
-
|
|
95
|
-
name = PersonName.find_by_person_id(drawer_id)
|
|
96
|
-
return "#{name.given_name} #{name.family_name} #{draw_date}" if name
|
|
97
|
-
|
|
98
|
-
user = User.find_by_user_id(drawer_id)
|
|
99
|
-
user ? "#{user.username} #{draw_date}" : 'N/A'
|
|
96
|
+
(order.discontinued_date || order.start_date).strftime('%d/%^b/%Y')
|
|
100
97
|
end
|
|
101
98
|
|
|
102
99
|
def drawer_date
|
|
@@ -23,7 +23,7 @@ module Lab
|
|
|
23
23
|
lims_api.consume_orders(from: last_seq, limit: batch_size, **) do |order_dto, context|
|
|
24
24
|
logger.debug("Retrieved order ##{order_dto[:tracking_number]}: #{order_dto}")
|
|
25
25
|
|
|
26
|
-
patient = find_patient_by_nhid(order_dto[:patient][:id])
|
|
26
|
+
patient = find_patient_by_nhid(order_dto[:patient][:id], order_dto[:tracking_number])
|
|
27
27
|
unless patient
|
|
28
28
|
logger.debug("Discarding order: Non local patient ##{order_dto[:patient][:id]} on order ##{order_dto[:tracking_number]}")
|
|
29
29
|
order_rejected(order_dto, "Patient NPID, '#{order_dto[:patient][:id]}', didn't match any local NPIDs")
|
|
@@ -58,7 +58,7 @@ module Lab
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def process_order(order_dto)
|
|
61
|
-
patient = find_patient_by_nhid(order_dto[:patient][:id])
|
|
61
|
+
patient = find_patient_by_nhid(order_dto[:patient][:id], order_dto[:tracking_number])
|
|
62
62
|
unless patient
|
|
63
63
|
logger.debug("Discarding order: Non local patient ##{order_dto[:patient][:id]} on order ##{order_dto[:tracking_number]}")
|
|
64
64
|
order_rejected(order_dto, "Patient NPID, '#{order_dto[:patient][:id]}', didn't match any local NPIDs")
|
|
@@ -107,7 +107,7 @@ module Lab
|
|
|
107
107
|
|
|
108
108
|
private
|
|
109
109
|
|
|
110
|
-
def find_patient_by_nhid(nhid)
|
|
110
|
+
def find_patient_by_nhid(nhid, accession_number)
|
|
111
111
|
national_id_type = PatientIdentifierType.where(name: ['National id', 'Old Identification Number'])
|
|
112
112
|
identifiers = PatientIdentifier.where(type: national_id_type, identifier: nhid)
|
|
113
113
|
.joins('INNER JOIN person ON person.person_id = patient_identifier.patient_id AND person.voided = 0')
|
|
@@ -126,10 +126,10 @@ module Lab
|
|
|
126
126
|
patients = Patient.where(patient_id: identifiers.select(:patient_id))
|
|
127
127
|
.distinct(:patient_id)
|
|
128
128
|
.all
|
|
129
|
+
order = Order.find_by(patient_id: patients.select(:patient_id), accession_number: accession_number)
|
|
130
|
+
raise Lab::Lims::LimsException, "Order #{accession_number} does not exists for patient #{nhid}" if order.nil?
|
|
129
131
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
patients.first
|
|
132
|
+
order.patient
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
##
|
|
@@ -13,7 +13,7 @@ module Lab
|
|
|
13
13
|
User.current = Utils.lab_user
|
|
14
14
|
|
|
15
15
|
fork(&method(:start_push_worker))
|
|
16
|
-
|
|
16
|
+
fork(&method(:start_pull_worker))
|
|
17
17
|
fork(&method(:start_acknowledgement_worker))
|
|
18
18
|
fork(&method(:start_realtime_pull_worker)) if realtime_updates_enabled?
|
|
19
19
|
|
|
@@ -160,6 +160,7 @@ module Lab
|
|
|
160
160
|
def create_rejection_notification(order_params)
|
|
161
161
|
order = find_order order_params['tracking_number']
|
|
162
162
|
data = { 'type': 'LIMS',
|
|
163
|
+
'specimen': ConceptName.find_by(concept_id: order.concept_id)&.name,
|
|
163
164
|
'accession_number': order&.accession_number,
|
|
164
165
|
'order_date': order&.start_date,
|
|
165
166
|
'arv_number': find_arv_number(order.patient_id),
|
|
@@ -226,8 +227,12 @@ module Lab
|
|
|
226
227
|
access_number = params[:accession_number] || next_accession_number(params[:date]&.to_date || Date.today)
|
|
227
228
|
raise 'Accession Number cannot be blank' unless access_number.present?
|
|
228
229
|
raise 'Accession cannot be this short' unless access_number.length > 6
|
|
230
|
+
|
|
231
|
+
order_type = nil
|
|
232
|
+
order_type = OrderType.find_by_order_type_id!(params[:order_type_id]) if params[:order_type_id].present?
|
|
233
|
+
|
|
229
234
|
Lab::LabOrder.create!(
|
|
230
|
-
order_type: OrderType.find_by_name!(Lab::Metadata::ORDER_TYPE_NAME),
|
|
235
|
+
order_type: order_type || OrderType.find_by_name!(Lab::Metadata::ORDER_TYPE_NAME),
|
|
231
236
|
concept_id: params.dig(:specimen, :concept_id) || unknown_concept_id,
|
|
232
237
|
encounter_id: encounter.encounter_id,
|
|
233
238
|
patient_id: encounter.patient_id,
|
|
@@ -32,17 +32,24 @@ module Lab
|
|
|
32
32
|
|
|
33
33
|
serializer = Lab::ResultSerializer.serialize(results_obs)
|
|
34
34
|
end
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
|
|
36
|
+
ProcessLabResultJob.perform_later(results_obs.id, serializer, result_enter_by)
|
|
37
|
+
|
|
37
38
|
Rails.logger.info("Lab::ResultsService: Result created for test #{test_id} #{serializer}")
|
|
38
39
|
serializer
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
def process_result_completion(results_obs, serializer, result_enter_by)
|
|
43
|
+
process_acknowledgement(results_obs, result_enter_by)
|
|
44
|
+
precess_notification_message(results_obs, serializer, result_enter_by)
|
|
45
|
+
end
|
|
46
|
+
|
|
41
47
|
private
|
|
42
48
|
|
|
43
49
|
def precess_notification_message(result, values, result_enter_by)
|
|
44
50
|
order = Order.find(result.order_id)
|
|
45
51
|
data = { Type: result_enter_by,
|
|
52
|
+
Specimen: ConceptName.find_by(concept_id: order.concept_id)&.name,
|
|
46
53
|
'Test type': ConceptName.find_by(concept_id: result.test.value_coded)&.name,
|
|
47
54
|
'Accession number': order&.accession_number,
|
|
48
55
|
'Orde date': order&.start_date,
|
data/lib/auto12epl.rb
CHANGED
|
@@ -96,7 +96,8 @@ class Auto12Epl
|
|
|
96
96
|
"#{last_name}, #{first_name}#{middle_initial.nil? ? '' : " #{middle_initial}"}"
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
def generate_small_specimen_label(last_name, first_name, gender, col_date_time, tests, acc_num, number_of_copies = print_copies)
|
|
99
|
+
def generate_small_specimen_label(last_name, first_name, gender, col_date_time, tests, acc_num, arv_number, number_of_copies = print_copies)
|
|
100
|
+
arv_display = tests.match?(/vl/i) ? arv_number : ''
|
|
100
101
|
<<~TEXT
|
|
101
102
|
N
|
|
102
103
|
R216,0
|
|
@@ -104,7 +105,7 @@ class Auto12Epl
|
|
|
104
105
|
S1
|
|
105
106
|
A100,6,0,1,1,1,N,"#{first_name}, #{last_name} - #{gender}"
|
|
106
107
|
B120,40,0,1A,1,2,48,N,"#{acc_num}"
|
|
107
|
-
A100,100,0,1,1,1,N,"#{acc_num}"
|
|
108
|
+
A100,100,0,1,1,1,N,"#{acc_num} #{arv_display}"
|
|
108
109
|
A100,118,0,1,1,1,N,"#{col_date_time}"
|
|
109
110
|
A100,140,0,1,1,1,N,"#{tests}"
|
|
110
111
|
P#{number_of_copies}
|
|
@@ -113,45 +114,22 @@ class Auto12Epl
|
|
|
113
114
|
|
|
114
115
|
# The main function to generate the EPL
|
|
115
116
|
def generate_epl(last_name, first_name, middle_initial, pid, dob, age, gender, col_date_time, col_name, tests, stat,
|
|
116
|
-
acc_num, schema_track, number_of_copies = print_copies)
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
tests_element_text = tests
|
|
133
|
-
|
|
134
|
-
# generate EPL statements
|
|
135
|
-
name_element = generate_ascii_element(to_dots(l_margin), to_dots(HEIGHT_MARGIN), 0, @element_font, false, name_text)
|
|
136
|
-
pid_dob_age_gender_element = generate_ascii_element(to_dots(l_margin),
|
|
137
|
-
to_dots(HEIGHT_MARGIN + HEIGHT_ELEMENT + HEIGHT_ELEMENT_SPACE), 0, @element_font, false, pid_dob_age_gender_text)
|
|
138
|
-
barcode_human_element = generate_ascii_element(to_dots(l_margin_barcode),
|
|
139
|
-
to_dots(HEIGHT_MARGIN + HEIGHT_ELEMENT + HEIGHT_ELEMENT_SPACE + HEIGHT_ELEMENT + HEIGHT_ELEMENT_SPACE + HEIGHT_BARCODE), 0, @barcode_human_font, false, barcode_human_text)
|
|
140
|
-
collector_element = generate_ascii_element(to_dots(l_margin),
|
|
141
|
-
to_dots(HEIGHT_MARGIN + HEIGHT_ELEMENT + HEIGHT_ELEMENT_SPACE + HEIGHT_ELEMENT + HEIGHT_ELEMENT_SPACE + HEIGHT_BARCODE + HEIGHT_BARCODE_HUMAN + HEIGHT_ELEMENT_SPACE), 0, @element_font, false, collector_element_text)
|
|
142
|
-
tests_element = generate_ascii_element(to_dots(l_margin),
|
|
143
|
-
to_dots(HEIGHT_MARGIN + HEIGHT_ELEMENT + HEIGHT_ELEMENT_SPACE + HEIGHT_ELEMENT + HEIGHT_ELEMENT_SPACE + HEIGHT_BARCODE + HEIGHT_BARCODE_HUMAN + HEIGHT_ELEMENT_SPACE + HEIGHT_ELEMENT + HEIGHT_ELEMENT_SPACE), 0, @element_font, false, tests_element_text)
|
|
144
|
-
barcode_element = generate_barcode_element(to_dots(l_margin_barcode),
|
|
145
|
-
to_dots(HEIGHT_MARGIN + HEIGHT_ELEMENT + HEIGHT_ELEMENT_SPACE + HEIGHT_ELEMENT + HEIGHT_ELEMENT_SPACE), to_dots(HEIGHT_BARCODE) - 4, schema_track)
|
|
146
|
-
stat_element = generate_ascii_element(to_dots(L_MARGIN) + FONT_Y_DOTS.at(@element_font - 1) + FONT_PAD_DOTS,
|
|
147
|
-
to_dots(HEIGHT_MARGIN), 1, @element_font, true, stat_element_text)
|
|
148
|
-
|
|
149
|
-
# combine EPL statements
|
|
150
|
-
if stat.nil?
|
|
151
|
-
"\nN\nR216,0\nZT\nS1\n#{name_element}\n#{pid_dob_age_gender_element}\n#{barcode_element}\n#{barcode_human_element}\n#{collector_element}\n#{tests_element}\nP#{number_of_copies}\n"
|
|
152
|
-
else
|
|
153
|
-
"\nN\nR216,0\nZT\nS1\n#{name_element}\n#{pid_dob_age_gender_element}\n#{barcode_element}\n#{barcode_human_element}\n#{collector_element}\n#{tests_element}\n#{stat_element}\nP#{number_of_copies}\n"
|
|
154
|
-
end
|
|
117
|
+
acc_num, schema_track, arv_number, number_of_copies = print_copies)
|
|
118
|
+
# Show ARV number only if test contains 'vl' (case insensitive)
|
|
119
|
+
arv_display = tests.match?(/vl/i) ? arv_number : ''
|
|
120
|
+
|
|
121
|
+
<<~TEXT
|
|
122
|
+
N
|
|
123
|
+
R130,0
|
|
124
|
+
ZT
|
|
125
|
+
S1
|
|
126
|
+
A100,6,0,1,1,1,N,"#{last_name} #{first_name} (#{age})"
|
|
127
|
+
B100,30,0,1A,2,2,37,N,"#{acc_num}"
|
|
128
|
+
A100,80,0,1,1,1,N,"#{acc_num} #{arv_display}"
|
|
129
|
+
A100,100,0,1,1,1,N,"#{col_date_time} #{tests}"
|
|
130
|
+
A80,6,1,1,1,1,R," #{stat} "
|
|
131
|
+
P#{number_of_copies}
|
|
132
|
+
TEXT
|
|
155
133
|
end
|
|
156
134
|
|
|
157
135
|
# Add spaces before and after the stat text so that black bars appear across the left edge of label
|
data/lib/lab/version.rb
CHANGED
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: 2.
|
|
4
|
+
version: 2.1.1
|
|
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:
|
|
11
|
+
date: 2026-01-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: couchrest
|
|
@@ -243,6 +243,7 @@ files:
|
|
|
243
243
|
- app/controllers/lab/tests_controller.rb
|
|
244
244
|
- app/controllers/lab/users_controller.rb
|
|
245
245
|
- app/jobs/lab/application_job.rb
|
|
246
|
+
- app/jobs/lab/process_lab_result_job.rb
|
|
246
247
|
- app/jobs/lab/push_order_job.rb
|
|
247
248
|
- app/jobs/lab/update_patient_orders_job.rb
|
|
248
249
|
- app/jobs/lab/void_order_job.rb
|