his_emr_api_lab 2.4.7 → 2.4.8

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: e025ec511241c94de0fe3a31ccc589159953af60258181036094f3be7d44c409
4
- data.tar.gz: 4061c96e3e6ce5017a2324ed93211a28b2b3b7bc374e1c3e78bd40ccd57e5b95
3
+ metadata.gz: 9f5de1660061373145b1d5de0080384f472c305659f23e599901cb72267134c9
4
+ data.tar.gz: a2689968ec069c8759d8c14a36ab767e124d8c042e43e7ecf3d10a20e6b01f1b
5
5
  SHA512:
6
- metadata.gz: 3b3d09afe97f56a907b6f71f1d1de58b7a9329de5cbcc19ab8fc1a67bb56d057193cf82cd663f25a1061b18937d2930020b682f8bf63b6908952b88b3c9b436f
7
- data.tar.gz: fbd1ab31395c8f1cca74f76bc075029f88da367cdc09fe7d295332524018d937f017dc78d75e0bd29d58ebbf0d9575027de3a68e5a771362a2582852804cf4af
6
+ metadata.gz: 0a172df300261d1828cda8d4942873ff3b5ce2d5607e854323ec9f5f9d18d73299e5c0e75488110f2a977b421d15f374342f141ec1833987f6c5de9222539084
7
+ data.tar.gz: 209dceff50ab86215a5c56b168568fbfb9c3d00d7b0ff0f035c25822d0b3ce84387d56448f5c084f7b2078b9a8a10ebb4efce649fee55a7e5fe5e94b862def60
@@ -22,8 +22,8 @@ module Lab
22
22
 
23
23
  def update
24
24
  specimen = params.require(:specimen).slice(:concept_id)
25
- order = OrdersService.update_order(params[:id], specimen:, force_update: params[:force_update])
26
-
25
+ order = OrdersService.update_order(params[:id], specimen:, force_update: params[:force_update], date: params[:date])
26
+
27
27
  begin
28
28
  Lab::PushOrderJob.perform_later(order.fetch(:order_id))
29
29
  rescue StandardError => e
@@ -10,7 +10,7 @@ module Lab
10
10
 
11
11
  # Add a specimen to an existing order
12
12
  def create
13
- test_params = params.slice(:order_id, :date, tests: [:concept_id])
13
+ test_params = params.permit(:order_id, :date, tests: %i[concept_id concept])
14
14
  order_id, test_concepts = test_params.require(%i[order_id tests])
15
15
  date = test_params[:date] || Date.today
16
16
 
@@ -66,6 +66,10 @@ module Lab
66
66
  .where.not(concept_id: ConceptName.where(name: 'Tests ordered').select(:concept_id))
67
67
  end
68
68
 
69
+ # Only checked when start_date is set or changed, so existing future-dated
70
+ # orders can still be voided or updated during clean-up.
71
+ validate :start_date_cannot_be_in_the_future, if: :will_save_change_to_start_date?
72
+
69
73
  scope :drawn, -> { where.not(concept_id: ConceptName.where(name: 'Unknown').select(:concept_id)) }
70
74
  scope :not_drawn, -> { where(concept_id: ConceptName.where(name: 'Unknown').select(:concept_id)) }
71
75
 
@@ -78,5 +82,13 @@ module Lab
78
82
  :comment_to_fulfiller,
79
83
  :tests)
80
84
  end
85
+
86
+ # Allows up to tomorrow to cover timezone differences, matching
87
+ # Lab::Lims::Utils.parse_date.
88
+ def start_date_cannot_be_in_the_future
89
+ return if start_date.blank? || start_date.to_date <= Date.current + 1.day
90
+
91
+ errors.add(:start_date, 'cannot be in the future')
92
+ end
81
93
  end
82
94
  end
@@ -9,6 +9,7 @@ module Lab
9
9
  class UnknownSpecimenType < LimsException; end
10
10
  class UnknownTestType < LimsException; end
11
11
  class ValidationUnavailable < LimsException; end
12
+ class InvalidDate < LimsException; end
12
13
  end
13
14
  end
14
15
  end
@@ -21,5 +22,6 @@ module Lab
21
22
  class UnknownSpecimenType < LimsException; end
22
23
  class UnknownTestType < LimsException; end
23
24
  class ValidationUnavailable < LimsException; end
25
+ class InvalidDate < LimsException; end
24
26
  end
25
27
  end
@@ -12,6 +12,8 @@ module Lab
12
12
  include Utils # for logger
13
13
 
14
14
  LIMS_LOG_PATH = Rails.root.join('log', 'lims')
15
+ UNMAPPED_ORDER_REASON = 'Order not mapped to LIMS: pull worker does not create orders'
16
+ DECEASED_PATIENT_REASON = "Order dated after patient's death"
15
17
 
16
18
  def initialize(lims_api, start_date: nil, accession_numbers: [], patient_id: nil)
17
19
  @lims_api = lims_api
@@ -171,47 +173,84 @@ module Lab
171
173
  name1.casecmp?(name2)
172
174
  end
173
175
 
176
+ ##
177
+ # Applies a LIMS order to the local order it is mapped to.
178
+ #
179
+ # Orders are only ever created in the EMR, never by the pull worker.
180
+ # find_patient_by_nhid only lets an order through when a local order with
181
+ # the same accession number already exists, so creating one here would
182
+ # always produce a duplicate. Orders without a LIMS mapping (e.g. legacy
183
+ # orders, or new orders the push worker has not sent yet) are skipped and
184
+ # recorded once in lab_lims_failed_imports for review. So are orders
185
+ # dated after the patient's death.
186
+ #
187
+ # Returns the updated order, or nil if the order was skipped.
174
188
  def save_order(patient, order_dto)
175
189
  raise MissingAccessionNumber if order_dto[:tracking_number].blank?
176
190
 
177
191
  logger.info("Importing LIMS order ##{order_dto[:tracking_number]}")
192
+
193
+ death_date = death_date_before_order(patient, order_dto)
194
+ if death_date
195
+ order_date = lims_order_date(order_dto)
196
+ logger.warn("Not importing order ##{order_dto[:tracking_number]}: dated #{order_date}, after patient's death on #{death_date}")
197
+ record_skipped_order(order_dto, DECEASED_PATIENT_REASON, { order_date:, death_date: })
198
+ return nil
199
+ end
200
+
178
201
  mapping = find_order_mapping_by_lims_id(order_dto[:_id])
179
202
 
203
+ unless mapping
204
+ logger.warn("Not creating order ##{order_dto[:tracking_number]} from LIMS: no LIMS mapping for local order")
205
+ record_skipped_order(order_dto, UNMAPPED_ORDER_REASON)
206
+ return nil
207
+ end
208
+
180
209
  ActiveRecord::Base.transaction do
181
- if mapping
182
- order = update_order(patient, mapping.order_id, order_dto)
183
- mapping.update(pulled_at: Time.now)
184
- else
185
- order = create_order(patient, order_dto)
186
- mapping = LimsOrderMapping.create(lims_id: order_dto[:_id],
187
- order_id: order['id'],
188
- pulled_at: Time.now,
189
- revision: order_dto['_rev'])
190
- end
210
+ order = update_order(patient, mapping.order_id, order_dto)
211
+ mapping.update(pulled_at: Time.now)
191
212
 
192
213
  order
193
214
  end
194
215
  end
195
216
 
196
- def create_order(patient, order_dto)
197
- logger.debug("Creating order ##{order_dto['_id']}")
198
- params = order_dto.to_order_service_params(patient_id: patient.patient_id)
199
- params[:location_id] ||= location_id_for_order_dto(order_dto)
200
- order = OrdersService.order_test(params)
217
+ ##
218
+ # Returns the patient's death date if the order is dated after it, else nil.
219
+ #
220
+ # Orders dated on or before the death date are still imported, so results
221
+ # for samples drawn before death keep arriving.
222
+ def death_date_before_order(patient, order_dto)
223
+ person = Person.unscoped.find_by(person_id: patient.patient_id)
224
+ return nil unless person && ActiveModel::Type::Boolean.new.cast(person.dead) && person.death_date
225
+
226
+ death_date = person.death_date.to_date
227
+ death_date if lims_order_date(order_dto) > death_date
228
+ end
201
229
 
202
- # Extract and save status trails from NLIMS
203
- save_status_trails_from_nlims(order, order_dto)
230
+ def lims_order_date(order_dto)
231
+ raise LimsException, 'Order missing created date' if order_dto[:date_created].blank?
204
232
 
205
- # Update results if present
206
- update_results(order, order_dto['test_results']) unless order_dto['test_results'].empty?
233
+ Utils.parse_date(order_dto[:date_created]).to_date
234
+ end
207
235
 
208
- order
236
+ # The REST pull re-selects skipped orders on every run, so each one is
237
+ # recorded only once instead of adding a row per run.
238
+ def record_skipped_order(order_dto, reason, diff = nil)
239
+ LimsFailedImport.find_or_create_by!(lims_id: order_dto[:_id],
240
+ tracking_number: order_dto[:tracking_number],
241
+ reason:) do |failed_import|
242
+ failed_import.patient_nhid = order_dto.dig(:patient, :id)
243
+ failed_import.diff = diff&.to_json
244
+ end
245
+ rescue StandardError => e
246
+ logger.error("Failed to record skipped order ##{order_dto[:tracking_number]}: #{e.message}")
209
247
  end
210
248
 
211
249
  def update_order(patient, order_id, order_dto)
212
250
  logger.debug("Updating order ##{order_dto['_id']}")
213
251
  params = order_dto.to_order_service_params(patient_id: patient.patient_id)
214
252
  params[:location_id] ||= Lab::OrderLocationResolver.location_id_for_order_id(order_id, facility_name: order_dto[:sending_facility])
253
+ keep_local_specimen(order_id, params) unless specimen_update_allowed?(order_id)
215
254
  order = OrdersService.update_order(order_id, params.merge(force_update: 'true'))
216
255
 
217
256
  # Extract and save status trails from NLIMS
@@ -223,6 +262,38 @@ module Lab
223
262
  order
224
263
  end
225
264
 
265
+ ##
266
+ # LIMS may only set the specimen of an order whose sample has not been
267
+ # drawn yet (specimen 'Unknown') and that has no results. Any other order
268
+ # keeps its local specimen, so a sync can never change or discontinue an
269
+ # order that has already been drawn or resulted.
270
+ def specimen_update_allowed?(order_id)
271
+ order = Lab::LabOrder.unscoped.find(order_id)
272
+
273
+ order.concept_id == unknown_specimen_concept_id && !order_has_results?(order_id)
274
+ end
275
+
276
+ def keep_local_specimen(order_id, params)
277
+ local_specimen_id = Lab::LabOrder.unscoped.find(order_id).concept_id
278
+ lims_specimen_id = params.dig(:specimen, :concept_id)
279
+
280
+ if lims_specimen_id.present? && lims_specimen_id.to_i != local_specimen_id
281
+ logger.info("Keeping local specimen ##{local_specimen_id} on order ##{order_id}, ignoring LIMS specimen ##{lims_specimen_id}")
282
+ end
283
+
284
+ params[:specimen] = { concept_id: local_specimen_id }
285
+ end
286
+
287
+ def unknown_specimen_concept_id
288
+ @unknown_specimen_concept_id ||= ConceptName.find_by(name: Lab::Metadata::UNKNOWN_SPECIMEN)&.concept_id
289
+ end
290
+
291
+ def order_has_results?(order_id)
292
+ result_concept = ConceptName.where(name: Lab::Metadata::TEST_RESULT_CONCEPT_NAME).select(:concept_id)
293
+
294
+ Observation.unscoped.where(order_id:, concept_id: result_concept, voided: 0).exists?
295
+ end
296
+
226
297
  def update_results(order, lims_results)
227
298
  logger.debug("Updating results for order ##{order[:accession_number]}: #{lims_results}")
228
299
 
@@ -266,12 +337,6 @@ module Lab
266
337
  LabTest.unscoped.find_by(order_id:, value_coded: test_concept.concept_id, voided: 0)
267
338
  end
268
339
 
269
- def location_id_for_order_dto(order_dto)
270
- local_order = Lab::LabOrder.unscoped.find_by(accession_number: order_dto[:tracking_number])
271
-
272
- Lab::OrderLocationResolver.location_id_for_order(local_order, facility_name: order_dto[:sending_facility])
273
- end
274
-
275
340
  def find_measure(_order, indicator_name, value)
276
341
  indicator = Utils.find_concept_by_name(indicator_name)
277
342
  unless indicator
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'cgi/util'
4
4
 
5
+ require_relative 'exceptions'
6
+
5
7
  module Lab
6
8
  module Lims
7
9
  ##
@@ -54,6 +56,16 @@ module Lab
54
56
  User.create!(username: 'lab_daemon', person:, creator: god_user.user_id)
55
57
  end
56
58
 
59
+ # Earliest date accepted from LIMS. Anything older is a corrupted
60
+ # legacy date (e.g. year 0026 or 18xx), not a real lab order.
61
+ EARLIEST_VALID_DATE = Date.new(2000, 1, 1)
62
+
63
+ ##
64
+ # Parses a LIMS date, accepting only real calendar dates with a
65
+ # four-digit year between EARLIEST_VALID_DATE and tomorrow (to allow for
66
+ # timezone differences). Invalid dates are never guessed at or rewritten:
67
+ # the fallback date is used if one is given, otherwise InvalidDate is
68
+ # raised.
57
69
  def self.parse_date(str_date, fallback_date = nil)
58
70
  str_date = str_date&.to_s
59
71
 
@@ -61,21 +73,35 @@ module Lab
61
73
 
62
74
  return parse_date(fallback_date) if str_date.blank?
63
75
 
64
- str_date = str_date.gsub(/^00/, '20').gsub(/^180/, '20')
65
-
66
- case str_date
67
- when /\d{4}-\d{2}-\d{2}/
68
- str_date
69
- when /\d{2}-\d{2}-\d{2}/
70
- Date.strptime(str_date, '%d-%m-%Y').strftime('%Y-%m-%d')
71
- when /(\d{4}\d{2}\d{2})\d+/
72
- Date.strptime(str_date, '%Y%m%d').strftime('%Y-%m-%d')
73
- when %r{\d{2}/\d{2}/\d{4}}
74
- str_date.to_date.to_s
75
- else
76
- Rails.logger.warn("Invalid date: #{str_date}")
77
- parse_date(fallback_date)
76
+ date = begin
77
+ case str_date
78
+ when /\d{4}-\d{2}-\d{2}/
79
+ str_date
80
+ when /\d{2}-\d{2}-\d{4}/
81
+ Date.strptime(str_date, '%d-%m-%Y').strftime('%Y-%m-%d')
82
+ when /(\d{4}\d{2}\d{2})\d+/
83
+ Date.strptime(str_date, '%Y%m%d').strftime('%Y-%m-%d')
84
+ when %r{\d{2}/\d{2}/\d{4}}
85
+ str_date.to_date.to_s
86
+ end
87
+ rescue Date::Error, ArgumentError
88
+ nil
78
89
  end
90
+
91
+ return date if valid_date?(date)
92
+
93
+ Rails.logger.warn("Invalid date: #{str_date}")
94
+ raise InvalidDate, "Invalid date: #{str_date}" if fallback_date.blank?
95
+
96
+ parse_date(fallback_date)
97
+ end
98
+
99
+ def self.valid_date?(date)
100
+ return false if date.blank?
101
+
102
+ date.to_date.between?(EARLIEST_VALID_DATE, Date.current + 1.day)
103
+ rescue Date::Error, ArgumentError
104
+ false
79
105
  end
80
106
 
81
107
  def self.find_concept_by_name(name)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'order_location_resolver'
4
+ require_relative 'lims/exceptions'
4
5
 
5
6
  module Lab
6
7
  ##
@@ -248,7 +249,7 @@ module Lab
248
249
  orders = Lab::LabOrder.where(order_id: tests.pluck(:order_id))
249
250
  data = {
250
251
  count: orders.count,
251
- last_order_date: Lab::LabOrder.last&.start_date&.to_date,
252
+ last_order_date: last_order_date(location_id),
252
253
  lab_orders: []
253
254
  }
254
255
  if (include_data.present? && include_data == 'true')
@@ -265,6 +266,19 @@ module Lab
265
266
 
266
267
  private
267
268
 
269
+ # Without a location this is the latest lab order overall (unchanged behaviour). With a location
270
+ # it is the latest lab order whose tests were drawn there, so a central EMR serving many sites
271
+ # reports each site's own last order instead of the latest order across all sites.
272
+ def last_order_date(location_id)
273
+ return Lab::LabOrder.last&.start_date&.to_date if location_id.blank?
274
+
275
+ order_ids = Lab::LabTest.unscoped
276
+ .where(voided: false, location_id:,
277
+ concept: ConceptName.where(name: Lab::Metadata::TEST_TYPE_CONCEPT_NAME))
278
+ .select(:order_id)
279
+ Lab::LabOrder.where(order_id: order_ids).maximum(:start_date)&.to_date
280
+ end
281
+
268
282
  def create_rejection_notification(order_params)
269
283
  order = find_order order_params['tracking_number']
270
284
  data = { type: 'LIMS',
@@ -372,11 +386,15 @@ module Lab
372
386
  end
373
387
 
374
388
  def accession_number_exists?(accession_number)
375
- Lab::LabOrder.where(accession_number:).exists?
389
+ Lab::LabOrder.unscoped.where(accession_number:, voided: 0).exists?
376
390
  end
377
391
 
378
392
  def nlims_accession_number_exists?(accession_number)
379
- config = YAML.load_file('config/application.yml')
393
+ # No config file means NLIMS is not configured, same as no lims_api entry.
394
+ config_path = Rails.root.join('config', 'application.yml')
395
+ return false unless File.exist?(config_path)
396
+
397
+ config = YAML.load_file(config_path)
380
398
  return false unless config['lims_api']
381
399
 
382
400
  # fetch from the rest api and check if it exists
@@ -15,7 +15,7 @@ module Lab
15
15
  Lab::UpdatePatientOrdersJob.perform_later(patient_id) if patient_id
16
16
 
17
17
  tests = filter_tests(tests, test_type_id: filters.delete(:test_type_id),
18
- patient_id: filters.delete(:patient_id),
18
+ patient_id:,
19
19
  patient: filters.delete(:patient))
20
20
 
21
21
  tests = filter_tests_by_results(tests) if %w[1 true].include?(filters[:pending_results]&.downcase)
@@ -17,6 +17,8 @@ module Lab
17
17
  def authenticate_user(username:, password:, user_agent:, request_ip:)
18
18
  # Use unscoped for authentication - should not be location-dependent
19
19
  user = User.unscoped.find_by_username username
20
+ return nil if user.nil?
21
+
20
22
  encrypted_pass = Password.new(user.password)
21
23
  if encrypted_pass == password
22
24
  generate_token(user, user_agent, request_ip)
@@ -24,6 +26,9 @@ module Lab
24
26
  # throw authentication error
25
27
  nil
26
28
  end
29
+ rescue BCrypt::Errors::InvalidHash
30
+ # Regular EMR users have SHA passwords; only users created through the LIMS user endpoint can log in here
31
+ nil
27
32
  end
28
33
 
29
34
  private
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.7'.freeze
4
+ VERSION = '2.4.8'.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.7
4
+ version: 2.4.8
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-09-21 00:00:00.000000000 Z
11
+ date: 2026-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: couchrest
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 7.0.6
55
+ - !ruby/object:Gem::Dependency
56
+ name: rest-client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.1.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.1.0
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: socket.io-client-simple
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -109,35 +123,35 @@ dependencies:
109
123
  - !ruby/object:Gem::Version
110
124
  version: 2.16.0
111
125
  - !ruby/object:Gem::Dependency
112
- name: rspec-rails
126
+ name: mysql2
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - "~>"
116
130
  - !ruby/object:Gem::Version
117
- version: 5.0.0
131
+ version: 0.5.7
118
132
  type: :development
119
133
  prerelease: false
120
134
  version_requirements: !ruby/object:Gem::Requirement
121
135
  requirements:
122
136
  - - "~>"
123
137
  - !ruby/object:Gem::Version
124
- version: 5.0.0
138
+ version: 0.5.7
125
139
  - !ruby/object:Gem::Dependency
126
- name: rswag-api
140
+ name: rspec-rails
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
143
  - - "~>"
130
144
  - !ruby/object:Gem::Version
131
- version: 2.5.1
145
+ version: 5.0.0
132
146
  type: :development
133
147
  prerelease: false
134
148
  version_requirements: !ruby/object:Gem::Requirement
135
149
  requirements:
136
150
  - - "~>"
137
151
  - !ruby/object:Gem::Version
138
- version: 2.5.1
152
+ version: 5.0.0
139
153
  - !ruby/object:Gem::Dependency
140
- name: rswag-specs
154
+ name: rswag-api
141
155
  requirement: !ruby/object:Gem::Requirement
142
156
  requirements:
143
157
  - - "~>"
@@ -151,7 +165,7 @@ dependencies:
151
165
  - !ruby/object:Gem::Version
152
166
  version: 2.5.1
153
167
  - !ruby/object:Gem::Dependency
154
- name: rswag-ui
168
+ name: rswag-specs
155
169
  requirement: !ruby/object:Gem::Requirement
156
170
  requirements:
157
171
  - - "~>"
@@ -165,61 +179,33 @@ dependencies:
165
179
  - !ruby/object:Gem::Version
166
180
  version: 2.5.1
167
181
  - !ruby/object:Gem::Dependency
168
- name: rubocop
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: 0.79.0
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: 0.79.0
181
- - !ruby/object:Gem::Dependency
182
- name: rubocop-rspec
182
+ name: rswag-ui
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - "~>"
186
186
  - !ruby/object:Gem::Version
187
- version: 1.41.0
187
+ version: 2.5.1
188
188
  type: :development
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
- version: 1.41.0
194
+ version: 2.5.1
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: shoulda-matchers
197
197
  requirement: !ruby/object:Gem::Requirement
198
198
  requirements:
199
199
  - - "~>"
200
200
  - !ruby/object:Gem::Version
201
- version: 4.5.0
202
- type: :development
203
- prerelease: false
204
- version_requirements: !ruby/object:Gem::Requirement
205
- requirements:
206
- - - "~>"
207
- - !ruby/object:Gem::Version
208
- version: 4.5.0
209
- - !ruby/object:Gem::Dependency
210
- name: sqlite3
211
- requirement: !ruby/object:Gem::Requirement
212
- requirements:
213
- - - "~>"
214
- - !ruby/object:Gem::Version
215
- version: 1.4.0
201
+ version: '5.3'
216
202
  type: :development
217
203
  prerelease: false
218
204
  version_requirements: !ruby/object:Gem::Requirement
219
205
  requirements:
220
206
  - - "~>"
221
207
  - !ruby/object:Gem::Version
222
- version: 1.4.0
208
+ version: '5.3'
223
209
  description: |
224
210
  This adds a lab interface to the OpenMRS compatible core API provided by
225
211
  [HIS-EMR-API](https://github.com/EGPAFMalawiHIS/HIS-EMR-API).