ruby_astm 1.4.1 → 1.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/mappings.json +199 -79
- data/lib/publisher/adapter.rb +0 -0
- data/lib/publisher/google_lab_interface.rb +2 -2
- data/lib/publisher/pf_lab_interface.rb +484 -0
- data/lib/publisher/poller.rb +12 -3
- data/lib/publisher/real_time_db.rb +76 -0
- data/lib/ruby_astm.rb +21 -17
- data/lib/ruby_astm/HL7/hl7_header.rb +0 -0
- data/lib/ruby_astm/HL7/hl7_observation.rb +0 -0
- data/lib/ruby_astm/HL7/hl7_order.rb +0 -0
- data/lib/ruby_astm/HL7/hl7_patient.rb +0 -0
- data/lib/ruby_astm/astm_server.rb +1 -0
- data/lib/ruby_astm/custom/siemens_abg_electrolyte_server.rb +315 -0
- data/lib/ruby_astm/frame.rb +0 -0
- data/lib/ruby_astm/header.rb +2 -4
- data/lib/ruby_astm/lab_interface.rb +113 -25
- data/lib/ruby_astm/line.rb +0 -0
- data/lib/ruby_astm/order.rb +1 -1
- data/lib/ruby_astm/patient.rb +1 -1
- data/lib/ruby_astm/query.rb +0 -0
- data/lib/ruby_astm/result.rb +1 -1
- data/lib/ruby_astm/usb_module.rb +0 -0
- metadata +35 -4
data/lib/publisher/poller.rb
CHANGED
@@ -24,8 +24,9 @@ class Poller
|
|
24
24
|
RUNNING = "running"
|
25
25
|
COMPLETED = "completed"
|
26
26
|
|
27
|
-
def initialize(mpg=nil)
|
27
|
+
def initialize(mpg=nil,real_time_db=nil)
|
28
28
|
$redis = Redis.new
|
29
|
+
$real_time_db = real_time_db
|
29
30
|
## this mapping is from MACHINE CODE AS THE KEY
|
30
31
|
$mappings = JSON.parse(IO.read(mpg || AstmServer.default_mappings))
|
31
32
|
## INVERTING THE MAPPINGS, GIVES US THE LIS CODE AS THE KEY.
|
@@ -177,12 +178,18 @@ class Poller
|
|
177
178
|
puts package_components.to_s
|
178
179
|
|
179
180
|
package_components.each do |component|
|
180
|
-
puts "doing component: #{component}"
|
181
|
+
#puts "doing component: #{component}"
|
181
182
|
## these are the machine codes.
|
182
183
|
## so to get the tube, you have to get it from the inverted mappings.
|
183
184
|
## cant get directly like this.
|
185
|
+
#puts "inverted mappings"
|
186
|
+
#puts $inverted_mappings
|
184
187
|
component_machine_code = $inverted_mappings[component]
|
185
|
-
puts "component machine code: #{component_machine_code}"
|
188
|
+
#puts "component machine code: #{component_machine_code}"
|
189
|
+
|
190
|
+
## for eg plasma tube can do all the tests
|
191
|
+
## so can serum
|
192
|
+
## but we use the plasma tube only for some.
|
186
193
|
|
187
194
|
tube = $mappings[component_machine_code]["TUBE"]
|
188
195
|
tube_key = nil
|
@@ -215,6 +222,8 @@ class Poller
|
|
215
222
|
|
216
223
|
## @param[Integer] epoch : the epoch at which these tests were requested.
|
217
224
|
## @param[Hash] tests : {"EDTA:barcode" => [MCV,MCH,MCHC...]}
|
225
|
+
## the test codes here are the lis_codes
|
226
|
+
## so we need the inverted mappings for this
|
218
227
|
def merge_with_requisitions_hash(epoch,tests)
|
219
228
|
## so we basically now add this to the epoch ?
|
220
229
|
## or a sorted set ?
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rest-firebase'
|
2
|
+
require "resolv-replace"
|
3
|
+
class RealTimeDb
|
4
|
+
|
5
|
+
SITE_URL = ENV["FIREBASE_SITE"]
|
6
|
+
SECRET = ENV["FIREBASE_SECRET"]
|
7
|
+
attr_accessor :connection
|
8
|
+
attr_accessor :work_allotment_hash
|
9
|
+
WORK_TYPES = {
|
10
|
+
"IMMUNO" => "",
|
11
|
+
"BIOCHEM" => "",
|
12
|
+
"BIOCHEM-EXL" => "",
|
13
|
+
"BIOCHEM-ELECTROLYTE" => "",
|
14
|
+
"HEMAT" => "",
|
15
|
+
"URINE" => "",
|
16
|
+
"OUTSOURCE" => ""
|
17
|
+
}
|
18
|
+
|
19
|
+
## first i email myself the site and secret
|
20
|
+
## then we proceed.
|
21
|
+
|
22
|
+
## @param[Hash] work_allotment_hash :
|
23
|
+
## key => one of the work types
|
24
|
+
## value => name of a worker
|
25
|
+
def initialize(work_allotment_hash)
|
26
|
+
self.connection = RestFirebase.new :site => SITE_URL,
|
27
|
+
:secret => SECRET
|
28
|
+
puts "initialized"
|
29
|
+
self.work_allotment_hash = work_allotment_hash || WORK_TYPES
|
30
|
+
end
|
31
|
+
|
32
|
+
def open_event_stream
|
33
|
+
es = self.connection.event_source('users/tom')
|
34
|
+
es.onopen { |sock| p sock } # Called when connected
|
35
|
+
es.onmessage{ |event, data, sock| p event, data } # Called for each message
|
36
|
+
es.onerror { |error, sock| p error } # Called whenever there's an error
|
37
|
+
# Extra: If we return true in onreconnect callback, it would automatically
|
38
|
+
# reconnect the node for us if disconnected.
|
39
|
+
@reconnect = true
|
40
|
+
|
41
|
+
es.onreconnect{ |error, sock| p error; @reconnect }
|
42
|
+
|
43
|
+
# Start making the request
|
44
|
+
es.start
|
45
|
+
|
46
|
+
self.connection.wait
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
## we pass the real_time_data instance into the
|
53
|
+
|
54
|
+
def assign_test(barcode,tests,mappings)
|
55
|
+
## so do we get the name of the worker.
|
56
|
+
inverted_mappings = {}
|
57
|
+
mappings.keys.each do |machine_code|
|
58
|
+
lis_code = mappings[machine_code][LIS_CODE]
|
59
|
+
inverted_mappings[lis_code] = mappings[machine_code]
|
60
|
+
end
|
61
|
+
worker_hash = {}
|
62
|
+
tests.each do |lis_code|
|
63
|
+
worker_name = "NO_ONE"
|
64
|
+
unless inverted_mappings[lis_code].blank?
|
65
|
+
test_type = inverted_mappings[lis_code]["TYPE"]
|
66
|
+
worker_name = self.work_allotment_hash[test_type]
|
67
|
+
end
|
68
|
+
worker_hash[worker_name] ||= []
|
69
|
+
worker_hash[worker_name] << lis_code
|
70
|
+
end
|
71
|
+
worker_hash.keys.each do |worker_name|
|
72
|
+
#self.connection.post("lab/work/#{worker_name}", :tests => worker_hash[worker_name], :barcode => barcode, :timestamp => Time.now.to_i)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
data/lib/ruby_astm.rb
CHANGED
@@ -1,17 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
1
|
+
require_relative "ruby_astm/usb_module"
|
2
|
+
require_relative "ruby_astm/query"
|
3
|
+
require_relative "ruby_astm/frame"
|
4
|
+
require_relative "ruby_astm/header"
|
5
|
+
require_relative "ruby_astm/lab_interface"
|
6
|
+
require_relative "ruby_astm/line"
|
7
|
+
require_relative "ruby_astm/order"
|
8
|
+
require_relative "ruby_astm/patient"
|
9
|
+
require_relative "ruby_astm/result"
|
10
|
+
require_relative "ruby_astm/astm_server"
|
11
|
+
require_relative "publisher/adapter"
|
12
|
+
require_relative "publisher/google_lab_interface"
|
13
|
+
require_relative "publisher/poller"
|
14
|
+
require_relative "publisher/real_time_db"
|
15
|
+
require_relative "publisher/pf_lab_interface"
|
16
|
+
require_relative "ruby_astm/HL7/hl7_header"
|
17
|
+
require_relative "ruby_astm/HL7/hl7_patient"
|
18
|
+
require_relative "ruby_astm/HL7/hl7_order"
|
19
|
+
require_relative "ruby_astm/HL7/hl7_observation"
|
20
|
+
require_relative "ruby_astm/custom/siemens_abg_electrolyte_server"
|
21
|
+
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,315 @@
|
|
1
|
+
class SiemensAbgElectrolyteServer < AstmServer
|
2
|
+
|
3
|
+
SIEMENS_ELECTROLYTE_END = [10,10,10,10]
|
4
|
+
ELECTROLYTE_START = [45, 45, 45, 32]
|
5
|
+
attr_accessor :current_text_segment
|
6
|
+
|
7
|
+
def get_po2
|
8
|
+
m = []
|
9
|
+
self.current_text_segment.scan(/pO2\s+(?<k>(\d+)(\.\d)*)(\^|v)?\s+mmHg/) do |l|
|
10
|
+
n = Regexp.last_match
|
11
|
+
m << n[:k].to_s
|
12
|
+
end
|
13
|
+
raise "more than one result #{m.to_s}" if (m.size > 1)
|
14
|
+
return m[0] if m.size == 1
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_pco2
|
19
|
+
m = []
|
20
|
+
self.current_text_segment.scan(/pCO2\s+(?<k>(\d+)(\.\d)*)(\^|v)?\s+mmHg/) do |l|
|
21
|
+
n = Regexp.last_match
|
22
|
+
m << n[:k].to_s
|
23
|
+
end
|
24
|
+
raise "more than one result #{m.to_s}" if (m.size > 1)
|
25
|
+
return m[0] if m.size == 1
|
26
|
+
return nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_ph
|
30
|
+
m = []
|
31
|
+
self.current_text_segment.scan(/pH\s+(?<k>(\d+)[\.\d]*)(\^|v)?\s+$/) do |l|
|
32
|
+
n = Regexp.last_match
|
33
|
+
m << n[:k].to_s
|
34
|
+
end
|
35
|
+
raise "more than one result #{m.to_s}" if (m.size > 1)
|
36
|
+
return m[0] if m.size == 1
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_na
|
41
|
+
m = []
|
42
|
+
self.current_text_segment.scan(/Na\+\s+(?<k>(\d+)[\.\d]*)(\^|v)?\s+mmol\/L/) do |l|
|
43
|
+
n = Regexp.last_match
|
44
|
+
m << n[:k].to_s
|
45
|
+
end
|
46
|
+
raise "more than one result #{m.to_s}" if (m.size > 1)
|
47
|
+
return m[0] if m.size == 1
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_k
|
52
|
+
m = []
|
53
|
+
self.current_text_segment.scan(/K\+\s+(?<k>(\d+)[\.\d]*)(\^|v)?\s+mmol\/L/) do |l|
|
54
|
+
n = Regexp.last_match
|
55
|
+
m << n[:k].to_s
|
56
|
+
end
|
57
|
+
raise "more than one result #{m.to_s}" if (m.size > 1)
|
58
|
+
return m[0] if m.size == 1
|
59
|
+
return nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_cl
|
63
|
+
m = []
|
64
|
+
self.current_text_segment.scan(/Cl\-\s+(?<k>(\d+)[\.\d]*)(\^|v)?\s+mmol\/L/) do |l|
|
65
|
+
n = Regexp.last_match
|
66
|
+
m << n[:k].to_s
|
67
|
+
end
|
68
|
+
raise "more than one result #{m.to_s}" if (m.size > 1)
|
69
|
+
return m[0] if m.size == 1
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_patient_id
|
74
|
+
m = []
|
75
|
+
self.current_text_segment.scan(/Patient\s+ID\s+(?<k>\d+)$/) do |l|
|
76
|
+
n = Regexp.last_match
|
77
|
+
m << n[:k].to_s
|
78
|
+
end
|
79
|
+
raise "more than one result #{m.to_s}" if (m.size > 1)
|
80
|
+
return m[0] if m.size == 1
|
81
|
+
return nil
|
82
|
+
end
|
83
|
+
|
84
|
+
## we override the lab interface methods
|
85
|
+
## and we don't pollute the lab interface itself.
|
86
|
+
## as this is a custom analyzer.
|
87
|
+
## @param[Array] :
|
88
|
+
## [[bytes],[bytes]....]
|
89
|
+
def process_electrolytes(data_bytes)
|
90
|
+
#puts "came to process electrolytes_plain_text"
|
91
|
+
byte_arr = data_bytes.flatten
|
92
|
+
#puts "the end part of the arr is"
|
93
|
+
return if byte_arr[-4..-1] != SIEMENS_ELECTROLYTE_END
|
94
|
+
self.data_bytes = []
|
95
|
+
concat = ""
|
96
|
+
byte_arr.each do |byte|
|
97
|
+
x = [byte].pack('c*').force_encoding('UTF-8')
|
98
|
+
if x == "\r"
|
99
|
+
concat+="\n"
|
100
|
+
elsif x == "\n"
|
101
|
+
#puts "new line found --- "
|
102
|
+
concat+=x
|
103
|
+
#puts "last thing in concat."
|
104
|
+
#puts concat[-1].to_s
|
105
|
+
else
|
106
|
+
concat+=x
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
self.headers ||= [Header.new]
|
111
|
+
concat.split("--------------------------------").each do |record|
|
112
|
+
|
113
|
+
self.current_text_segment = record
|
114
|
+
if patient_id = get_patient_id
|
115
|
+
self.headers[-1].patients ||= []
|
116
|
+
p = Patient.new
|
117
|
+
p.patient_id = patient_id
|
118
|
+
p.orders ||= []
|
119
|
+
o = Order.new
|
120
|
+
o.results ||= {}
|
121
|
+
if sodium = get_na
|
122
|
+
r = Result.new
|
123
|
+
r.name = "SNATRIUM"
|
124
|
+
r.report_name = "Serum Electrolytes"
|
125
|
+
r.value = sodium
|
126
|
+
r.units = "mmol/L"
|
127
|
+
r.timestamp = Time.now.to_i
|
128
|
+
o.results["SNATRIUM"] = r
|
129
|
+
end
|
130
|
+
|
131
|
+
if potassium = get_k
|
132
|
+
r = Result.new
|
133
|
+
r.name = "SPOTASSIUM"
|
134
|
+
r.report_name = "Serum Electrolytes"
|
135
|
+
r.value = potassium
|
136
|
+
r.units = "mmol/L"
|
137
|
+
r.timestamp = Time.now.to_i
|
138
|
+
o.results["SPOTASSIUM"] = r
|
139
|
+
end
|
140
|
+
|
141
|
+
if chloride = get_cl
|
142
|
+
r = Result.new
|
143
|
+
r.name = "SCHLORIDE"
|
144
|
+
r.report_name = "Serum Electrolytes"
|
145
|
+
r.value = chloride
|
146
|
+
r.units = "mmol/L"
|
147
|
+
r.timestamp = Time.now.to_i
|
148
|
+
o.results["SCHLORIDE"] = r
|
149
|
+
end
|
150
|
+
|
151
|
+
if ph = get_ph
|
152
|
+
r = Result.new
|
153
|
+
r.name = "pH"
|
154
|
+
r.report_name = "Serum Electrolytes"
|
155
|
+
r.value = ph
|
156
|
+
r.units = "mmol/L"
|
157
|
+
r.timestamp = Time.now.to_i
|
158
|
+
o.results["pH"] = r
|
159
|
+
end
|
160
|
+
|
161
|
+
if po2 = get_po2
|
162
|
+
r = Result.new
|
163
|
+
r.name = "po2"
|
164
|
+
r.report_name = "Serum Electrolytes"
|
165
|
+
r.value = po2
|
166
|
+
r.units = "mmHg"
|
167
|
+
r.timestamp = Time.now.to_i
|
168
|
+
o.results["po2"] = r
|
169
|
+
end
|
170
|
+
|
171
|
+
if pco2 = get_pco2
|
172
|
+
r = Result.new
|
173
|
+
r.name = "pco2"
|
174
|
+
r.report_name = "Serum Electrolytes"
|
175
|
+
r.value = pco2
|
176
|
+
r.units = "mmHg"
|
177
|
+
r.timestamp = Time.now.to_i
|
178
|
+
o.results["pco2"] = r
|
179
|
+
end
|
180
|
+
|
181
|
+
p.orders << o
|
182
|
+
self.headers[-1].patients << p
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
def process_text_file(full_file_path)
|
189
|
+
k = IO.read(full_file_path)
|
190
|
+
process_electrolytes(k.bytes)
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
def receive_data(data)
|
195
|
+
|
196
|
+
|
197
|
+
begin
|
198
|
+
|
199
|
+
|
200
|
+
self.data_buffer ||= ''
|
201
|
+
|
202
|
+
concat = ""
|
203
|
+
|
204
|
+
byte_arr = data.bytes.to_a
|
205
|
+
|
206
|
+
self.test_data_bytes ||= []
|
207
|
+
|
208
|
+
self.data_bytes ||= []
|
209
|
+
|
210
|
+
self.test_data_bytes.push(byte_arr)
|
211
|
+
|
212
|
+
self.data_bytes.push(byte_arr)
|
213
|
+
|
214
|
+
|
215
|
+
concat = pre_process_bytes(byte_arr,concat)
|
216
|
+
|
217
|
+
#puts "concat is:"
|
218
|
+
|
219
|
+
#puts concat.to_s
|
220
|
+
|
221
|
+
self.data_buffer << concat
|
222
|
+
|
223
|
+
## if the last byte is EOT, then call process text.
|
224
|
+
## inside that split by line and process one at a time.
|
225
|
+
##process_text(concat)
|
226
|
+
#puts "data bytes -1: #{self.data_bytes[-1]}"
|
227
|
+
#puts "data bytes 0: #{self.data_bytes[0]}"
|
228
|
+
#if self.data_bytes[0] == ELECTROLYTE_START
|
229
|
+
self.process_electrolytes(self.data_bytes)
|
230
|
+
#end
|
231
|
+
=begin
|
232
|
+
if data.bytes.to_a[-1] == 4
|
233
|
+
puts "GOT EOT --- PROCESSING BUFFER, AND CLEARING."
|
234
|
+
process_text(self.data_buffer)
|
235
|
+
#root_path = File.dirname __dir
|
236
|
+
#puts "root path #{root_path}"
|
237
|
+
#IO.write((File.join root_path,'test','resources','roche_multi_frame_bytes.txt'),self.test_data_bytes.to_s)
|
238
|
+
#puts self.test_data_bytes.flatten.to_s
|
239
|
+
self.data_buffer = ''
|
240
|
+
unless self.headers.blank?
|
241
|
+
if self.headers[-1].queries.blank?
|
242
|
+
#puts "no queries in header so sending ack after getting EOT and processing the buffer"
|
243
|
+
send_data(ACK)
|
244
|
+
else
|
245
|
+
#puts "sending ENQ"
|
246
|
+
send_data(ENQ)
|
247
|
+
end
|
248
|
+
else
|
249
|
+
puts "sending catch all --------------- ACK --------------"
|
250
|
+
send_data(ACK)
|
251
|
+
end
|
252
|
+
elsif data.bytes.to_a[0] == 6
|
253
|
+
puts "GOT ACK --- GENERATING RESPONSE"
|
254
|
+
unless self.headers.blank?
|
255
|
+
header_responses = self.headers[-1].build_one_response({machine_name: self.headers[-1].machine_name})
|
256
|
+
## if no queries then, we have to send ack.
|
257
|
+
if header_responses.blank?
|
258
|
+
#puts "sending ACK since there are no queries in the header"
|
259
|
+
send_data(ACK)
|
260
|
+
end
|
261
|
+
header_responses.each_with_index {|response,key|
|
262
|
+
message_checksum = checksum(response + terminator + ETX)
|
263
|
+
final_resp = STX + response + terminator + ETX + message_checksum + "\r"
|
264
|
+
final_resp_arr = final_resp.bytes.to_a
|
265
|
+
final_resp_arr << 10
|
266
|
+
if (self.headers[-1].response_sent == false)
|
267
|
+
#puts "sending the data as follows----------------------------------------------"
|
268
|
+
#puts "response sent is:"
|
269
|
+
#puts self.headers[-1].response_sent
|
270
|
+
#puts final_resp_arr.pack('c*').gsub(/\r/,'\n')
|
271
|
+
send_data(final_resp_arr.pack('c*'))
|
272
|
+
self.headers[-1].response_sent = true if (key == (header_responses.size - 1))
|
273
|
+
else
|
274
|
+
#puts "sending EOT"
|
275
|
+
send_data(EOT)
|
276
|
+
end
|
277
|
+
}
|
278
|
+
else
|
279
|
+
#puts "NO HEADERS PRESENT --- "
|
280
|
+
end
|
281
|
+
elsif data.bytes.to_a[0] == 255
|
282
|
+
puts " ----------- got 255 data -----------, not sending anything back. "
|
283
|
+
else
|
284
|
+
#unless self.data_buffer.blank?
|
285
|
+
# puts self.data_buffer.gsub(/\r/,'\n').to_s
|
286
|
+
#end
|
287
|
+
## send the header
|
288
|
+
#puts "--------- SENT ACK -----------"
|
289
|
+
## strip non utf 8 characters from it.
|
290
|
+
self.data_buffer.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
|
291
|
+
if self.data_buffer =~ /MSH\|/
|
292
|
+
#puts " -------------- HEADERS ARE BLANK WITH HL7, sending ack. ------------ "
|
293
|
+
process_text(self.data_buffer)
|
294
|
+
self.data_buffer = ''
|
295
|
+
if self.headers.size > 0
|
296
|
+
self.headers[-1].commit
|
297
|
+
send_data(self.headers[-1].generate_ack_success_response)
|
298
|
+
end
|
299
|
+
else
|
300
|
+
#puts " -------------- HEADERS ARE BLANK NOT HL7, sending ack. ------------ "
|
301
|
+
send_data(ACK)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
=end
|
305
|
+
rescue => e
|
306
|
+
|
307
|
+
#self.headers = []
|
308
|
+
AstmServer.log("data was: " + self.data_buffer + "error is:" + e.backtrace.to_s)
|
309
|
+
#send_data(EOT)
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
end
|