ruby_astm 0.0.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 +7 -0
- data/lib/publisher/adapter.rb +5 -0
- data/lib/publisher/credentials.json +1 -0
- data/lib/publisher/google_lab_interface.rb +135 -0
- data/lib/publisher/poller.rb +339 -0
- data/lib/publisher/token.yaml +2 -0
- data/lib/ruby_astm/astm_server.rb +49 -0
- data/lib/ruby_astm/frame.rb +25 -0
- data/lib/ruby_astm/header.rb +47 -0
- data/lib/ruby_astm/lab_interface.rb +160 -0
- data/lib/ruby_astm/line.rb +65 -0
- data/lib/ruby_astm/order.rb +75 -0
- data/lib/ruby_astm/patient.rb +32 -0
- data/lib/ruby_astm/query.rb +34 -0
- data/lib/ruby_astm/result.rb +34 -0
- data/lib/ruby_astm.rb +12 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9583ca630b54cc83f9bffdb91b5094b7cc01145b
|
4
|
+
data.tar.gz: 7695f9fe20e298629f5eb5af67d8ea8f5c345616
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ec25812e078a96cf28ecd54ed6cf2325ef84ce46e15087d49b55341ca9a9be8542e5848bc493933dd33fd856c2c5a958a9855e0ca7158d4cd579a439d604760d
|
7
|
+
data.tar.gz: 210f8f1ff0d433b688af770419592f4966dedac076499a0913193cd948ee71f7a545b185e007206ced54c3bb5642f780d5f53ead36c06e8575727cfbd0d1e631
|
@@ -0,0 +1 @@
|
|
1
|
+
{"installed":{"client_id":"555057029489-sel4u1t4tnv3qicdhsssp9phn3r8tvrj.apps.googleusercontent.com","project_id":"project-id-4907840994004404941","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://www.googleapis.com/oauth2/v3/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"L6DjrWGteAcwJwh7YdfzYwDp","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}}
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'google/apis/script_v1'
|
2
|
+
require 'googleauth'
|
3
|
+
require 'googleauth/stores/file_token_store'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'publisher/poller'
|
6
|
+
|
7
|
+
class Google_Lab_Interface < Poller
|
8
|
+
|
9
|
+
|
10
|
+
OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'.freeze
|
11
|
+
APPLICATION_NAME = 'Google Apps Script API Ruby Quickstart'.freeze
|
12
|
+
CREDENTIALS_PATH = 'credentials.json'.freeze
|
13
|
+
# The file token.yaml stores the user's access and refresh tokens, and is
|
14
|
+
# created automatically when the authorization flow completes for the first
|
15
|
+
# time.
|
16
|
+
TOKEN_PATH = 'token.yaml'.freeze
|
17
|
+
SCOPE = 'https://www.googleapis.com/auth/script.projects'.freeze
|
18
|
+
|
19
|
+
SCOPES = ["https://www.googleapis.com/auth/documents","https://www.googleapis.com/auth/drive","https://www.googleapis.com/auth/script.projects","https://www.googleapis.com/auth/spreadsheets"]
|
20
|
+
|
21
|
+
$service = nil
|
22
|
+
SCRIPT_ID = "M7JDg7zmo0Xldo4RTWFGCsI2yotVzKYhk"
|
23
|
+
|
24
|
+
|
25
|
+
##
|
26
|
+
# Ensure valid credentials, either by restoring from the saved credentials
|
27
|
+
# files or intitiating an OAuth2 authorization. If authorization is required,
|
28
|
+
# the user's default browser will be launched to approve the request.
|
29
|
+
#
|
30
|
+
# @return [Google::Auth::UserRefreshCredentials] OAuth2 credentials
|
31
|
+
def authorize
|
32
|
+
|
33
|
+
client_id = Google::Auth::ClientId.from_file(root_path + "/publisher/" + CREDENTIALS_PATH)
|
34
|
+
token_store = Google::Auth::Stores::FileTokenStore.new(file: (root_path + "/publisher/" + TOKEN_PATH))
|
35
|
+
authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPES, token_store)
|
36
|
+
user_id = 'default'
|
37
|
+
credentials = authorizer.get_credentials(user_id)
|
38
|
+
if credentials.nil?
|
39
|
+
url = authorizer.get_authorization_url(base_url: OOB_URI)
|
40
|
+
puts 'Open the following URL in the browser and enter the ' \
|
41
|
+
"resulting code after authorization:\n" + url
|
42
|
+
code = gets
|
43
|
+
credentials = authorizer.get_and_store_credentials_from_code(
|
44
|
+
user_id: user_id, code: code, base_url: OOB_URI
|
45
|
+
)
|
46
|
+
end
|
47
|
+
credentials
|
48
|
+
end
|
49
|
+
|
50
|
+
## @param[String] mpg : path to mappings file. Defaults to nil.
|
51
|
+
def initialize(mpg=nil)
|
52
|
+
AstmServer.log("Initialized Google Lab Interface")
|
53
|
+
super(mpg)
|
54
|
+
$service = Google::Apis::ScriptV1::ScriptService.new
|
55
|
+
$service.client_options.application_name = APPLICATION_NAME
|
56
|
+
$service.authorization = authorize
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def poll_LIS_for_requisition
|
61
|
+
|
62
|
+
pre_poll_LIS
|
63
|
+
|
64
|
+
AstmServer.log("polling LIS for new requisitions")
|
65
|
+
|
66
|
+
epoch = (Time.now - 5.days).to_i*1000
|
67
|
+
|
68
|
+
pp = {
|
69
|
+
:input => JSON.generate([epoch])
|
70
|
+
}
|
71
|
+
|
72
|
+
request = Google::Apis::ScriptV1::ExecutionRequest.new(
|
73
|
+
function: 'get_latest_test_information',
|
74
|
+
parameters: pp
|
75
|
+
)
|
76
|
+
|
77
|
+
begin
|
78
|
+
resp = $service.run_script(SCRIPT_ID, request)
|
79
|
+
if resp.error
|
80
|
+
AstmServer.log("Response Error polling LIS for requisitions: #{resp.error.message}: #{resp.error.code}")
|
81
|
+
else
|
82
|
+
process_LIS_response(resp.response["result"])
|
83
|
+
AstmServer.log("Successfully polled lis for requisitions: #{resp.response}")
|
84
|
+
end
|
85
|
+
rescue => e
|
86
|
+
AstmServer.log("Rescue Error polling LIS for requisitions: #{e.to_s}")
|
87
|
+
AstmServer.log("Error backtrace")
|
88
|
+
AstmServer.log(e.backtrace.to_s)
|
89
|
+
ensure
|
90
|
+
post_poll_LIS
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# method overriden from adapter.
|
97
|
+
# data should be an array of objects.
|
98
|
+
# see adapter for the recommended structure.
|
99
|
+
def update(data)
|
100
|
+
|
101
|
+
orders = JSON.generate(data)
|
102
|
+
|
103
|
+
pp = {
|
104
|
+
:input => orders
|
105
|
+
}
|
106
|
+
|
107
|
+
request = Google::Apis::ScriptV1::ExecutionRequest.new(
|
108
|
+
function: 'update_report',
|
109
|
+
parameters: pp
|
110
|
+
)
|
111
|
+
|
112
|
+
begin
|
113
|
+
AstmServer.log("updating following results to LIS")
|
114
|
+
AstmServer.log(request.parameters.to_s)
|
115
|
+
resp = $service.run_script(SCRIPT_ID, request)
|
116
|
+
if resp.error
|
117
|
+
AstmServer.log("Error updating results to LIS, message follows")
|
118
|
+
AstmServer.log("error: #{resp.error.message} : code: #{resp.error.code}")
|
119
|
+
#puts "there was an error."
|
120
|
+
else
|
121
|
+
AstmServer.log("Updating results to LIS successfull")
|
122
|
+
end
|
123
|
+
rescue => e
|
124
|
+
AstmServer.log("Error updating results to LIS, backtrace follows")
|
125
|
+
AstmServer.log(e.backtrace.to_s)
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def poll
|
132
|
+
super
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,339 @@
|
|
1
|
+
require 'rufus-scheduler'
|
2
|
+
require 'time'
|
3
|
+
require 'redis'
|
4
|
+
|
5
|
+
class Poller
|
6
|
+
#############################################################################3
|
7
|
+
##
|
8
|
+
##
|
9
|
+
## require the file that implements the Adapter class.
|
10
|
+
##
|
11
|
+
##
|
12
|
+
##############################################################################
|
13
|
+
EDTA = "EDTA"
|
14
|
+
SERUM = "SERUM"
|
15
|
+
PLASMA = "PLASMA"
|
16
|
+
FLUORIDE = "FLUORIDE"
|
17
|
+
ESR = "ESR"
|
18
|
+
URINE = "URINE"
|
19
|
+
REQUISITIONS_SORTED_SET = "requisitions_sorted_set"
|
20
|
+
REQUISITIONS_HASH = "requisitions_hash"
|
21
|
+
POLL_STATUS_KEY = "ruby_astm_lis_poller"
|
22
|
+
LAST_REQUEST_AT = "last_request_at"
|
23
|
+
LAST_REQUEST_STATUS = "last_request_status"
|
24
|
+
RUNNING = "running"
|
25
|
+
COMPLETED = "completed"
|
26
|
+
|
27
|
+
def initialize(mpg=nil)
|
28
|
+
$redis = Redis.new
|
29
|
+
## this mapping is from MACHINE CODE AS THE KEY
|
30
|
+
$mappings = JSON.parse(IO.read(mpg || ("mappings.json")))
|
31
|
+
## INVERTING THE MAPPINGS, GIVES US THE LIS CODE AS THE KEY.
|
32
|
+
$inverted_mappings = Hash[$mappings.values.map{|c| c = c["LIS_CODE"]}.zip($mappings.keys)]
|
33
|
+
end
|
34
|
+
|
35
|
+
def root_path
|
36
|
+
File.dirname __dir__
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def prepare_redis
|
41
|
+
if ($redis.exists "processing") == 0
|
42
|
+
$redis.lpush("processing",JSON.generate([]))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def pre_poll_LIS
|
47
|
+
previous_requisition_request_status = nil
|
48
|
+
|
49
|
+
if previous_requisition_request_status = $redis.get(POLL_STATUS_KEY)
|
50
|
+
|
51
|
+
last_request_at = previous_requisition_request_status[LAST_REQUEST_AT]
|
52
|
+
|
53
|
+
last_request_status = previous_requisition_request_status[LAST_REQUEST_STATUS]
|
54
|
+
end
|
55
|
+
|
56
|
+
running_time = Time.now.to_i
|
57
|
+
|
58
|
+
$redis.watch(POLL_STATUS_KEY) do
|
59
|
+
|
60
|
+
if $redis.get(POLL_STATUS_KEY) == previous_requisition_request_status
|
61
|
+
if ((last_request_status != RUNNING) || ((Time.now.to_i - last_request_at) > 600))
|
62
|
+
$redis.multi do |multi|
|
63
|
+
multi.set(POLL_STATUS_KEY, JSON.generate({LAST_REQUEST_STATUS => RUNNING, LAST_REQUEST_AT => running_time}))
|
64
|
+
end
|
65
|
+
AstmServer.log("pre poll lis status set to running")
|
66
|
+
end
|
67
|
+
else
|
68
|
+
AstmServer.log("pre poll lis status check interrupted by another client, so exiting here")
|
69
|
+
$redis.unwatch
|
70
|
+
return
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
## uses redis CAS to ensure that two requests don't overlap.
|
76
|
+
## will update to the requisitions hash the specimen id -> and the
|
77
|
+
## now lets test this.
|
78
|
+
## how to stub it out ?
|
79
|
+
## first we call it direct.
|
80
|
+
def post_poll_LIS
|
81
|
+
|
82
|
+
requisition_status = JSON.parse($redis.get(POLL_STATUS_KEY))
|
83
|
+
|
84
|
+
if (requisition_status[LAST_REQUEST_STATUS] == RUNNING)
|
85
|
+
|
86
|
+
$redis.watch(POLL_STATUS_KEY) do
|
87
|
+
|
88
|
+
if $redis.get(POLL_STATUS_KEY) == JSON.generate(requisition_status)
|
89
|
+
|
90
|
+
$redis.multi do |multi|
|
91
|
+
multi.set(POLL_STATUS_KEY,JSON.generate({LAST_REQUEST_STATUS => COMPLETED, LAST_REQUEST_AT => requisition_status[LAST_REQUEST_AT]}))
|
92
|
+
end
|
93
|
+
AstmServer.log("post poll LIS status set to completed")
|
94
|
+
else
|
95
|
+
AstmServer.log("post poll LIS was was interrupted by another client , so exited this thread")
|
96
|
+
$redis.unwatch(POLL_STATUS_KEY)
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
else
|
103
|
+
AstmServer.log("post poll LIS was not in running state")
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
def build_tests_hash(record)
|
109
|
+
tests_hash = {}
|
110
|
+
|
111
|
+
## key -> TUBE_NAME : eg: EDTA
|
112
|
+
## value -> its barcode id.
|
113
|
+
tube_ids = {}
|
114
|
+
## assign.
|
115
|
+
## lavender -> index 28
|
116
|
+
## serum -> index 29
|
117
|
+
## plasm -> index 30
|
118
|
+
## fluoride -> index 31
|
119
|
+
## urine -> index 32
|
120
|
+
## esr -> index 33
|
121
|
+
unless record[28].blank?
|
122
|
+
tube_ids[EDTA] = record[28]
|
123
|
+
tests_hash[EDTA + ":" + record[28]] = []
|
124
|
+
end
|
125
|
+
|
126
|
+
unless record[29].blank?
|
127
|
+
tube_ids[SERUM] = record[29]
|
128
|
+
tests_hash[SERUM + ":" + record[29]] = []
|
129
|
+
end
|
130
|
+
|
131
|
+
unless record[30].blank?
|
132
|
+
tube_ids[PLASMA] = record[30]
|
133
|
+
tests_hash[PLASMA + ":" + record[30]] = []
|
134
|
+
end
|
135
|
+
|
136
|
+
unless record[31].blank?
|
137
|
+
tube_ids[FLUORIDE] = record[31]
|
138
|
+
tests_hash[FLUORIDE + ":" + record[31]] = []
|
139
|
+
end
|
140
|
+
|
141
|
+
unless record[32].blank?
|
142
|
+
tube_ids[URINE] = record[32]
|
143
|
+
tests_hash[URINE + ":" + record[32]] = []
|
144
|
+
end
|
145
|
+
|
146
|
+
unless record[33].blank?
|
147
|
+
tube_ids[ESR] = record[33]
|
148
|
+
tests_hash[ESR + ":" + record[33]] = []
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
tests = record[8].split(",")
|
153
|
+
tests.each do |test|
|
154
|
+
## use the inverted mappings to
|
155
|
+
if machine_code = $inverted_mappings[test]
|
156
|
+
## now get its tube type
|
157
|
+
## mappings have to match the tubes defined in this file.
|
158
|
+
tube = $mappings[machine_code]["TUBE"]
|
159
|
+
## now find the tests_hash which has this tube.
|
160
|
+
## and the machine code to its array.
|
161
|
+
## so how to find this.
|
162
|
+
tube_key = tests_hash.keys.select{|c| c=~/#{tube}/ }[0]
|
163
|
+
tests_hash[tube_key] << machine_code
|
164
|
+
else
|
165
|
+
AstmServer.log("ERROR: Test: #{test} does not have an LIS code")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
AstmServer.log("tests hash generated")
|
169
|
+
AstmServer.log(JSON.generate(tests_hash))
|
170
|
+
tests_hash
|
171
|
+
end
|
172
|
+
|
173
|
+
## @param[Integer] epoch : the epoch at which these tests were requested.
|
174
|
+
## @param[Hash] tests : {"EDTA:barcode" => [MCV,MCH,MCHC...]}
|
175
|
+
def merge_with_requisitions_hash(epoch,tests)
|
176
|
+
## so we basically now add this to the epoch ?
|
177
|
+
## or a sorted set ?
|
178
|
+
## key -> TUBE:specimen_id
|
179
|
+
## value -> array of tests as json
|
180
|
+
## score -> time.
|
181
|
+
$redis.multi do |multi|
|
182
|
+
$redis.zadd REQUISITIONS_SORTED_SET, epoch, JSON.generate(tests)
|
183
|
+
tests.keys.each do |tube_barcode|
|
184
|
+
$redis.hset REQUISITIONS_HASH, tube_barcode, JSON.generate(tests[tube_barcode])
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
## @param[String] json_response : contains the response from the LIS
|
191
|
+
## it should be the jsonified version of a hash that is structured as follows:
|
192
|
+
=begin
|
193
|
+
{
|
194
|
+
"epoch" => [
|
195
|
+
[index_8 : list_of_LIS_TEST_CODES_seperated_by_commas, index 28 => lavender, index 29 => serum, index 30 => plasma, index 31 => fluoride, index 32 => urine, index 33 => esr]
|
196
|
+
}
|
197
|
+
=end
|
198
|
+
## @return[nil]
|
199
|
+
def process_LIS_response(json_response)
|
200
|
+
lab_results = JSON.parse(json_response)
|
201
|
+
AstmServer.log("requisitions downloaded from LIS")
|
202
|
+
AstmServer.log(JSON.generate(lab_results))
|
203
|
+
lab_results.keys.each do |epoch|
|
204
|
+
merge_with_requisitions_hash(epoch,build_tests_hash(lab_results[epoch][0]))
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
## override to define how the data is updated.
|
209
|
+
def update(data)
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
##@param[Array] data : array of objects.
|
214
|
+
##@return[Boolean] true/false : depending on whether it was successfully updated or not.
|
215
|
+
## recommended structure for data.
|
216
|
+
=begin
|
217
|
+
data = [
|
218
|
+
{
|
219
|
+
:id => "ARUBA",
|
220
|
+
:results => [
|
221
|
+
{
|
222
|
+
:name => "TLCparam",
|
223
|
+
:value => 10
|
224
|
+
},
|
225
|
+
{
|
226
|
+
:name => "Nparam",
|
227
|
+
:value => 23
|
228
|
+
},
|
229
|
+
{
|
230
|
+
:name => "ANCparam",
|
231
|
+
:value => 25
|
232
|
+
},
|
233
|
+
{
|
234
|
+
:name => "Lparam",
|
235
|
+
:value => 10
|
236
|
+
},
|
237
|
+
{
|
238
|
+
:name => "ALCparam",
|
239
|
+
:value => 44
|
240
|
+
},
|
241
|
+
{
|
242
|
+
:name => "Mparam",
|
243
|
+
:value => 55
|
244
|
+
},
|
245
|
+
{
|
246
|
+
:name => "AMCparam",
|
247
|
+
:value => 22
|
248
|
+
},
|
249
|
+
{
|
250
|
+
:name => "Eparam",
|
251
|
+
:value => 222
|
252
|
+
},
|
253
|
+
{
|
254
|
+
:name => "AECparam",
|
255
|
+
:value => 21
|
256
|
+
},
|
257
|
+
{
|
258
|
+
:name => "BASOparam",
|
259
|
+
:value => 222
|
260
|
+
},
|
261
|
+
{
|
262
|
+
:name => "ABCparam",
|
263
|
+
:value => 300
|
264
|
+
},
|
265
|
+
{
|
266
|
+
:name => "RBCparam",
|
267
|
+
:value => 2.22
|
268
|
+
},
|
269
|
+
{
|
270
|
+
:name => "HBparam",
|
271
|
+
:value => 19
|
272
|
+
},
|
273
|
+
{
|
274
|
+
:name => "HCTparam",
|
275
|
+
:value => 22
|
276
|
+
},
|
277
|
+
{
|
278
|
+
:name => "MCVparam",
|
279
|
+
:value => 222
|
280
|
+
},
|
281
|
+
{
|
282
|
+
:name => "MCHparam",
|
283
|
+
:value => 21
|
284
|
+
},
|
285
|
+
{
|
286
|
+
:name => "MCHCparam",
|
287
|
+
:value => 10
|
288
|
+
},
|
289
|
+
{
|
290
|
+
:name => "MCVparam",
|
291
|
+
:value => 222
|
292
|
+
},
|
293
|
+
{
|
294
|
+
:name => "RDWCVparam",
|
295
|
+
:value => 12
|
296
|
+
},
|
297
|
+
{
|
298
|
+
:name => "PCparam",
|
299
|
+
:value => 1.22322
|
300
|
+
}
|
301
|
+
]
|
302
|
+
}
|
303
|
+
]
|
304
|
+
=end
|
305
|
+
## pretty simple, if the value is not already there it will be updated, otherwise it won't be.
|
306
|
+
def update_LIS
|
307
|
+
prepare_redis
|
308
|
+
patients_to_process = $redis.llen("patients") > 0
|
309
|
+
while patients_to_process == true
|
310
|
+
if patient_results = $redis.rpoplpush("patients","processing")
|
311
|
+
patient_results = JSON.parse(patient_results)
|
312
|
+
update(patient_results)
|
313
|
+
patients_to_process = $redis.llen("patients") > 0
|
314
|
+
else
|
315
|
+
patients_to_process = false
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
## this method should be overriden.
|
321
|
+
## will poll the lis, and store locally, in a redis sorted set the following:
|
322
|
+
## key => specimen_id
|
323
|
+
## value => tests designated for that specimen.
|
324
|
+
## score => time of requisition of that specimen.
|
325
|
+
## name of the sorted set can be defined in the class that inherits from adapter, or will default to "requisitions"
|
326
|
+
## when a query is sent from any laboratory equipment to the local ASTMServer, it will query the redis sorted set, for the test information.
|
327
|
+
## so this poller basically constantly replicates the cloud based test information to the local server.
|
328
|
+
def poll_LIS_for_requisition
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
def poll
|
333
|
+
pre_poll_LIS
|
334
|
+
poll_LIS_for_requisition
|
335
|
+
update_LIS
|
336
|
+
post_poll_LIS
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
@@ -0,0 +1,2 @@
|
|
1
|
+
---
|
2
|
+
default: '{"client_id":"555057029489-sel4u1t4tnv3qicdhsssp9phn3r8tvrj.apps.googleusercontent.com","access_token":"ya29.GlxjBtK4IVi08eyrCDI6WaEgkZnjhNK7wvJIw8vd7PzzfLiMfuphIlZFk4t8UUMSROqQIsBw7RQXTifEXE2FDjvjdLoeEr-sDj1xHVifr53_V-ZcJlbOXBCUexjxTQ","refresh_token":"1/SPwjKXMExoiU43WCJxPJYPd2cj0Hhb1Ms2ONe3KgIIs","scope":["https://www.googleapis.com/auth/documents","https://www.googleapis.com/auth/drive","https://www.googleapis.com/auth/script.projects","https://www.googleapis.com/auth/spreadsheets"],"expiration_time_millis":1543480295000}'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'em-rubyserial'
|
4
|
+
require "active_support/all"
|
5
|
+
require "json"
|
6
|
+
require "redis"
|
7
|
+
|
8
|
+
class AstmServer
|
9
|
+
|
10
|
+
include LabInterface
|
11
|
+
|
12
|
+
def self.log(message)
|
13
|
+
puts "" + message
|
14
|
+
$redis.zadd("ruby_astm_log",Time.now.to_i,message)
|
15
|
+
end
|
16
|
+
|
17
|
+
$ENQ = "[5]"
|
18
|
+
$start_text = "[2]"
|
19
|
+
$end_text = "[3]"
|
20
|
+
$record_end = "[13]"
|
21
|
+
$frame_end = "[10]"
|
22
|
+
|
23
|
+
def initialize(server_ip,server_port,mpg,respond_to_queries=false)
|
24
|
+
$redis = Redis.new
|
25
|
+
AstmServer.log("Initializing AstmServer")
|
26
|
+
self.server_ip = server_ip || "192.168.1.14"
|
27
|
+
self.server_port = server_port || 3000
|
28
|
+
self.respond_to_queries = respond_to_queries
|
29
|
+
$mappings = JSON.parse(IO.read(mpg || ("mappings.json")))
|
30
|
+
end
|
31
|
+
|
32
|
+
def start_server
|
33
|
+
EventMachine.run {
|
34
|
+
self.ethernet_server = EventMachine::start_server self.server_ip, self.server_port, LabInterface
|
35
|
+
AstmServer.log("Running ETHERNET SERVER on #{server_port}")
|
36
|
+
#serial = EventMachine.open_serial('/dev/ttyUSB0', 9600, 8)
|
37
|
+
#serial.on_data do |data|
|
38
|
+
# puts "got some data"
|
39
|
+
# puts data.to_s
|
40
|
+
# serial.send_data("\X06")
|
41
|
+
#end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
## now we need to run the server and poller.
|
46
|
+
## thats what we need.
|
47
|
+
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Frame
|
2
|
+
|
3
|
+
attr_accessor :machine_model
|
4
|
+
attr_accessor :astm_version
|
5
|
+
attr_accessor :records
|
6
|
+
|
7
|
+
def initalize(args)
|
8
|
+
self.records = []
|
9
|
+
super(args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.is_start_frame?(l)
|
13
|
+
line_without_control_char = l[1..-1]
|
14
|
+
return line_without_control_char =~ /\d+H/
|
15
|
+
#k = l[0].bytes
|
16
|
+
#if k.to_s == "[5]"
|
17
|
+
# puts "ENQ"
|
18
|
+
#end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.is_end_frame?(line)
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Header
|
2
|
+
attr_accessor :machine_name
|
3
|
+
attr_accessor :patients
|
4
|
+
attr_accessor :queries
|
5
|
+
attr_accessor :response_sent
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
if args[:line]
|
9
|
+
line = args[:line]
|
10
|
+
unless line.fields[4].empty?
|
11
|
+
fields = line.fields[4].split(/\^/)
|
12
|
+
self.machine_name = fields[0].strip
|
13
|
+
end
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
self.patients = []
|
18
|
+
self.queries = []
|
19
|
+
self.response_sent = false
|
20
|
+
end
|
21
|
+
|
22
|
+
## pushes each patient into a redis list called "patients"
|
23
|
+
def commit
|
24
|
+
self.patients.map{|patient| $redis.lpush("patients",patient.to_json)}
|
25
|
+
#puts JSON.pretty_generate(JSON.parse(self.to_json))
|
26
|
+
end
|
27
|
+
|
28
|
+
## used to respond to queries.
|
29
|
+
## @return[String] response_to_query : response to the header query.
|
30
|
+
def build_responses
|
31
|
+
responses = self.queries.map {|query|
|
32
|
+
header_response = "1H|\`^&||||||||||P|E 1394-97|#{Time.now.strftime("%Y%m%d%H%M%S")}\r"
|
33
|
+
query.response = header_response + query.build_response
|
34
|
+
query.response
|
35
|
+
}
|
36
|
+
responses
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_json
|
40
|
+
hash = {}
|
41
|
+
self.instance_variables.each do |x|
|
42
|
+
hash[x] = self.instance_variable_get x
|
43
|
+
end
|
44
|
+
return hash.to_json
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require "active_support/all"
|
2
|
+
|
3
|
+
module LabInterface
|
4
|
+
|
5
|
+
ACK = "\x06"
|
6
|
+
ENQ = "\x05"
|
7
|
+
STX = "\x02"
|
8
|
+
LF = "\x10"
|
9
|
+
CR = "\x13"
|
10
|
+
ETX = "\x03"
|
11
|
+
EOT = "\x04"
|
12
|
+
|
13
|
+
mattr_accessor :headers
|
14
|
+
mattr_accessor :ethernet_server
|
15
|
+
mattr_accessor :server_ip
|
16
|
+
mattr_accessor :server_port
|
17
|
+
mattr_accessor :mapping
|
18
|
+
mattr_accessor :respond_to_queries
|
19
|
+
|
20
|
+
|
21
|
+
## returns the root directory of the gem.
|
22
|
+
def root
|
23
|
+
File.dirname __dir__
|
24
|
+
end
|
25
|
+
|
26
|
+
## can process a file which contains ASTM output.
|
27
|
+
## this method is added so that you can load previously generated ASTM output into your database
|
28
|
+
## it also simplifies testing.
|
29
|
+
def process_text_file(full_file_path)
|
30
|
+
#full_file_path ||= File.join root,'../test','resources','sysmex_550_sample.txt'
|
31
|
+
IO.read(full_file_path).each_line do |line|
|
32
|
+
process_text(line)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def terminator
|
37
|
+
"L|1|N\r"
|
38
|
+
end
|
39
|
+
|
40
|
+
def checksum(input)
|
41
|
+
strString = input
|
42
|
+
checksum = strString.sum
|
43
|
+
b = checksum.to_s(16)
|
44
|
+
strCksm = b[-2..-1]
|
45
|
+
if strCksm.length < 2
|
46
|
+
for i in strString.length..1
|
47
|
+
strCksm = "0" + strCksm
|
48
|
+
end
|
49
|
+
end
|
50
|
+
strCksm.upcase
|
51
|
+
end
|
52
|
+
|
53
|
+
def receive_data(data)
|
54
|
+
|
55
|
+
puts "incoming data bytes."
|
56
|
+
|
57
|
+
concat = ""
|
58
|
+
|
59
|
+
|
60
|
+
puts data.bytes.to_a.to_s
|
61
|
+
|
62
|
+
data.bytes.to_a.each do |byte|
|
63
|
+
x = [byte].pack('c*').force_encoding('UTF-8')
|
64
|
+
if x == "\r"
|
65
|
+
concat+="\n"
|
66
|
+
elsif x == "\n"
|
67
|
+
#puts "new line found --- "
|
68
|
+
concat+=x
|
69
|
+
#puts "last thing in concat."
|
70
|
+
#puts concat[-1].to_s
|
71
|
+
else
|
72
|
+
concat+=x
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
#puts "concat is:"
|
77
|
+
puts concat.to_s
|
78
|
+
process_text(concat)
|
79
|
+
|
80
|
+
if data.bytes.to_a[0] == 4
|
81
|
+
puts "sent ENQ"
|
82
|
+
send_data(ENQ)
|
83
|
+
elsif data.bytes.to_a[0] == 6
|
84
|
+
## so now the middle part has to be
|
85
|
+
=begin
|
86
|
+
response = STX + "1H|\`^&||||||||||P|E 1394-97|#{Time.now.strftime("%Y%m%d%H%M%S")}\r" + terminator + ETX + checksum("1H|\`^&||||||||||P|E 1394-97|#{Time.now.strftime("%Y%m%d%H%M%S")}\r" + terminator) + "\r"
|
87
|
+
response = response.bytes.to_a
|
88
|
+
response << 10
|
89
|
+
send_data(response.pack('c*'))
|
90
|
+
=end
|
91
|
+
self.headers[-1].build_responses.each do |response|
|
92
|
+
message_checksum = checksum(response + terminator + ETX)
|
93
|
+
puts "Calculated checksum is: #{message_checksum}"
|
94
|
+
final_resp = STX + response + terminator + ETX + message_checksum + "\r"
|
95
|
+
final_resp_arr = final_resp.bytes.to_a
|
96
|
+
final_resp_arr << 10
|
97
|
+
puts final_resp_arr.to_s
|
98
|
+
if (self.headers[-1].response_sent == false)
|
99
|
+
send_data(final_resp_arr.pack('c*'))
|
100
|
+
self.headers[-1].response_sent = true
|
101
|
+
else
|
102
|
+
puts "response was already sent."
|
103
|
+
send_data(EOT)
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
else
|
108
|
+
## send the header
|
109
|
+
puts "--------- SENT ACK -----------"
|
110
|
+
send_data(ACK)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def send_enq
|
115
|
+
puts "enq as bytes is:"
|
116
|
+
puts ENQ.unpack('c*')
|
117
|
+
send_data(ENQ)
|
118
|
+
end
|
119
|
+
|
120
|
+
def process_text(text)
|
121
|
+
text.split("\n").each do |l|
|
122
|
+
line = Line.new({:text => l})
|
123
|
+
process_type(line)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def process_type(line)
|
128
|
+
case line.type
|
129
|
+
when "Header"
|
130
|
+
header = Header.new({:line => line})
|
131
|
+
self.headers ||= []
|
132
|
+
self.headers << header
|
133
|
+
when "Query"
|
134
|
+
query = Query.new({:line => line})
|
135
|
+
self.headers[-1].queries << query
|
136
|
+
when "Patient"
|
137
|
+
patient = Patient.new({:line => line})
|
138
|
+
self.headers[-1].patients << patient
|
139
|
+
when "Order"
|
140
|
+
order = Order.new({:line => line})
|
141
|
+
self.headers[-1].patients[-1].orders << order
|
142
|
+
when "Result"
|
143
|
+
result = Result.new({:line => line})
|
144
|
+
self.headers[-1].patients[-1].orders[-1].results[result.name] = result
|
145
|
+
when "Terminator"
|
146
|
+
self.headers[-1].commit
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
=begin
|
151
|
+
1.STX + response + LF + ETX
|
152
|
+
2.response
|
153
|
+
3.STX + response + "L|1|N\r" + ETX
|
154
|
+
4.response + "L|1|N\r"
|
155
|
+
=end
|
156
|
+
def unbind
|
157
|
+
puts "-- someone disconnected from the echo server!"
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class Line
|
2
|
+
|
3
|
+
TYPES = {
|
4
|
+
"H" => "Header",
|
5
|
+
"P" => "Patient",
|
6
|
+
"Q" => "Query",
|
7
|
+
"O" => "Order",
|
8
|
+
"R" => "Result",
|
9
|
+
"L" => "Terminator"
|
10
|
+
}
|
11
|
+
|
12
|
+
attr_accessor :text
|
13
|
+
|
14
|
+
attr_accessor :fields
|
15
|
+
|
16
|
+
attr_accessor :type
|
17
|
+
|
18
|
+
########################################################
|
19
|
+
##
|
20
|
+
##
|
21
|
+
## HEADER ATTRIBUTES
|
22
|
+
##
|
23
|
+
##
|
24
|
+
########################################################
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
## sets the types, and fields
|
29
|
+
## we can have processing based on line.
|
30
|
+
def initialize(args)
|
31
|
+
self.fields = []
|
32
|
+
raise "no text provided" unless args[:text]
|
33
|
+
if args[:text]
|
34
|
+
args[:text].split(/\|/).each do |field|
|
35
|
+
self.fields << field
|
36
|
+
end
|
37
|
+
end
|
38
|
+
detect_type
|
39
|
+
end
|
40
|
+
|
41
|
+
def detect_type
|
42
|
+
#puts "detecting line type: #{self.text}"
|
43
|
+
line_type = self.fields[0]
|
44
|
+
line_type.scan(/(?<ltype>[A-Z])/) { |ltype|
|
45
|
+
#puts "got ltype as: #{ltype[0]}"
|
46
|
+
#puts Line::TYPES.to_s
|
47
|
+
if Line::TYPES[ltype[0]]
|
48
|
+
self.type = Line::TYPES[ltype[0]]
|
49
|
+
#puts "assigning type as: #{self.type}"
|
50
|
+
end
|
51
|
+
}
|
52
|
+
|
53
|
+
=begin
|
54
|
+
if self.fields[0][1..-1] =~/#{type}/
|
55
|
+
puts "got type: #{Line::TYPES[type]}"
|
56
|
+
self.type = Line::TYPES[type]
|
57
|
+
break
|
58
|
+
end
|
59
|
+
=end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class Order
|
2
|
+
## => patient id.
|
3
|
+
## => should match the specimen id that comes into the query.
|
4
|
+
## index (2)
|
5
|
+
attr_accessor :id
|
6
|
+
|
7
|
+
## this should be same as the one for patient.
|
8
|
+
## index (1)
|
9
|
+
attr_accessor :sequence_number
|
10
|
+
|
11
|
+
## => key : result name.
|
12
|
+
## => value : result object
|
13
|
+
attr_accessor :results
|
14
|
+
|
15
|
+
## the list of tests that need to be performed.
|
16
|
+
## each should be prefixed by three carets
|
17
|
+
## index (4)
|
18
|
+
attr_accessor :tests
|
19
|
+
## the specimen type.
|
20
|
+
|
21
|
+
## index (15)
|
22
|
+
attr_accessor :specimen_type
|
23
|
+
|
24
|
+
|
25
|
+
## the order type, like stat, routine etc.
|
26
|
+
## index (5)
|
27
|
+
attr_accessor :priority
|
28
|
+
|
29
|
+
## the date and time of collection
|
30
|
+
## index (7)
|
31
|
+
attr_accessor :date_time
|
32
|
+
|
33
|
+
## action code
|
34
|
+
## index (11)
|
35
|
+
attr_accessor :action_code
|
36
|
+
|
37
|
+
def initialize(args)
|
38
|
+
if args[:line]
|
39
|
+
line = args[:line]
|
40
|
+
if line.fields[2]
|
41
|
+
line.fields[2].strip.scan(/(?<specimen_id>[^\^]+)/) { |specimen_id|
|
42
|
+
self.id ||= specimen_id
|
43
|
+
}
|
44
|
+
elsif line.fields[3]
|
45
|
+
## for the sysmex xn-550 this is the regex.
|
46
|
+
line.fields[3].strip.scan(/(?<tube_rack>\d+\^)+(?<patient_id>.+)\^/) { |tube_rack,patient_id| self.id = patient_id.strip}
|
47
|
+
end
|
48
|
+
else
|
49
|
+
self.sequence_number = args[:sequence_number]
|
50
|
+
self.tests = args[:tests]
|
51
|
+
self.id = args[:specimen_id]
|
52
|
+
self.specimen_type = args[:specimen_type]
|
53
|
+
self.tests = args[:tests]
|
54
|
+
self.date_time = args[:date_time]
|
55
|
+
self.priority = args[:priority]
|
56
|
+
self.action_code = args[:action_code]
|
57
|
+
end
|
58
|
+
self.results = {}
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_response
|
62
|
+
|
63
|
+
raise "provide a sequence number" if self.sequence_number.blank?
|
64
|
+
raise "provide a specimen id" if self.id.blank?
|
65
|
+
raise "provide a list of tests" if self.tests.blank?
|
66
|
+
raise "provide a test priority" if self.priority.blank?
|
67
|
+
|
68
|
+
if self.specimen_type.blank?
|
69
|
+
#puts "no specimen type has been provided, sending SERUM"
|
70
|
+
end
|
71
|
+
|
72
|
+
"O|#{self.sequence_number}|#{self.id}^01||^^^#{self.tests.join('^^^')}|#{self.priority}||#{Time.now.strftime("%Y%m%d%H%M%S")}||||N||||#{self.specimen_type || 'SERUM'}\r"
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Patient
|
2
|
+
|
3
|
+
## sequence number can only be from 0 -- 9.
|
4
|
+
attr_accessor :sequence_number
|
5
|
+
attr_accessor :patient_id
|
6
|
+
attr_accessor :orders
|
7
|
+
|
8
|
+
def initialize(args)
|
9
|
+
if args[:line]
|
10
|
+
line = args[:line]
|
11
|
+
self.sequence_number = line.fields[1].to_i
|
12
|
+
self.orders = []
|
13
|
+
else
|
14
|
+
self.sequence_number = args[:sequence_number]
|
15
|
+
self.patient_id = args[:patient_id]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
## patient id.
|
20
|
+
def build_response
|
21
|
+
"P|#{self.sequence_number}|#{self.patient_id}|||||||||||||||\r"
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_json
|
25
|
+
hash = {}
|
26
|
+
self.instance_variables.each do |x|
|
27
|
+
hash[x] = self.instance_variable_get x
|
28
|
+
end
|
29
|
+
return hash.to_json
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Query
|
2
|
+
|
3
|
+
attr_accessor :sample_id
|
4
|
+
|
5
|
+
attr_accessor :response
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
line = args[:line]
|
9
|
+
unless line.fields[2].empty?
|
10
|
+
fields = line.fields[2].split(/\^/)
|
11
|
+
self.sample_id = fields[1].strip
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
## each query will build one patient and one order inside it.
|
16
|
+
## the order can have many tests.
|
17
|
+
def build_response(variables=nil)
|
18
|
+
variables ||= {
|
19
|
+
:sequence_number => "0",
|
20
|
+
:patient_id => "abcde",
|
21
|
+
:specimen_id => self.sample_id,
|
22
|
+
:tests => ["TRIG"],
|
23
|
+
:priority => "R"
|
24
|
+
}
|
25
|
+
|
26
|
+
patient = Patient.new({:sequence_number => "0", :patient_id => "abcde"})
|
27
|
+
|
28
|
+
order = Order.new({:sequence_number => patient.sequence_number, :specimen_id => variables[:specimen_id], :tests => variables[:tests], :priority => variables[:priority]})
|
29
|
+
|
30
|
+
return patient.build_response + order.build_response
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Result
|
2
|
+
attr_accessor :name
|
3
|
+
attr_accessor :value
|
4
|
+
attr_accessor :units
|
5
|
+
attr_accessor :flags
|
6
|
+
attr_accessor :timestamp
|
7
|
+
attr_accessor :reference_ranges
|
8
|
+
attr_accessor :dilution
|
9
|
+
|
10
|
+
## here will call mappings and check the result correlation
|
11
|
+
def initialize(args)
|
12
|
+
if args[:line]
|
13
|
+
line = args[:line]
|
14
|
+
line.fields[2].scan(/\^+(?<name>[A-Za-z0-9\%\#]+)\^?(?<dilution>\d+)?/) { |name,dilution|
|
15
|
+
self.name = lookup_mapping(name)
|
16
|
+
self.dilution = dilution
|
17
|
+
}
|
18
|
+
self.value = line.fields[3].strip
|
19
|
+
self.reference_ranges = line.fields[5].strip
|
20
|
+
self.flags = line.fields[6].strip
|
21
|
+
line.fields[12].strip.scan(/(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})(?<hours>\d{2})(?<minutes>\d{2})(?<seconds>\d{2})/) {|year,month,day,hours,minutes,seconds|
|
22
|
+
self.timestamp = Time.new(year,month,day,hours,minutes,seconds)
|
23
|
+
}
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
## @return[String] the name defined in the mappings.json file, or the name that wqs passed in.
|
30
|
+
def lookup_mapping(name)
|
31
|
+
$mappings[name] ? $mappings[name]["LIS_CODE"] : name
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/lib/ruby_astm.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "ruby_astm/query"
|
2
|
+
require "ruby_astm/frame"
|
3
|
+
require "ruby_astm/header"
|
4
|
+
require "ruby_astm/lab_interface"
|
5
|
+
require "ruby_astm/line"
|
6
|
+
require "ruby_astm/order"
|
7
|
+
require "ruby_astm/patient"
|
8
|
+
require "ruby_astm/result"
|
9
|
+
require "ruby_astm/astm_server"
|
10
|
+
require "publisher/adapter"
|
11
|
+
require "publisher/google_lab_interface"
|
12
|
+
require "publisher/poller"
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby_astm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bhargav Raut
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: eventmachine
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: em-rubyserial
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: json
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redis
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: This gem provides a server that can handle communication from medical
|
84
|
+
instruments that send/receive information on the ASTM protocol.
|
85
|
+
email: bhargav.r.raut@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- lib/publisher/adapter.rb
|
91
|
+
- lib/publisher/credentials.json
|
92
|
+
- lib/publisher/google_lab_interface.rb
|
93
|
+
- lib/publisher/poller.rb
|
94
|
+
- lib/publisher/token.yaml
|
95
|
+
- lib/ruby_astm.rb
|
96
|
+
- lib/ruby_astm/astm_server.rb
|
97
|
+
- lib/ruby_astm/frame.rb
|
98
|
+
- lib/ruby_astm/header.rb
|
99
|
+
- lib/ruby_astm/lab_interface.rb
|
100
|
+
- lib/ruby_astm/line.rb
|
101
|
+
- lib/ruby_astm/order.rb
|
102
|
+
- lib/ruby_astm/patient.rb
|
103
|
+
- lib/ruby_astm/query.rb
|
104
|
+
- lib/ruby_astm/result.rb
|
105
|
+
homepage: https://www.github/com/wordjelly/ruby_astm
|
106
|
+
licenses:
|
107
|
+
- MIT
|
108
|
+
metadata: {}
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options: []
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 2.6.14.1
|
126
|
+
signing_key:
|
127
|
+
specification_version: 4
|
128
|
+
summary: A Ruby gem to interface with Medical instruments that work on the ASTM protocol
|
129
|
+
test_files: []
|