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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f0e2b1fd0f7372571d684a23a529651631e55a271d1355aa1d12f8b7d2a85d5
4
- data.tar.gz: 6509d471a8f57e1108430e3d6e08a4308b5bca2e0eb386d1088534f0c0863856
3
+ metadata.gz: 0ea18a06a50c6c0dfaded6e6911e0f9603cf2f9b8d1c4e81a476da3d8b8aba29
4
+ data.tar.gz: b0991a42ac153666de1d4ce358226134e9804cfd93ba5a749c8030f364d5c2aa
5
5
  SHA512:
6
- metadata.gz: 2500b6aa2b1cc75bc8476a4ed68576faf0156cc7f836b7fd3039606db135c05672f957fe4188d69f994b9fc54c952fb39974dcd398dee0376af32e78b5f9f0ab
7
- data.tar.gz: f74c867864d8c77e8b73ec31b8ac0dd82d5b6d77d4c6152d32cbf3c010e909d24c844839153d4670f14bcfaa9db1e45de6f5d767176d70b262321eec8603b9f4
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
 
@@ -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).order(date_created: :asc)
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: Lab::LimsOrderMapping.select(: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).pluck(:concept_id)
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
- # Save order status trail if available
184
- save_order_status_trail(order, order_params) if order_params['status']
185
- end
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lab
4
- VERSION = '2.4.9'.freeze
4
+ VERSION = '2.5.0'.freeze
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.4.9
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-10-13 00:00:00.000000000 Z
11
+ date: 2026-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: couchrest