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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 306fe47c9b027c61e15e1bd6fe2cee372f08e084acc06375f3a1dcbdf79be1bd
4
- data.tar.gz: 8ac31940e00893ffcc554e721c12aa957017c7ab8fc7db69c231830eb063595e
3
+ metadata.gz: 1c80aacd232547335da6ecc016e3dd846f4f10f5490653db6ad877c21fc4eb3e
4
+ data.tar.gz: 4ddb8bf35b0c011f9e85c7167c1b3f6e551933d0f697123771ce007ea728a74e
5
5
  SHA512:
6
- metadata.gz: 28b4c75bed9b3da3c47e0c19e717b51606e3ff1212b5c07d9c9e78cd29496d3b18299e0ce394b8491847faf6dc4c3e9eaf7f51e6f3836a7d957dd4f260292aea
7
- data.tar.gz: d9e847ea4fd3c2725ac69222d4e0e1ef8398d06baba9112c2988c0894faeff778eb866ac10a44af3f9a7b9ba9a966c0f90d5f0c704bc0c7a1ff3ce345d238a01
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
@@ -41,7 +41,10 @@ module Lab
41
41
 
42
42
  default_scope do
43
43
  joins(:order_type)
44
- .merge(OrderType.where(name: Lab::Metadata::ORDER_TYPE_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
- drawer_date,
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
- raise Lab::Lims::DuplicateNHID, "Duplicate National Health ID: #{nhid}" if patients.size > 1
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
- # fork(&method(:start_pull_worker))
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
 
@@ -18,6 +18,7 @@ module Lab
18
18
 
19
19
  # Order types
20
20
  ORDER_TYPE_NAME = 'Lab'
21
+ HTS_ORDER_TYPE_NAME = 'HTS Lab'
21
22
 
22
23
  # Programs
23
24
  LAB_PROGRAM_NAME = 'Laboratory Program'
@@ -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
- process_acknowledgement(results_obs, result_enter_by)
36
- precess_notification_message(results_obs, serializer, result_enter_by)
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
- # format text and set margin
118
- if stat.nil?
119
- name_text = truncate_name(last_name, first_name, middle_initial, false)
120
- pid_dob_age_gender_text = full_justify(pid, "#{dob} #{age} #{gender}", @element_font, WIDTH_ELEMENT)
121
- l_margin = L_MARGIN
122
- l_margin_barcode = L_MARGIN_BARCODE
123
- else
124
- name_text = truncate_name(last_name, first_name, middle_initial, true)
125
- pid_dob_age_gender_text = full_justify(pid, "#{dob} #{age} #{gender}", @element_font, STAT_WIDTH_ELEMENT)
126
- stat_element_text = pad_stat_w_space(stat)
127
- l_margin = L_MARGIN_W_STAT
128
- l_margin_barcode = L_MARGIN_BARCODE_W_STAT
129
- end
130
- barcode_human_text = "#{acc_num} * #{schema_track.gsub(/-/i, '')}"
131
- collector_element_text = "Col: #{col_date_time} #{col_name}"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lab
4
- VERSION = '2.0.9'
4
+ VERSION = '2.1.1'
5
5
  end
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.0.9
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: 2025-10-02 00:00:00.000000000 Z
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