ruby_astm 1.4.1 → 1.4.3
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 +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
|