his_emr_api_lab 0.0.3 → 0.0.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 +4 -4
- data/app/controllers/lab/labels_controller.rb +2 -0
- data/app/services/lab/labelling_service/order_label.rb +20 -2
- data/app/services/lab/lims/api.rb +0 -5
- data/app/services/lab/lims/migrator.rb +14 -21
- data/app/services/lab/lims/order_dto.rb +4 -2
- data/app/services/lab/lims/order_serializer.rb +3 -1
- data/app/services/lab/lims/utils.rb +12 -0
- data/app/services/lab/lims/worker.rb +29 -9
- data/app/services/lab/orders_search_service.rb +26 -16
- data/lib/couch_bum/couch_bum.rb +10 -5
- data/lib/generators/lab/install/templates/swagger.yaml +32 -0
- 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: 7b410e215fad55950c150d2ec7a82bda3f434cca4cc287e0d4fca2a9116e9182
|
4
|
+
data.tar.gz: 6b3a0c75c303c2139c1f6f963875667c4606bce42d23f6a22c277412e24db960
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f6068aecbc02d5c3df75ac422c4d4f9baeac86c6559a1e75078ef68088b4e6702239a366fb5e5879a2925592e3440a2880668bcc50f4730c9d2051f522ac94a
|
7
|
+
data.tar.gz: 5e3b4d61f507f2de7847951dcdd738a50dd29ba4ae5f981ce8284f3ba11c35d6d1c337787c1b78154b96eb318fc5343d404a3827fd02c89ffc3da60eb2d617c3
|
@@ -27,7 +27,7 @@ module Lab
|
|
27
27
|
'',
|
28
28
|
drawer,
|
29
29
|
'',
|
30
|
-
|
30
|
+
tests,
|
31
31
|
reason_for_test,
|
32
32
|
order.accession_number,
|
33
33
|
order.accession_number)
|
@@ -36,7 +36,7 @@ module Lab
|
|
36
36
|
def reason_for_test
|
37
37
|
return 'Unknown' unless order.reason_for_test
|
38
38
|
|
39
|
-
|
39
|
+
short_concept_name(order.reason_for_test.value_coded) || 'Unknown'
|
40
40
|
end
|
41
41
|
|
42
42
|
def patient
|
@@ -73,6 +73,24 @@ module Lab
|
|
73
73
|
ConceptName.find_by_concept_id(order.concept_id)&.name || 'Unknown'
|
74
74
|
end
|
75
75
|
|
76
|
+
def tests
|
77
|
+
tests = order.tests.map do |test|
|
78
|
+
name = short_concept_name(test.value_coded) || 'Unknown'
|
79
|
+
|
80
|
+
next 'VL' if name.match?(/Viral load/i)
|
81
|
+
|
82
|
+
name.size > 7 ? name[0..6] : name
|
83
|
+
end
|
84
|
+
|
85
|
+
tests.join(', ')
|
86
|
+
end
|
87
|
+
|
88
|
+
def short_concept_name(concept_id)
|
89
|
+
ConceptName.where(concept_id: concept_id)
|
90
|
+
.min_by { |concept| concept.name.size }
|
91
|
+
&.name
|
92
|
+
end
|
93
|
+
|
76
94
|
def unknown_concept
|
77
95
|
ConceptName.find_by_name('Unknown')
|
78
96
|
end
|
@@ -29,16 +29,11 @@ module Lab
|
|
29
29
|
# given block until the queue is empty or connection is terminated
|
30
30
|
# by calling method +choke+.
|
31
31
|
def consume_orders(from: 0, limit: 30)
|
32
|
-
last_seq = { value: 0 }
|
33
|
-
|
34
32
|
bum.binge_changes(since: from, limit: limit, include_docs: true) do |change|
|
35
33
|
next unless change['doc']['type']&.casecmp?('Order')
|
36
34
|
|
37
35
|
yield OrderDTO.new(change['doc']), self
|
38
|
-
last_seq[:value] = self.last_seq
|
39
36
|
end
|
40
|
-
|
41
|
-
last_seq[:value]
|
42
37
|
end
|
43
38
|
|
44
39
|
def create_order(order)
|
@@ -29,6 +29,7 @@ require 'lab/lab_test'
|
|
29
29
|
require 'lab/lims_order_mapping'
|
30
30
|
require 'lab/lims_failed_import'
|
31
31
|
|
32
|
+
require_relative './worker'
|
32
33
|
require_relative '../orders_service'
|
33
34
|
require_relative '../results_service'
|
34
35
|
require_relative '../tests_service'
|
@@ -47,14 +48,16 @@ module Lab
|
|
47
48
|
|
48
49
|
attr_reader :rejections
|
49
50
|
|
50
|
-
def consume_orders(from: nil,
|
51
|
+
def consume_orders(from: nil, **_kwargs)
|
52
|
+
limit = 50_000
|
53
|
+
|
51
54
|
Parallel.each(read_orders(from, limit),
|
52
55
|
in_processes: MAX_THREADS,
|
53
56
|
finish: order_pmap_post_processor(from)) do |row|
|
54
57
|
next unless row['doc']['type']&.casecmp?('Order')
|
55
58
|
|
56
|
-
User.current =
|
57
|
-
yield OrderDTO.new(row['doc']), OpenStruct.new(last_seq: from)
|
59
|
+
User.current = Utils.lab_user
|
60
|
+
yield OrderDTO.new(row['doc']), OpenStruct.new(last_seq: (from || 0) + limit, current_seq: from)
|
58
61
|
end
|
59
62
|
end
|
60
63
|
|
@@ -70,7 +73,7 @@ module Lab
|
|
70
73
|
private
|
71
74
|
|
72
75
|
def last_seq_path
|
73
|
-
|
76
|
+
LIMS_LOG_PATH.join('migration-last-id.dat')
|
74
77
|
end
|
75
78
|
|
76
79
|
def order_pmap_post_processor(last_seq)
|
@@ -129,18 +132,6 @@ module Lab
|
|
129
132
|
def update_last_seq(_last_seq); end
|
130
133
|
end
|
131
134
|
|
132
|
-
def self.lab_user
|
133
|
-
user = User.find_by_username('lab_daemon')
|
134
|
-
return user if user
|
135
|
-
|
136
|
-
god_user = User.first
|
137
|
-
|
138
|
-
person = Person.create!(creator: god_user.user_id)
|
139
|
-
PersonName.create!(person: person, given_name: 'Lab', family_name: 'Daemon', creator: god_user.user_id)
|
140
|
-
|
141
|
-
User.create!(username: 'lab_daemon', person: person, creator: god_user.user_id)
|
142
|
-
end
|
143
|
-
|
144
135
|
def self.save_csv(filename, rows:, headers: nil)
|
145
136
|
CSV.open(filename, File::WRONLY | File::CREAT) do |csv|
|
146
137
|
csv << headers if headers
|
@@ -148,12 +139,13 @@ module Lab
|
|
148
139
|
end
|
149
140
|
end
|
150
141
|
|
151
|
-
MIGRATION_REJECTIONS_CSV_PATH =
|
142
|
+
MIGRATION_REJECTIONS_CSV_PATH = LIMS_LOG_PATH.join('migration-rejections.csv')
|
152
143
|
|
153
144
|
def self.export_rejections(rejections)
|
154
|
-
headers = ['Accession number', 'NHID', 'First name', 'Last name', 'Reason']
|
145
|
+
headers = ['doc_id', 'Accession number', 'NHID', 'First name', 'Last name', 'Reason']
|
155
146
|
rows = (rejections || []).map do |rejection|
|
156
147
|
[
|
148
|
+
rejection.order[:_id],
|
157
149
|
rejection.order[:tracking_number],
|
158
150
|
rejection.order[:patient][:id],
|
159
151
|
rejection.order[:patient][:first_name],
|
@@ -165,12 +157,13 @@ module Lab
|
|
165
157
|
save_csv(MIGRATION_REJECTIONS_CSV_PATH, headers: headers, rows: rows)
|
166
158
|
end
|
167
159
|
|
168
|
-
MIGRATION_FAILURES_CSV_PATH =
|
160
|
+
MIGRATION_FAILURES_CSV_PATH = LIMS_LOG_PATH.join('migration-failures.csv')
|
169
161
|
|
170
162
|
def self.export_failures
|
171
|
-
headers = ['Accession number', 'NHID', 'Reason', 'Difference']
|
163
|
+
headers = ['doc_id', 'Accession number', 'NHID', 'Reason', 'Difference']
|
172
164
|
rows = Lab::LimsFailedImport.all.map do |failure|
|
173
165
|
[
|
166
|
+
failure.lims_id,
|
174
167
|
failure.tracking_number,
|
175
168
|
failure.patient_nhid,
|
176
169
|
failure.reason,
|
@@ -181,7 +174,7 @@ module Lab
|
|
181
174
|
save_csv(MIGRATION_FAILURES_CSV_PATH, headers: headers, rows: rows)
|
182
175
|
end
|
183
176
|
|
184
|
-
MIGRATION_LOG_PATH =
|
177
|
+
MIGRATION_LOG_PATH = LIMS_LOG_PATH.join('migration.log')
|
185
178
|
|
186
179
|
def self.start_migration
|
187
180
|
log_dir = Rails.root.join('log/lims')
|
@@ -30,9 +30,9 @@ module Lab
|
|
30
30
|
|
31
31
|
# Translates a LIMS specimen name to an OpenMRS concept_id
|
32
32
|
def specimen_type_id
|
33
|
-
lims_specimen_name = self['sample_type']
|
33
|
+
lims_specimen_name = self['sample_type']&.strip&.downcase
|
34
34
|
|
35
|
-
if %w[specimen_not_collected not_assigned].include?(lims_specimen_name)
|
35
|
+
if %w[specimen_not_collected not_assigned not_specified].include?(lims_specimen_name)
|
36
36
|
return ConceptName.select(:concept_id).find_by_name!('Unknown').concept_id
|
37
37
|
end
|
38
38
|
|
@@ -53,6 +53,8 @@ module Lab
|
|
53
53
|
|
54
54
|
# Extract requesting clinician name from LIMS
|
55
55
|
def requesting_clinician
|
56
|
+
return 'Unknown' unless self['who_order_test']
|
57
|
+
|
56
58
|
# TODO: Extend requesting clinician to an obs tree having extra parameters
|
57
59
|
# like phone number and ID to closely match the lims user.
|
58
60
|
first_name = self['who_order_test']['first_name'] || ''
|
@@ -97,7 +97,9 @@ module Lab
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def format_test_results(order)
|
100
|
-
order.tests
|
100
|
+
order.tests&.each_with_object({}) do |test, results|
|
101
|
+
next unless test.result
|
102
|
+
|
101
103
|
results[test.name] = {
|
102
104
|
results: test.result.each_with_object({}) do |measure, measures|
|
103
105
|
measures[measure.indicator.name] = { result_value: "#{measure.value_modifier}#{measure.value}" }
|
@@ -39,6 +39,18 @@ module Lab
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
+
def self.lab_user
|
43
|
+
user = User.find_by_username('lab_daemon')
|
44
|
+
return user if user
|
45
|
+
|
46
|
+
god_user = User.first
|
47
|
+
|
48
|
+
person = Person.create!(creator: god_user.user_id)
|
49
|
+
PersonName.create!(person: person, given_name: 'Lab', family_name: 'Daemon', creator: god_user.user_id)
|
50
|
+
|
51
|
+
User.create!(username: 'lab_daemon', person: person, creator: god_user.user_id)
|
52
|
+
end
|
53
|
+
|
42
54
|
def self.parse_date(str_date, fallback_date = nil)
|
43
55
|
if str_date.blank? && fallback_date.blank?
|
44
56
|
raise "Can't parse blank date"
|
@@ -2,12 +2,16 @@
|
|
2
2
|
|
3
3
|
require 'cgi/util'
|
4
4
|
|
5
|
+
require_relative './api'
|
5
6
|
require_relative './exceptions'
|
6
7
|
require_relative './order_serializer'
|
7
8
|
require_relative './utils'
|
8
9
|
|
9
10
|
module Lab
|
10
11
|
module Lims
|
12
|
+
LIMS_LOG_PATH = Rails.root.join('log/lims')
|
13
|
+
Dir.mkdir(LIMS_LOG_PATH) unless File.exist?(LIMS_LOG_PATH)
|
14
|
+
|
11
15
|
##
|
12
16
|
# Pull/Push orders from/to the LIMS queue (Oops meant CouchDB).
|
13
17
|
class Worker
|
@@ -15,6 +19,21 @@ module Lab
|
|
15
19
|
|
16
20
|
attr_reader :lims_api
|
17
21
|
|
22
|
+
def self.start
|
23
|
+
File.open(LIMS_LOG_PATH.join('worker.lock'), File::WRONLY | File::CREAT, 0o644) do |fout|
|
24
|
+
fout.flock(File::LOCK_EX)
|
25
|
+
|
26
|
+
User.current = Utils.lab_user
|
27
|
+
|
28
|
+
fout.write("Worker ##{Process.pid} started at #{Time.now}")
|
29
|
+
worker = new(Api.new)
|
30
|
+
worker.pull_orders
|
31
|
+
# TODO: Verify that names being pushed to LIMS are of the correct format (ie matching
|
32
|
+
# LIMS naming conventions). Enable pushing when that is done
|
33
|
+
# worker.push_orders
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
18
37
|
def initialize(lims_api)
|
19
38
|
@lims_api = lims_api
|
20
39
|
end
|
@@ -52,8 +71,8 @@ module Lab
|
|
52
71
|
lims_api.update_order(mapping.lims_id, order_dto)
|
53
72
|
mapping.update(pushed_at: Time.now)
|
54
73
|
else
|
55
|
-
|
56
|
-
LimsOrderMapping.create!(order: order, lims_id:
|
74
|
+
update = lims_api.create_order(order_dto)
|
75
|
+
LimsOrderMapping.create!(order: order, lims_id: update['id'], revision: update['rev'], pushed_at: Time.now)
|
57
76
|
end
|
58
77
|
end
|
59
78
|
|
@@ -64,7 +83,8 @@ module Lab
|
|
64
83
|
# Pulls orders from the LIMS queue and writes them to the local database
|
65
84
|
def pull_orders
|
66
85
|
logger.info("Retrieving LIMS orders starting from #{last_seq}")
|
67
|
-
|
86
|
+
|
87
|
+
lims_api.consume_orders(from: last_seq, limit: 100) do |order_dto, context|
|
68
88
|
logger.debug("Retrieved order ##{order_dto[:tracking_number]}: #{order_dto}")
|
69
89
|
|
70
90
|
patient = find_patient_by_nhid(order_dto[:patient][:id])
|
@@ -80,6 +100,8 @@ module Lab
|
|
80
100
|
save_failed_import(order_dto, 'Demographics not matching', diff)
|
81
101
|
end
|
82
102
|
|
103
|
+
update_last_seq(context.current_seq)
|
104
|
+
|
83
105
|
[:accepted, "Patient NPID, '#{order_dto[:patient][:id]}', matched"]
|
84
106
|
rescue DuplicateNHID
|
85
107
|
logger.warn("Failed to import order due to duplicate patient NHID: #{order_dto[:patient][:id]}")
|
@@ -90,8 +112,6 @@ module Lab
|
|
90
112
|
rescue LimsException => e
|
91
113
|
logger.warn("Failed to import order due to #{e.class} - #{e.message}")
|
92
114
|
save_failed_import(order_dto, e.message)
|
93
|
-
ensure
|
94
|
-
update_last_seq(context.last_seq)
|
95
115
|
end
|
96
116
|
end
|
97
117
|
|
@@ -264,12 +284,12 @@ module Lab
|
|
264
284
|
indicator: { concept_id: indicator.concept_id },
|
265
285
|
value_type: value_type,
|
266
286
|
value: value_type == 'numeric' ? value.to_f : value,
|
267
|
-
value_modifier: value_modifier
|
287
|
+
value_modifier: value_modifier.blank? ? '=' : value_modifier
|
268
288
|
)
|
269
289
|
end
|
270
290
|
|
271
291
|
def parse_lims_result_value(value)
|
272
|
-
value = value['result_value']
|
292
|
+
value = value['result_value']&.strip
|
273
293
|
return nil, nil, nil if value.blank?
|
274
294
|
|
275
295
|
match = value&.match(/^(>|=|<|<=|>=)(.*)$/)
|
@@ -279,7 +299,7 @@ module Lab
|
|
279
299
|
end
|
280
300
|
|
281
301
|
def guess_result_datatype(result)
|
282
|
-
return 'numeric' if result.match?(/^[+-]?(\d+(\.\d+)|\.\d+)?$/)
|
302
|
+
return 'numeric' if result.strip.match?(/^[+-]?(\d+(\.\d+)|\.\d+)?$/)
|
283
303
|
|
284
304
|
'text'
|
285
305
|
end
|
@@ -303,7 +323,7 @@ module Lab
|
|
303
323
|
end
|
304
324
|
|
305
325
|
def last_seq_path
|
306
|
-
|
326
|
+
LIMS_LOG_PATH.join('last_seq.dat')
|
307
327
|
end
|
308
328
|
end
|
309
329
|
end
|
@@ -5,44 +5,54 @@ module Lab
|
|
5
5
|
module OrdersSearchService
|
6
6
|
class << self
|
7
7
|
def find_orders(filters)
|
8
|
-
|
9
|
-
status = filters.delete(:status)
|
8
|
+
extra_filters = pop_filters(filters, :date, :end_date, :status)
|
10
9
|
|
11
10
|
orders = Lab::LabOrder.prefetch_relationships
|
12
11
|
.where(filters)
|
13
12
|
.order(start_date: :desc)
|
14
13
|
|
15
|
-
orders =
|
16
|
-
orders =
|
14
|
+
orders = filter_orders_by_status(orders, pop_filters(extra_filters, :status))
|
15
|
+
orders = filter_orders_by_date(orders, extra_filters)
|
17
16
|
|
18
17
|
orders.map { |order| Lab::LabOrderSerializer.serialize_order(order) }
|
19
18
|
end
|
20
19
|
|
21
|
-
def filter_orders_by_date(orders, date)
|
22
|
-
|
23
|
-
|
20
|
+
def filter_orders_by_date(orders, date: nil, end_date: nil)
|
21
|
+
date = date&.to_date
|
22
|
+
end_date = end_date&.to_date
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
when 'ordered' then orders.where(concept_id: unknown_concept_id)
|
28
|
-
when 'drawn' then orders.where.not(concept_id: unknown_concept_id)
|
24
|
+
if date && end_date
|
25
|
+
return orders.where('start_date BETWEEN ? AND ?', date, end_date + 1.day)
|
29
26
|
end
|
30
|
-
end
|
31
27
|
|
32
|
-
|
33
|
-
|
28
|
+
if date
|
29
|
+
return orders.where('start_date BETWEEN ? AND ?', date, date + 1.day)
|
30
|
+
end
|
31
|
+
|
32
|
+
return orders.where('start_date < ?', end_date + 1.day) if end_date
|
33
|
+
|
34
|
+
orders
|
34
35
|
end
|
35
36
|
|
36
|
-
def filter_orders_by_status(orders, status)
|
37
|
-
case status
|
37
|
+
def filter_orders_by_status(orders, status: nil)
|
38
|
+
case status&.downcase
|
38
39
|
when 'ordered' then orders.where(concept_id: unknown_concept_id)
|
39
40
|
when 'drawn' then orders.where.not(concept_id: unknown_concept_id)
|
41
|
+
else orders
|
40
42
|
end
|
41
43
|
end
|
42
44
|
|
43
45
|
def unknown_concept_id
|
44
46
|
ConceptName.find_by_name!('Unknown').concept_id
|
45
47
|
end
|
48
|
+
|
49
|
+
def pop_filters(params, *filters)
|
50
|
+
filters.each_with_object({}) do |filter, popped_params|
|
51
|
+
next unless params.key?(filter)
|
52
|
+
|
53
|
+
popped_params[filter.to_sym] = params.delete(filter)
|
54
|
+
end
|
55
|
+
end
|
46
56
|
end
|
47
57
|
end
|
48
58
|
end
|
data/lib/couch_bum/couch_bum.rb
CHANGED
@@ -24,18 +24,21 @@ class CouchBum
|
|
24
24
|
# within the passed block.
|
25
25
|
def binge_changes(since: 0, limit: nil, include_docs: nil, &block)
|
26
26
|
catch(:choke) do
|
27
|
-
|
27
|
+
logger.debug("Binging #{limit} changes from '#{since}'")
|
28
|
+
params = stringify_params(limit: limit, include_docs: include_docs)
|
29
|
+
params = "since=#{since}&#{params}" unless since.blank?
|
28
30
|
|
29
|
-
changes = couch_rest(:get, "_changes
|
31
|
+
changes = couch_rest(:get, "_changes?#{params}")
|
30
32
|
context = BingeContext.new(changes)
|
31
|
-
changes['results'].each
|
33
|
+
changes['results'].each do |change|
|
34
|
+
context.current_seq = change['seq']
|
35
|
+
context.instance_exec(change, &block)
|
36
|
+
end
|
32
37
|
end
|
33
38
|
end
|
34
39
|
|
35
40
|
def couch_rest(method, route, *args, **kwargs)
|
36
41
|
url = expand_route(route)
|
37
|
-
|
38
|
-
logger.debug("CouchBum: Executing #{method} #{url}")
|
39
42
|
CouchRest.send(method, url, *args, **kwargs)
|
40
43
|
rescue CouchRest::Exception => e
|
41
44
|
logger.error("Failed to communicate with CouchDB: Status: #{e.http_code} - #{e.http_body}")
|
@@ -46,6 +49,8 @@ class CouchBum
|
|
46
49
|
|
47
50
|
# Context under which the callback passed to binge_changes is executed.
|
48
51
|
class BingeContext
|
52
|
+
attr_accessor :current_seq
|
53
|
+
|
49
54
|
def initialize(changes)
|
50
55
|
@changes = changes
|
51
56
|
end
|
@@ -175,6 +175,12 @@ paths:
|
|
175
175
|
description: 'Filter by sample status: ordered, drawn'
|
176
176
|
schema:
|
177
177
|
type: string
|
178
|
+
- name: end_date
|
179
|
+
in: query
|
180
|
+
required: false
|
181
|
+
description: Select all results before this date
|
182
|
+
schema:
|
183
|
+
type: date
|
178
184
|
responses:
|
179
185
|
'200':
|
180
186
|
description: Success
|
@@ -396,6 +402,32 @@ paths:
|
|
396
402
|
responses:
|
397
403
|
'204':
|
398
404
|
description: No Content
|
405
|
+
"/api/v1/lab/reasons_for_test":
|
406
|
+
get:
|
407
|
+
summary: Reasons for test
|
408
|
+
description: Retrieve default reasons for test concept set
|
409
|
+
tags:
|
410
|
+
- Concepts
|
411
|
+
security:
|
412
|
+
- api_key: []
|
413
|
+
responses:
|
414
|
+
'200':
|
415
|
+
description: Success
|
416
|
+
content:
|
417
|
+
application/json:
|
418
|
+
schema:
|
419
|
+
type: array
|
420
|
+
items:
|
421
|
+
type: object
|
422
|
+
properties:
|
423
|
+
concept_id:
|
424
|
+
type: integer
|
425
|
+
name:
|
426
|
+
type: string
|
427
|
+
example: Routine
|
428
|
+
required:
|
429
|
+
- concept_id
|
430
|
+
- name
|
399
431
|
"/api/v1/lab/tests/{test_id}/results":
|
400
432
|
post:
|
401
433
|
summary: Add results to order
|
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: 0.0.
|
4
|
+
version: 0.0.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: 2021-04-
|
11
|
+
date: 2021-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: couchrest
|