his_emr_api_lab 2.4.9 → 2.5.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/app/jobs/lab/update_patient_orders_job.rb +25 -1
- data/app/models/lab/lab_order.rb +5 -2
- data/app/services/lab/lims/api/rest_api.rb +14 -1
- data/app/services/lab/order_location_resolver.rb +4 -1
- data/app/services/lab/orders_service.rb +5 -23
- data/lib/lab/version.rb +1 -1
- 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: 0ea18a06a50c6c0dfaded6e6911e0f9603cf2f9b8d1c4e81a476da3d8b8aba29
|
|
4
|
+
data.tar.gz: b0991a42ac153666de1d4ce358226134e9804cfd93ba5a749c8030f364d5c2aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c39da38186a7c15671704fb88981c4b8d76be7acddad44426b77c0b30ea88245ca4887d2cb79a787c04b4d7cf127498497be651c36ba25fe41b45490eeebff86
|
|
7
|
+
data.tar.gz: 8c9469a31a9c6f5b82e7985b628f288178e7aaf9d6f4e4f24de70182267e23fad71659c2afc3e5308b33ed41ad89c48e2ec5dd3fde1eda4b1277712754264c7a
|
|
@@ -6,7 +6,17 @@ module Lab
|
|
|
6
6
|
class UpdatePatientOrdersJob < ApplicationJob
|
|
7
7
|
queue_as :default
|
|
8
8
|
|
|
9
|
+
# Opening a patient's lab orders or tests queues this job, so a busy
|
|
10
|
+
# patient page would otherwise sync with NLIMS on every request.
|
|
11
|
+
MIN_INTERVAL_BETWEEN_RUNS = 5.minutes
|
|
12
|
+
|
|
13
|
+
# Only orders from this window are checked; older orders are left to the
|
|
14
|
+
# sync worker (bin/lab/sync_worker.rb uses the same window).
|
|
15
|
+
SYNC_WINDOW = 6.months
|
|
16
|
+
|
|
9
17
|
def perform(patient_id)
|
|
18
|
+
return if ran_recently?(patient_id)
|
|
19
|
+
|
|
10
20
|
Rails.logger.info('Initialising LIMS REST API...')
|
|
11
21
|
|
|
12
22
|
User.current = Lab::Lims::Utils.lab_user
|
|
@@ -21,7 +31,7 @@ module Lab
|
|
|
21
31
|
break false
|
|
22
32
|
end
|
|
23
33
|
|
|
24
|
-
worker = Lab::Lims::PullWorker.new(Lab::Lims::ApiFactory.create_api)
|
|
34
|
+
worker = Lab::Lims::PullWorker.new(Lab::Lims::ApiFactory.create_api, start_date: SYNC_WINDOW.ago)
|
|
25
35
|
worker.pull_orders(patient_id:)
|
|
26
36
|
|
|
27
37
|
true
|
|
@@ -32,6 +42,20 @@ module Lab
|
|
|
32
42
|
|
|
33
43
|
private
|
|
34
44
|
|
|
45
|
+
# Uses a file's mtime rather than Rails.cache so all Puma workers see it.
|
|
46
|
+
# The file is touched before the sync starts, so a burst of requests for
|
|
47
|
+
# the same patient results in a single sync.
|
|
48
|
+
def ran_recently?(patient_id)
|
|
49
|
+
stamp = Rails.root.join('tmp', "update-patient-orders-#{patient_id}.last_run")
|
|
50
|
+
if File.exist?(stamp) && File.mtime(stamp) > MIN_INTERVAL_BETWEEN_RUNS.ago
|
|
51
|
+
Rails.logger.info("Skipping LIMS update for patient #{patient_id}: last run at #{File.mtime(stamp)} and next run not due until #{File.mtime(stamp) + MIN_INTERVAL_BETWEEN_RUNS}")
|
|
52
|
+
return true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
FileUtils.touch(stamp)
|
|
56
|
+
false
|
|
57
|
+
end
|
|
58
|
+
|
|
35
59
|
def set_location_from_patient_encounter(patient_id)
|
|
36
60
|
Rails.logger.info("Setting location context for patient #{patient_id}")
|
|
37
61
|
|
data/app/models/lab/lab_order.rb
CHANGED
|
@@ -49,10 +49,13 @@ module Lab
|
|
|
49
49
|
class_name: '::Lab::LimsOrderMapping',
|
|
50
50
|
foreign_key: :order_id
|
|
51
51
|
|
|
52
|
-
# Status trails are stored as observations with concept 'Lab Order Status'
|
|
52
|
+
# Status trails are stored as observations with concept 'Lab Order Status'.
|
|
53
|
+
# Ordered by when the status happened, not when it was saved: the pull
|
|
54
|
+
# worker can save an earlier NLIMS status after a later one was pushed.
|
|
53
55
|
has_many :status_trail_observations,
|
|
54
56
|
lambda {
|
|
55
|
-
unscoped.where(voided: 0, concept_id: Lab::LabOrder.order_status_concept_id)
|
|
57
|
+
unscoped.where(voided: 0, concept_id: Lab::LabOrder.order_status_concept_id)
|
|
58
|
+
.order(obs_datetime: :asc, obs_id: :asc)
|
|
56
59
|
},
|
|
57
60
|
class_name: 'Observation',
|
|
58
61
|
foreign_key: :order_id
|
|
@@ -11,6 +11,11 @@ module Lab
|
|
|
11
11
|
class InvalidParameters < LimsApiError; end
|
|
12
12
|
|
|
13
13
|
START_DATE = Time.parse('2024-09-03').freeze
|
|
14
|
+
|
|
15
|
+
# How long to wait before asking NLIMS again about an order that still
|
|
16
|
+
# has no results. Orders that never get results (rejected or abandoned
|
|
17
|
+
# samples) would otherwise be fetched from NLIMS on every pull.
|
|
18
|
+
RESULTS_RECHECK_INTERVAL = 30.minutes
|
|
14
19
|
|
|
15
20
|
def initialize(config)
|
|
16
21
|
@config = config
|
|
@@ -632,12 +637,20 @@ module Lab
|
|
|
632
637
|
# Lab::OrdersSearchService.find_orders_without_results(patient_id: patient_id)
|
|
633
638
|
# .where.not(accession_number: Lab::LimsOrderMapping.select(:lims_id).where("pulled_at IS NULL"))
|
|
634
639
|
orders = Lab::OrdersSearchService.find_orders_without_results(patient_id:)
|
|
635
|
-
.where(order_id:
|
|
640
|
+
.where(order_id: mappings_due_for_results_check(accession_numbers).select(:order_id))
|
|
636
641
|
orders = orders.where('orders.date_created >= ?', start_date) if start_date
|
|
637
642
|
orders = orders.where(accession_number: accession_numbers) if accession_numbers.any?
|
|
638
643
|
orders
|
|
639
644
|
end
|
|
640
645
|
|
|
646
|
+
# Skips orders pulled within RESULTS_RECHECK_INTERVAL, unless specific
|
|
647
|
+
# accession numbers were asked for (a manual re-sync).
|
|
648
|
+
def mappings_due_for_results_check(accession_numbers)
|
|
649
|
+
return Lab::LimsOrderMapping.all if accession_numbers.any?
|
|
650
|
+
|
|
651
|
+
Lab::LimsOrderMapping.where('pulled_at IS NULL OR pulled_at < ?', RESULTS_RECHECK_INTERVAL.ago)
|
|
652
|
+
end
|
|
653
|
+
|
|
641
654
|
def orders_without_reason(patient_id = nil, start_date: nil, accession_numbers: [])
|
|
642
655
|
Rails.logger.debug('Looking for orders without a reason for test')
|
|
643
656
|
orders = Lab::LabOrder.joins(:reason_for_test)
|
|
@@ -97,8 +97,11 @@ module Lab
|
|
|
97
97
|
scope.order(Arel.sql("CASE WHEN concept_id IN (#{quoted_ids}) THEN 0 ELSE 1 END, date_created ASC, obs_id ASC"))
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
# Cached like the concept IDs in Lab::LabTest; empty results are not cached
|
|
101
|
+
# so a missing concept is picked up once it is added.
|
|
100
102
|
def order_location_concept_ids
|
|
101
|
-
ConceptName.where(name: ORDER_OBSERVATION_CONCEPT_NAMES)
|
|
103
|
+
@order_location_concept_ids ||= ConceptName.where(name: ORDER_OBSERVATION_CONCEPT_NAMES)
|
|
104
|
+
.pluck(:concept_id).presence
|
|
102
105
|
end
|
|
103
106
|
|
|
104
107
|
def encounter_location_id_for_record(record)
|
|
@@ -165,24 +165,12 @@ module Lab
|
|
|
165
165
|
def update_order_status(order_params)
|
|
166
166
|
# find the order
|
|
167
167
|
order = find_order(order_params['tracking_number'])
|
|
168
|
-
concept = ConceptName.find_by_name Lab::Metadata::LAB_ORDER_STATUS_CONCEPT_NAME
|
|
169
|
-
location_id = Lab::OrderLocationResolver.location_id_for_order(order)
|
|
170
|
-
ActiveRecord::Base.transaction do
|
|
171
|
-
void_order_status(order, concept)
|
|
172
|
-
Observation.create!(
|
|
173
|
-
person_id: order.patient_id,
|
|
174
|
-
encounter_id: order.encounter_id,
|
|
175
|
-
concept_id: concept.concept_id,
|
|
176
|
-
order_id: order.id,
|
|
177
|
-
obs_datetime: order_params['status_time'] || Time.now,
|
|
178
|
-
value_text: order_params['status'],
|
|
179
|
-
creator: User.current.id,
|
|
180
|
-
location_id:
|
|
181
|
-
)
|
|
182
168
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
169
|
+
# Statuses form the order's trail and the newest one is the current
|
|
170
|
+
# status, so earlier ones are kept. Voiding them here made the pull
|
|
171
|
+
# worker re-create the whole trail on its next run, and the two kept
|
|
172
|
+
# adding copies of the same statuses.
|
|
173
|
+
save_order_status_trail(order, order_params) if order_params['status']
|
|
186
174
|
create_rejection_notification(order_params) if order_params['status'] == 'test-rejected'
|
|
187
175
|
|
|
188
176
|
# Publish notification that order status has changed
|
|
@@ -499,12 +487,6 @@ module Lab
|
|
|
499
487
|
add_reason_for_test(order, date: date, reason_for_test_id: concept_id)
|
|
500
488
|
end
|
|
501
489
|
|
|
502
|
-
def void_order_status(order, concept)
|
|
503
|
-
Observation.unscoped.where(order_id: order.id, concept_id: concept.concept_id).each do |obs|
|
|
504
|
-
obs.void('New Status Received from LIMS')
|
|
505
|
-
end
|
|
506
|
-
end
|
|
507
|
-
|
|
508
490
|
def location_id_from_order_params(order_params)
|
|
509
491
|
order_params[:location_id] ||
|
|
510
492
|
order_params[:order_location_id] ||
|
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.5.0
|
|
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: 2026-
|
|
11
|
+
date: 2026-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: couchrest
|