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.
@@ -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
@@ -1,17 +1,21 @@
1
- require "ruby_astm/usb_module"
2
- require "ruby_astm/query"
3
- require "ruby_astm/frame"
4
- require "ruby_astm/header"
5
- require "ruby_astm/lab_interface"
6
- require "ruby_astm/line"
7
- require "ruby_astm/order"
8
- require "ruby_astm/patient"
9
- require "ruby_astm/result"
10
- require "ruby_astm/astm_server"
11
- require "publisher/adapter"
12
- require "publisher/google_lab_interface"
13
- require "publisher/poller"
14
- require "ruby_astm/HL7/hl7_header"
15
- require "ruby_astm/HL7/hl7_patient"
16
- require "ruby_astm/HL7/hl7_order"
17
- require "ruby_astm/HL7/hl7_observation"
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
@@ -4,6 +4,7 @@ require 'em-rubyserial'
4
4
  require "active_support/all"
5
5
  require "json"
6
6
  require "redis"
7
+ require "rest-firebase"
7
8
 
8
9
  class AstmServer
9
10
 
@@ -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