ruby_astm 1.5.3 → 1.5.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c048fb081850c73e0920aea67af6ba42022e3453
4
- data.tar.gz: 447df9ea7aa6eaea6708b120d248af10664f78ae
2
+ SHA256:
3
+ metadata.gz: 4e23929d7d26dd7c171b839033998571960bf11b5ca4c8668fa3c67a9a08327b
4
+ data.tar.gz: 603c5c33d330b6bdeb54d7d6d39750cc84f1d428c8dc042c9be278bd72f35644
5
5
  SHA512:
6
- metadata.gz: 5003ceacee31b6fd72a3e85cb2042bd312bb774ab158215c92fb11e0efcb894a0ab8e446035412fc4c43b193f5ece52140125955fd7f6b2f02880ee957aa63f2
7
- data.tar.gz: e944a019d055a5fd14e64660a290ae07806a96793111b1b13a2a9563ed6db97051a9528c6a787a4e0f6741ffd6f887b4315031c26a4b650ba9feb5f1f019f5b6
6
+ metadata.gz: 5cf677ebb74365e0be42400c7ed573093892343720eba5256a4a2041cf996b2577067e749ee20d4fa7b74dece3682794462273188270319802f1a3523274ab7c
7
+ data.tar.gz: f4832f9050d30efac849cb7b0bf594716aa468bd0faa66cc53dcbcbdf02090891a0e15e5b18b66a09e9500a43c6fbdd7b3fab6bc2764cf570fae4ab9fc0b672d
@@ -0,0 +1,6 @@
1
+ class PfDownloadException < StandardError
2
+ def initialize(message)
3
+ super(message)
4
+ AstmServer.log(message)
5
+ end
6
+ end
@@ -1,9 +1,60 @@
1
1
  require 'fileutils'
2
- require 'publisher/poller'
2
+ require_relative 'poller'
3
+ require_relative 'pf_download_exception'
3
4
  require 'typhoeus'
5
+ require 'resolv-replace'
6
+
7
+ RestFirebase.class_eval do
8
+
9
+ attr_accessor :private_key_hash
10
+
11
+ def query
12
+ {:access_token => auth}
13
+ end
14
+
15
+ def get_jwt
16
+ puts Base64.encode64(JSON.generate(self.private_key_hash))
17
+ # Get your service account's email address and private key from the JSON key file
18
+ $service_account_email = self.private_key_hash["client_email"]
19
+ $private_key = OpenSSL::PKey::RSA.new self.private_key_hash["private_key"]
20
+ now_seconds = Time.now.to_i
21
+ payload = {:iss => $service_account_email,
22
+ :sub => $service_account_email,
23
+ :aud => self.private_key_hash["token_uri"],
24
+ :iat => now_seconds,
25
+ :exp => now_seconds + 1, # Maximum expiration time is one hour
26
+ :scope => 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/firebase.database'
27
+
28
+ }
29
+ JWT.encode payload, $private_key, "RS256"
30
+
31
+ end
32
+
33
+ def generate_access_token
34
+ uri = URI.parse(self.private_key_hash["token_uri"])
35
+ https = Net::HTTP.new(uri.host, uri.port)
36
+ https.use_ssl = true
37
+ req = Net::HTTP::Post.new(uri.path)
38
+ req['Cache-Control'] = "no-store"
39
+ req.set_form_data({
40
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
41
+ assertion: get_jwt
42
+ })
43
+
44
+ resp = JSON.parse(https.request(req).body)
45
+ resp["access_token"]
46
+ end
47
+
48
+ def generate_auth opts={}
49
+ generate_access_token
50
+ end
51
+
52
+ end
4
53
 
5
54
  class Pf_Lab_Interface < Poller
6
55
 
56
+ include StreamModule
57
+
7
58
  ORDERS = "orders"
8
59
  ORDERS_SORTED_SET = "orders_sorted_set"
9
60
  BARCODES = "barcodes"
@@ -16,8 +67,8 @@ class Pf_Lab_Interface < Poller
16
67
  ## 48 hours, expressed as seconds.
17
68
  DEFAULT_STORAGE_TIME_FOR_ORDERS_IN_SECONDS = 48*3600
18
69
  ## the last request that was made and what it said.
19
- POLL_URL_PATH = BASE_URL + "interfaces"
20
- PUT_URL_PATH = BASE_URL + "lis_update_orders"
70
+ POLL_ENDPOINT = "interfaces"
71
+ PUT_ENDPOINT = "lis_update_orders"
21
72
  LAST_REQUEST = "last_request"
22
73
  FROM_EPOCH = "from_epoch"
23
74
  TO_EPOCH = "to_epoch"
@@ -34,9 +85,16 @@ class Pf_Lab_Interface < Poller
34
85
  ITEMS = "items"
35
86
  CODE = "code"
36
87
  ORDERS_TO_UPDATE_PER_CYCLE = 10
88
+ PREV_REQUEST_COMPLETED = "prev_request_completed"
37
89
 
38
90
  attr_accessor :lis_security_key
39
91
 
92
+ ## should include the https://www.xyz.com:3000
93
+ ## defaults to http://localhost:3000
94
+ attr_accessor :server_url_with_port
95
+
96
+ attr_accessor :retry_count
97
+
40
98
  ###################################################################
41
99
  ##
42
100
  ##
@@ -81,14 +139,20 @@ class Pf_Lab_Interface < Poller
81
139
  ###################################################################
82
140
  def remove_order(order_id)
83
141
  order = get_order(order_id)
84
- order["reports"].each do |report|
85
- report["tests"].each do |test|
86
- remove_barcode(test["barcode"])
87
- remove_barcode(test["code"])
88
- end
142
+ puts "order id is:#{order_id} is"
143
+ unless order.blank?
144
+ puts "order not blank."
145
+ order[:reports].each do |report|
146
+ report[:tests].each do |test|
147
+ remove_barcode(test[:barcode])
148
+ remove_barcode(test[:code])
149
+ end
150
+ end
151
+ $redis.hdel(ORDERS,order_id)
152
+ $redis.zrem(ORDERS_SORTED_SET,order_id)
153
+ else
154
+ puts "order is blank."
89
155
  end
90
- $redis.hdel(ORDERS,order[ID])
91
- $redis.zrem(ORDERS_SORTED_SET,order[ID])
92
156
  end
93
157
 
94
158
  def remove_barcode(barcode)
@@ -132,7 +196,7 @@ class Pf_Lab_Interface < Poller
132
196
 
133
197
  ## @param[Hash] order : order object, as a hash.
134
198
  def add_order(order)
135
- ## this whole thing should be done in one transaction
199
+ at_least_one_item_exists = false
136
200
  order[REPORTS].each do |report|
137
201
  test_machine_codes = report[TESTS].map{|c|
138
202
  $inverted_mappings[c[LIS_CODE]]
@@ -140,6 +204,7 @@ class Pf_Lab_Interface < Poller
140
204
  report[REQUIREMENTS].each do |req|
141
205
  get_priority_category(req)[ITEMS].each do |item|
142
206
  if !item[BARCODE].blank?
207
+ at_least_one_item_exists = true
143
208
  add_barcode(item[BARCODE],JSON.generate(
144
209
  {
145
210
  :order_id => order[ID],
@@ -147,6 +212,7 @@ class Pf_Lab_Interface < Poller
147
212
  }
148
213
  ))
149
214
  elsif !item[CODE].blank?
215
+ at_least_one_item_exists = true
150
216
  add_barcode(item[CODE],JSON.generate({
151
217
  :order_id => order[ID],
152
218
  :machine_codes => test_machine_codes
@@ -155,14 +221,17 @@ class Pf_Lab_Interface < Poller
155
221
  end
156
222
  end
157
223
  end
158
- $redis.hset(ORDERS,order[ID],JSON.generate(order))
159
- $redis.zadd(ORDERS_SORTED_SET,Time.now.to_i,order[ID])
224
+
225
+ unless at_least_one_item_exists.blank?
226
+ $redis.hset(ORDERS,order[ID],JSON.generate(order))
227
+ $redis.zadd(ORDERS_SORTED_SET,Time.now.to_i,order[ID])
228
+ end
160
229
  end
161
230
 
162
231
  ## start work on simple.
163
232
 
164
233
  def update_order(order)
165
- $redis.hset(ORDERS,order[ID],JSON.generate(order))
234
+ $redis.hset(ORDERS,order[ID.to_sym],JSON.generate(order))
166
235
  end
167
236
 
168
237
  ## @param[Hash] order : the existing order
@@ -171,17 +240,30 @@ class Pf_Lab_Interface < Poller
171
240
  ## @working : updates the results from res, into the order at the relevant tests inside the order.
172
241
  ## $MAPPINGS -> [MACHINE_CODE => LIS_CODE]
173
242
  ## $INVERTED_MAPPINGS -> [LIS_CODE => MACHINE_CODE]
174
- def add_test_result(order,res)
243
+ def add_test_result(order,res,lis_code)
244
+ #puts "res is:"
245
+ #puts res.to_s
246
+
175
247
  order[REPORTS.to_sym].each do |report|
248
+ #puts "doing report"
176
249
  report[TESTS.to_sym].each_with_index{|t,k|
177
- if t[LIS_CODE.to_sym] == $mappings[res[:name]]
250
+ #puts "doing test"
251
+ #puts t.to_s
252
+
253
+ puts "teh test lis code to sym is:"
254
+ puts t[LIS_CODE.to_sym]
255
+ puts "lis code is: #{lis_code.to_s}"
256
+ if t[LIS_CODE.to_sym] == lis_code.to_s
257
+ puts "got equality"
178
258
  t[RESULT_RAW.to_sym] = res[:value]
259
+ puts "set value"
179
260
  end
180
261
  }
181
262
  end
182
263
  end
183
264
 
184
265
  def queue_order_for_update(order)
266
+ update_order(order)
185
267
  $redis.lpush(UPDATE_QUEUE,order[ID.to_sym])
186
268
  end
187
269
 
@@ -199,31 +281,51 @@ class Pf_Lab_Interface < Poller
199
281
  end
200
282
  =end
201
283
  def all_hits_downloaded?(last_request)
202
- last_request[FROM_EPOCH] == last_request[SIZE]
284
+ last_request[PREV_REQUEST_COMPLETED].to_s == "true"
203
285
  end
204
-
205
- def fresh_request_params(from_epoch=nil)
286
+
287
+ ## @param[Time] from : time object
288
+ ## @param[Time] to : time object
289
+ def fresh_request_params(from,to)
290
+ #puts "came to make fresh request params, with from epoch: #{from_epoch}"
206
291
  params = {}
207
- params[TO_EPOCH] = Time.now.to_i
208
- params[FROM_EPOCH] = from_epoch || (params[TO_EPOCH] - DEFAULT_LOOK_BACK_IN_SECONDS)
292
+ params[TO_EPOCH] = to.to_i
293
+ params[FROM_EPOCH] = from.to_i
209
294
  params[SKIP] = 0
210
295
  params
211
296
  end
212
297
 
213
- def build_request
298
+ ## so build request should have a from and a to
299
+ ## what are the defaults ?
300
+ ## @param[Time] from : default (nil)
301
+ ## @param[Time] to : default(nil)
302
+ def build_request(from=nil,to=nil)
303
+ puts "entering build request with from: #{from} and to:#{to}"
304
+ to ||= Time.now
305
+ from ||= to - 1.day
214
306
  last_request = get_last_request
215
307
  params = nil
216
308
  if last_request.blank?
217
- params = fresh_request_params
309
+ AstmServer.log("no last request, making fresh request")
310
+ params = fresh_request_params(from,to)
218
311
  else
219
312
  if all_hits_downloaded?(last_request)
220
- params = fresh_request_params(last_request[:to_epoch])
313
+ AstmServer.log("last request all hits have been downloaded, going for next request.")
314
+ if last_request[TO_EPOCH].to_i == to.to_i
315
+ return nil
316
+ else
317
+ params = fresh_request_params(last_request[TO_EPOCH],to)
318
+ end
221
319
  else
320
+ AstmServer.log("last request all hits not downloaded.")
222
321
  params = last_request
223
322
  end
224
323
  end
225
324
  params.merge!(lis_security_key: self.lis_security_key)
226
- Typhoeus::Request.new(POLL_URL_PATH,params: params)
325
+ AstmServer.log("reuqest params become: #{params}")
326
+ AstmServer.log("sleeping")
327
+ #sleep(10000)
328
+ Typhoeus::Request.new(self.get_poll_url_path,params: params)
227
329
  end
228
330
 
229
331
  ## commits the request params to redis.
@@ -237,13 +339,15 @@ class Pf_Lab_Interface < Poller
237
339
  $redis.hset(LAST_REQUEST,SIZE,response_hash[SIZE].to_i)
238
340
  $redis.hset(LAST_REQUEST,FROM_EPOCH,response_hash[FROM_EPOCH].to_i)
239
341
  $redis.hset(LAST_REQUEST,TO_EPOCH,response_hash[TO_EPOCH].to_i)
342
+ $redis.hset(LAST_REQUEST,PREV_REQUEST_COMPLETED,request_size_completed?(response_hash).to_s)
240
343
  end
241
344
 
242
345
  # since we request only a certain set of orders per request
243
346
  # we need to know if the earlier request has been completed
244
347
  # or we still need to rerequest the same time frame again.
245
348
  def request_size_completed?(response_hash)
246
- response_hash[SKIP].to_i + response_hash[ORDERS].size >= response_hash[SIZE]
349
+ #puts response_hash.to_s
350
+ response_hash[SKIP].to_i + response_hash[ORDERS].size >= response_hash[SIZE].to_i
247
351
  end
248
352
  ###################################################################
249
353
  ##
@@ -263,36 +367,152 @@ class Pf_Lab_Interface < Poller
263
367
  ###################################################################
264
368
  ## @param[String] mpg : path to mappings file. Defaults to nil.
265
369
  ## @param[String] lis_security_key : the security key for the LIS organization, to be dowloaded from the organizations/show/id, endpoint in the website.
266
- def initialize(mpg=nil,lis_security_key)
370
+ def initialize(mpg=nil,lis_security_key,server_url_with_port,organization_id,private_key_hash)
267
371
  super(mpg)
372
+ self.private_key_hash = private_key_hash
373
+ self.event_source = "organizations/" + organization_id
374
+ self.on_message_handler_function = "evented_poll_LIS_for_requisition"
268
375
  self.lis_security_key = lis_security_key
376
+
377
+ self.server_url_with_port = (server_url_with_port || BASE_URL)
378
+ self.retry_count = 0
379
+ ## called from stream module
380
+ setup_connection
269
381
  AstmServer.log("Initialized Lab Interface")
270
382
  end
271
383
 
272
- def poll_LIS_for_requisition
273
- AstmServer.log("Polling LIS at url:#{BASE_URL}")
274
- request = build_request
275
- request.on_complete do |response|
276
- if response.success?
277
- response_hash = JSON.parse(response.body)
278
- orders = response_hash[ORDERS]
279
- orders.each do |order|
280
- add_order(order)
281
- end
282
- commit_request_params_to_redis(response_hash)
283
- elsif response.timed_out?
284
- # aw hell no
285
- # put to astm log.
286
- AstmServer.log("Polling time out")
287
- elsif response.code == 0
288
- # Could not get an http response, something's wrong.
289
- AstmServer.log(response.return_message)
290
- else
291
- # Received a non-successful http response.
292
- AstmServer.log("HTTP request failed: " + response.code.to_s)
293
- end
384
+ ## this is triggered by whatever firebase sends
385
+ ## you put this in the callback, and let me block and see what happens.
386
+ ## we cannot watch two different endpoints ?
387
+ ## or we can ?
388
+ ## on the same endpoint -> will
389
+ ## so it becomes a merged document.
390
+ ## and both events will fire.
391
+ ## and get triggered.
392
+ def evented_poll_LIS_for_requisition(data)
393
+ unless data.blank?
394
+
395
+ data = data["data"].blank? ? data : data["data"]
396
+
397
+ unless data["delete_order"].blank?
398
+ puts "delete order is not blank"
399
+ unless data["delete_order"]["order_id"].blank?
400
+ puts "order id is not blank"
401
+ puts "going to delete the completed order --------------->"
402
+ delete_completed_order(data["delete_order"]["order_id"])
403
+ end
404
+ end
405
+ unless data["trigger_lis_poll"].blank?
406
+ unless data["trigger_lis_poll"]["epoch"].blank?
407
+ new_poll_LIS_for_requisition(data["trigger_lis_poll"]["epoch"].to_i)
408
+ end
409
+ end
410
+ else
411
+
412
+ end
413
+ end
414
+
415
+ def delete_completed_order(order_id)
416
+ remove_order(order_id)
417
+ end
418
+
419
+ def put_delete_order_event(order_id)
420
+ puts self.connection.put(self.event_source,:order_id => order_id)
421
+ end
422
+
423
+ def test_trigger_lis_poll(epoch=nil)
424
+ puts self.connection.put(self.event_source + "/trigger_lis_poll", :epoch => epoch)
425
+ end
426
+
427
+ def test_trigger_delete_order(order_id)
428
+ puts self.connection.put(self.event_source + "/delete_order", :order_id => order_id)
429
+ end
430
+
431
+ def new_poll_LIS_for_requisition(to_epoch=nil)
432
+ AstmServer.log(to_epoch.to_s)
433
+ while true
434
+ orders = []
435
+ begin
436
+ Retriable.retriable(on: PfDownloadException) do
437
+ self.retry_count+=1
438
+ AstmServer.log("retrying----->")
439
+ request = build_request(nil,to_epoch)
440
+ break if request.blank?
441
+ request.run
442
+ response = request.response
443
+ if response.success?
444
+ code = response.code
445
+ time = response.total_time
446
+ headers = response.headers
447
+ #AstmServer.log("successfully polled server")
448
+ response_hash = JSON.parse(response.body)
449
+ #AstmServer.log("Pathofast LIS poll response --->")
450
+ #AstmServer.log(response_hash.to_s)
451
+ orders = response_hash[ORDERS]
452
+ orders.each do |order|
453
+ add_order(order)
454
+ end
455
+ commit_request_params_to_redis(response_hash)
456
+ #puts "are the orders blank: #{orders.blank?}"
457
+ #break if orders.blank?
458
+ elsif response.timed_out?
459
+ #AstmServer.log("Error polling server with code: #{code}")
460
+ raise PfDownloadException.new("timeout")
461
+ elsif response.code == 0
462
+ #AstmServer.log("Error polling server with code: #{code}")
463
+ raise PfDownloadException.new("didnt get any http response")
464
+ else
465
+ #AstmServer.log("Error polling server with code: #{code}")
466
+ raise PfDownloadException.new("non 200 response")
467
+ end
468
+ end
469
+ rescue => e
470
+ puts e.to_s
471
+ puts "raised exception-----------> breaking."
472
+ ## retryable has raised the errors again.
473
+ break
474
+ else
475
+ ## break only if the orders are blank.
476
+ break if orders.blank?
477
+ end
478
+ end
479
+ end
480
+
481
+ ## how it deletes the records is that when all the reports in that order are verified, then that order is cleared from all LIS receptive organizations.
482
+ ## that's the only way.
483
+ ## once I'm done with that, its only the barcodes, and then the inventory bits.
484
+
485
+ ## how it deletes records
486
+ ## so this is called in the form of a while loop.
487
+ ## how it handles the update response.
488
+ def poll_LIS_for_requisition(to_epoch=nil)
489
+ AstmServer.log(to_epoch.to_s)
490
+ while true
491
+ #puts "came back to true"
492
+ request = build_request(nil,to_epoch)
493
+ break if request.blank?
494
+ request.run
495
+ response = request.response
496
+ code = response.code
497
+ time = response.total_time
498
+ headers = response.headers
499
+ if code.to_s != "200"
500
+ AstmServer.log("Error polling server with code: #{code}")
501
+ break
502
+ else
503
+ AstmServer.log("successfully polled server")
504
+ response_hash = JSON.parse(response.body)
505
+ AstmServer.log("Pathofast LIS poll response --->")
506
+ #AstmServer.log(response_hash.to_s)
507
+ orders = response_hash[ORDERS]
508
+ orders.each do |order|
509
+ add_order(order)
510
+ end
511
+ commit_request_params_to_redis(response_hash)
512
+ puts "are the orders blank: #{orders.blank?}"
513
+ break if orders.blank?
514
+ end
294
515
  end
295
- request.run
296
516
  end
297
517
 
298
518
  =begin
@@ -388,39 +608,59 @@ data = [
388
608
  def process_update_queue
389
609
  #puts "came to process update queue."
390
610
  order_ids = []
611
+ #puts $redis.lrange UPDATE_QUEUE, 0, -1
612
+
613
+ ## first push that to patient.
614
+ ## first create that order and add that barcode.
615
+ ## for citrate.
616
+ ## then let that get downloaded.
617
+ ## so keep on test going for that.
618
+ ##
619
+ ## why complicate this so much.
620
+ ## just do a brpop?
621
+ ##
391
622
  ORDERS_TO_UPDATE_PER_CYCLE.times do |n|
392
623
  order_ids << $redis.rpop(UPDATE_QUEUE)
393
624
  end
394
625
  #puts "order ids popped"
395
626
  #puts order_ids.to_s
627
+ order_ids.compact!
628
+ order_ids.uniq!
396
629
  orders = order_ids.map{|c|
397
630
  get_order(c)
398
631
  }.compact
399
-
632
+ #puts orders[0].to_s
400
633
 
401
634
  #puts "orders are:"
402
- #puts orders.to_s
635
+ #puts orders.size
636
+ #exit(1)
403
637
 
404
- req = Typhoeus::Request.new(PUT_URL_PATH, method: :put, body: {orders: orders}.to_json, params: {lis_security_key: self.lis_security_key}, headers: {Accept: 'application/json', "Content-Type".to_sym => 'application/json'})
638
+ req = Typhoeus::Request.new(self.get_put_url_path, method: :put, body: {orders: orders}.to_json, params: {lis_security_key: self.lis_security_key}, headers: {Accept: 'application/json', "Content-Type".to_sym => 'application/json'})
405
639
 
406
640
 
407
641
  req.on_complete do |response|
408
642
  if response.success?
409
643
  response_body = response.body
410
644
  orders = JSON.parse(response.body)["orders"]
411
- orders.each do |order|
645
+ #puts orders.to_s
646
+ orders.values.each do |order|
647
+ #puts order.to_s
412
648
  if order["errors"].blank?
413
649
  else
414
650
  puts "got an error for the order."
415
651
  ## how many total error attempts to manage.
416
652
  end
417
653
  end
654
+ ## here we have to raise.
418
655
  elsif response.timed_out?
419
656
  AstmServer.log("got a time out")
657
+ raise PfUpdateException.new("update order timed out")
420
658
  elsif response.code == 0
421
659
  AstmServer.log(response.return_message)
660
+ raise PfUpdateException.new("update order response code 0")
422
661
  else
423
662
  AstmServer.log("HTTP request failed: " + response.code.to_s)
663
+ raise PfUpdateException.new("update order response code non success: #{response.code}")
424
664
  end
425
665
  end
426
666
 
@@ -440,45 +680,142 @@ data = [
440
680
  end
441
681
  end
442
682
 
683
+ ORDERS_KEY = "@orders"
684
+ FAILED_UPDATES = "failed_updates"
685
+ PATIENTS_REDIS_LIST = "patients"
686
+ PROCESSING_REDIS_LIST = "processing"
687
+
688
+ ## this is only done on startup
689
+ ## okay so what do we
690
+ def reattempt_failed_updates
691
+ $redis.scard(FAILED_UPDATES).times do
692
+ if patient_results = $redis.spop(FAILED_UPDATES)
693
+ patient_results = JSON.parse(patient_results)
694
+ begin
695
+ Retriable.retriable(on: PfUpdateException) do
696
+ unless update(patient_results)
697
+ raise PfUpdateException.new("didnt get any http response")
698
+ end
699
+ end
700
+ rescue => e
701
+ AstmServer.log("reattempted and failed")
702
+ ensure
703
+
704
+ end
705
+ end
706
+ end
707
+ end
708
+
709
+ ## we can do this.
710
+ ## args can be used to modulate exit behaviours
711
+ ## @param[Hash] args : hash of arguments
712
+ def update_LIS(args={})
713
+ prepare_redis
714
+ exit_requested = false
715
+ Kernel.trap( "INT" ) { exit_requested = true }
716
+ while !exit_requested
717
+ puts "exit not requested."
718
+ if patient_results = $redis.brpoplpush(PATIENTS_REDIS_LIST,PROCESSING_REDIS_LIST,0)
719
+ puts "got patient results."
720
+ patient_results = JSON.parse(patient_results)
721
+ puts "patient results are:"
722
+ puts JSON.pretty_generate(patient_results)
723
+ begin
724
+ Retriable.retriable(on: PfUpdateException) do
725
+ unless update(patient_results)
726
+ raise PfUpdateException.new("didnt get any http response")
727
+ end
728
+ end
729
+ exit_requested = !args[:exit_on_success].blank?
730
+ #puts "exit requested becomes: #{exit_requested}"
731
+ rescue => e
732
+ $redis.sadd(FAILED_UPDATES,JSON.generate(patient_results))
733
+ exit_requested = !args[:exit_on_failure].blank?
734
+ puts "came to eventual rescue, exit requested is: #{exit_requested}"
735
+ ensure
736
+ $redis.lpop("processing")
737
+ end
738
+ else
739
+ puts "no patient results"
740
+ end
741
+ end
742
+ end
743
+
744
+
745
+
443
746
  def update(data)
444
- data.each do |result|
445
- barcode = result[:id]
446
- results = result[:results]
747
+ puts "data is:"
748
+ puts JSON.pretty_generate(data)
749
+ data[ORDERS_KEY].each do |order|
750
+ puts "order is "
751
+ puts order
752
+ barcode = order["id"]
753
+ results = order["results"]
754
+ puts "barcode is: #{barcode}, results are : #{results}"
755
+ results.deep_symbolize_keys!
447
756
  if barcode_hash = get_barcode(barcode)
757
+ puts "barcode hash is: #{barcode_hash}"
448
758
  if order = get_order(barcode_hash[:order_id])
759
+ puts "got order"
449
760
  ## update the test results, and add the order to the final update hash.
450
- puts "order got from barcode is:"
451
- puts order
761
+ #puts "order got from barcode is:"
762
+ #puts order
452
763
  machine_codes = barcode_hash[:machine_codes]
453
- ## it has to be registered on this.
454
- results.each do |res|
764
+ puts "machine codes: #{machine_codes}"
765
+
766
+ results.keys.each do |lis_code|
767
+ res = results[lis_code]
768
+ add_test_result(order,res,lis_code)
769
+ end
770
+
771
+ =begin
772
+ results.values.each do |res|
455
773
  if machine_codes.include? res[:name]
456
774
  ## so we need to update to the requisite test inside the order.
457
- add_test_result(order,res)
775
+ puts "came to add test result"
776
+ puts res
777
+ add_test_result(order,res,nil)
458
778
  ## commit to redis
459
779
  ## and then
460
780
  end
461
781
  end
462
- puts "came to queue order for update"
782
+ =end
783
+ #puts "came to queue order for update"
463
784
  queue_order_for_update(order)
464
785
  end
465
786
  else
466
787
  AstmServer.log("the barcode:#{barcode}, does not exist in the barcodes hash")
467
788
  ## does not exist.
468
789
  end
469
- end
790
+ end
470
791
 
471
792
  process_update_queue
472
- remove_old_orders
793
+
473
794
 
474
795
  end
475
796
 
797
+ def get_poll_url_path
798
+ self.server_url_with_port + POLL_ENDPOINT
799
+ end
800
+
801
+ def get_put_url_path
802
+ self.server_url_with_port + PUT_ENDPOINT
803
+ end
804
+
805
+
806
+ def _start
807
+ evented_poll_LIS_for_requisition({"trigger_lis_poll" => {"epoch" => Time.now.to_i.to_s}})
808
+ reattempt_failed_updates
809
+ update_LIS
810
+ end
811
+
812
+ ## the watcher is seperate.
813
+ ## that deals with other things.
814
+
815
+ ## this method is redundant, and no longer used
816
+ ## the whole thing is now purely evented.
476
817
  def poll
477
- pre_poll_LIS
478
- poll_LIS_for_requisition
479
- update_LIS
480
- post_poll_LIS
481
818
  end
482
819
 
483
-
820
+
484
821
  end
@@ -0,0 +1,6 @@
1
+ class PfUpdateException < StandardError
2
+ def initialize(message)
3
+ super(message)
4
+ AstmServer.log(message)
5
+ end
6
+ end
@@ -442,7 +442,7 @@ class Poller
442
442
  ## name of the sorted set can be defined in the class that inherits from adapter, or will default to "requisitions"
443
443
  ## when a query is sent from any laboratory equipment to the local ASTMServer, it will query the redis sorted set, for the test information.
444
444
  ## so this poller basically constantly replicates the cloud based test information to the local server.
445
- def poll_LIS_for_requisition
445
+ def poll_LIS_for_requisition(to_epoch=nil)
446
446
 
447
447
  end
448
448
 
@@ -0,0 +1,68 @@
1
+ module StreamModule
2
+
3
+ SECRET = ENV["FIREBASE_SECRET"]
4
+ SITE_URL = ENV["FIREBASE_SITE"]
5
+
6
+ attr_accessor :on_message_handler_function
7
+ attr_accessor :connection
8
+ attr_accessor :private_key_hash
9
+ attr_accessor :event_source
10
+ ## the event_stream object
11
+ attr_accessor :es
12
+
13
+ def setup_connection
14
+ raise "please provide the private key hash, from firebase service account -> create private key " if self.private_key_hash.blank?
15
+ raise "no event source endpoint provided" if self.event_source.blank?
16
+ self.connection = RestFirebase.new :site => SITE_URL,
17
+ :secret => SECRET, :private_key_hash => private_key_hash, :auth_ttl => 1800
18
+ self.on_message_handler_function ||= "on_message_handler"
19
+
20
+ end
21
+
22
+ def watch
23
+ @reconnect = true
24
+ self.es = self.connection.event_source(self.event_source)
25
+ self.es.onopen { |sock| p sock } # Called when connecte
26
+ self.es.onmessage{ |event, data, sock|
27
+ #puts "event: #{event}"
28
+ send(self.on_message_handler_function,data)
29
+ }
30
+ self.es.onerror { |error, sock| p error } # Called 4
31
+ self.es.onreconnect{ |error, sock| p error; @reconnect }
32
+ self.es.start
33
+ rd, wr = IO.pipe
34
+ %w[INT TERM].each do |sig|
35
+ Signal.trap(sig) do
36
+ wr.puts # unblock the main thread
37
+ end
38
+ end
39
+ rd.gets # block main thread until INT or TERM received
40
+ @reconnect = false
41
+ self.es.close
42
+ self.es.wait # shutdown cleanly
43
+ end
44
+
45
+ def watch_limited(seconds)
46
+
47
+ @reconnect = true
48
+ self.es = self.connection.event_source(self.event_source)
49
+ self.es.onopen { |sock| p sock } # Called when connecte
50
+ self.es.onmessage{ |event, data, sock|
51
+ send(self.on_message_handler_function,data)
52
+ }
53
+ self.es.onerror { |error, sock| p error } # Called 4
54
+ self.es.onreconnect{ |error, sock| p error; @reconnect }
55
+ self.es.start
56
+ sleep(seconds)
57
+ @reconnect = false
58
+ self.es.close
59
+ self.es.wait # shutdown cleanly
60
+
61
+ end
62
+
63
+ def on_message_handler(data)
64
+ #puts "got some data"
65
+ #puts data
66
+ end
67
+
68
+ end
@@ -1,4 +1,5 @@
1
1
  require_relative "ruby_astm/usb_module"
2
+ require_relative "ruby_astm/custom/esr"
2
3
  require_relative "ruby_astm/query"
3
4
  require_relative "ruby_astm/frame"
4
5
  require_relative "ruby_astm/header"
@@ -8,6 +9,9 @@ require_relative "ruby_astm/order"
8
9
  require_relative "ruby_astm/patient"
9
10
  require_relative "ruby_astm/result"
10
11
  require_relative "ruby_astm/astm_server"
12
+ require_relative "publisher/pf_download_exception"
13
+ require_relative "publisher/pf_update_exception"
14
+ require_relative "publisher/stream_module"
11
15
  require_relative "publisher/adapter"
12
16
  require_relative "publisher/google_lab_interface"
13
17
  require_relative "publisher/poller"
@@ -19,4 +23,6 @@ require_relative "ruby_astm/HL7/hl7_order"
19
23
  require_relative "ruby_astm/HL7/hl7_observation"
20
24
  require_relative "ruby_astm/custom/siemens_abg_electrolyte_module"
21
25
  require_relative "ruby_astm/custom/siemens_abg_electrolyte_server"
26
+ require_relative "ruby_astm/custom/siemens_dimension_exl_module"
27
+ require_relative "ruby_astm/custom/siemens_dimension_exl_server"
22
28
 
@@ -0,0 +1,3 @@
1
+ class Esr
2
+ include UsbModule
3
+ end
@@ -207,7 +207,7 @@ module SiemensAbgElectrolyteModule
207
207
  unless o.results.blank?
208
208
  p.orders << o
209
209
  $redis.hset(SIEMENS_ELEC_ABG_RESULTS_HASH,patient_id,JSON.generate(o.results_values_hash))
210
- self.headers[-1].patients << p
210
+ self.headers[-1].patients = [p]
211
211
  end
212
212
 
213
213
  end
@@ -0,0 +1,130 @@
1
+ module SiemensDimensionExlModule
2
+
3
+ include LabInterface
4
+
5
+ def self.included base
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ FS = "\x1C"
10
+
11
+ def pre_process_bytes(byte_arr,concat)
12
+
13
+ puts "this is the overridden method"
14
+ puts byte_arr.to_s
15
+
16
+ indices_to_delete = is_mid_frame_end?(byte_arr)
17
+ #puts "indices to delete"
18
+ #puts indices_to_delete.to_s
19
+
20
+ if self.mid_frame_end_detected == true
21
+ #puts "deletected mid fram is true, so deleting first byte before delete"
22
+ #puts byte_arr.to_s
23
+ byte_arr = byte_arr[1..-1]
24
+ #puts "after deleteing"
25
+ #puts byte_arr.to_s
26
+ self.mid_frame_end_detected = false
27
+ end
28
+
29
+ unless indices_to_delete.blank?
30
+ if byte_arr[(indices_to_delete[-1] + 1)]
31
+ #puts "before deleting frame number "
32
+ #puts byte_arr.to_s
33
+ byte_arr.delete_at((indices_to_delete[-1] + 1))
34
+ #puts "after deleting"
35
+ #puts byte_arr.to_s
36
+ else
37
+ self.mid_frame_end_detected = true
38
+ end
39
+ end
40
+ #puts "byte arr before reject"
41
+ byte_arr = byte_arr.reject.with_index{|c,i| indices_to_delete.include? i}
42
+
43
+
44
+ byte_arr.each do |byte|
45
+ x = [byte].pack('c*').force_encoding('UTF-8')
46
+ if x == "\r"
47
+ concat+="\n"
48
+ elsif x == "\n"
49
+ #puts "new line found --- "
50
+ concat+=x
51
+ #puts "last thing in concat."
52
+ #puts concat[-1].to_s
53
+ else
54
+ concat+=x
55
+ end
56
+ end
57
+
58
+ concat
59
+
60
+ end
61
+
62
+ def message_ends?
63
+ x = self.data_bytes.flatten[-1] == 3
64
+ if x == true
65
+ puts "message ends #{self.data_bytes.flatten}"
66
+ self.data_bytes = []
67
+ end
68
+ x
69
+ end
70
+
71
+ def enq?
72
+ self.data_bytes.flatten[-1] == 5
73
+ end
74
+
75
+ def acknowledge
76
+ resp = ACK
77
+ send_data(ACK)
78
+ end
79
+
80
+ def no_request
81
+ resp = STX + "N" + FS + "6A" + ETX + "\n"
82
+ send_data(resp.bytes.to_a.pack('c*'))
83
+ end
84
+
85
+
86
+
87
+ def receive_data(data)
88
+
89
+
90
+ begin
91
+
92
+
93
+ self.data_buffer ||= ''
94
+
95
+ #puts "incoming data bytes."
96
+
97
+ concat = ""
98
+
99
+
100
+ byte_arr = data.bytes.to_a
101
+
102
+ self.test_data_bytes ||= []
103
+
104
+ self.data_bytes ||= []
105
+
106
+ self.test_data_bytes.push(byte_arr)
107
+
108
+ self.data_bytes.push(byte_arr)
109
+
110
+ concat = pre_process_bytes(byte_arr,concat)
111
+
112
+ self.data_buffer << concat
113
+
114
+
115
+ if message_ends?
116
+ acknowledge
117
+ no_request
118
+ end
119
+
120
+
121
+ rescue => e
122
+
123
+ #self.headers = []
124
+ AstmServer.log("data was: " + self.data_buffer + "error is:" + e.backtrace.to_s)
125
+ #send_data(EOT)
126
+ end
127
+
128
+ end
129
+
130
+ end
@@ -0,0 +1,46 @@
1
+ class SiemensDimensionExlServer
2
+
3
+ include SiemensDimensionExlModule
4
+
5
+ ## DEFAULT SERIAL PORT : /dev/ttyS0
6
+ ## DEFAULT USB PORT : /dev/ttyUSB0
7
+ ## @param[Array] ethernet_connections : each element is expected to be a hash, with keys for :server_ip, :server_port.
8
+ ## @param[Array] serial_connections : each element is expected to be a hash with port_address, baud_rate, and parity
9
+ #def initialize(server_ip=nil,server_port=nil,mpg=nil,respond_to_queries=false,serial_port='/dev/ttyS0',usb_port='/dev/ttyUSB0',serial_baud=9600,serial_parity=8,usb_baud=19200,usb_parity=8)
10
+ def initialize(ethernet_connections,serial_connections,mpg=nil,respond_to_queries=nil)
11
+ $redis = Redis.new
12
+ self.class.log("Initializing AstmServer")
13
+ self.ethernet_connections = ethernet_connections
14
+ self.serial_connections = serial_connections
15
+ self.server_ip = server_ip || "127.0.0.1"
16
+ self.server_port = server_port || 3000
17
+ self.respond_to_queries = respond_to_queries
18
+ self.serial_port = serial_port
19
+ self.serial_baud = serial_baud
20
+ self.serial_parity = serial_parity
21
+ self.usb_port = usb_port
22
+ self.usb_baud = usb_baud
23
+ self.usb_parity = usb_parity
24
+ $mappings = JSON.parse(IO.read(mpg || self.class.default_mappings))
25
+ end
26
+
27
+ def start_server
28
+ EventMachine.run {
29
+ self.ethernet_connections.each do |econn|
30
+ raise "please provide a valid ethernet configuration with ip address" unless econn[:server_ip]
31
+ raise "please provide a valid ethernet configuration with port" unless econn[:server_port]
32
+ EventMachine::start_server econn[:server_ip], econn[:server_port], SiemensDimensionExlModule
33
+ self.class.log("Running ETHERNET with configuration #{econn}")
34
+ end
35
+ self.serial_connections.each do |sconn|
36
+ raise "please provide a valid serial configuration with port address" unless sconn[:port_address]
37
+ raise "please provide a valid serial configuration with baud rate" unless sconn[:baud_rate]
38
+ raise "please provide a valid serial configuration with parity" unless sconn[:parity]
39
+ EventMachine.open_serial(sconn[:port_address], sconn[:baud_rate], sconn[:parity],SiemensDimensionExlModule)
40
+ puts "RUNNING SERIAL port with configuration : #{sconn}"
41
+ end
42
+
43
+ }
44
+ end
45
+
46
+ end
@@ -60,7 +60,7 @@ module LabInterface
60
60
  #######################################################
61
61
  module ClassMethods
62
62
  def log(message)
63
- puts "" + message
63
+ puts message
64
64
  $redis.zadd("ruby_astm_log",Time.now.to_i,message)
65
65
  end
66
66
 
@@ -267,8 +267,8 @@ module LabInterface
267
267
  puts "GOT EOT --- PROCESSING BUFFER, AND CLEARING."
268
268
  process_text(self.data_buffer)
269
269
  root_path = File.dirname __dir__
270
- puts "root path #{root_path}"
271
- IO.write((File.join root_path,'../test','resources','d10_error.txt'),self.test_data_bytes.to_s)
270
+ #puts "root path #{root_path}"
271
+ #IO.write((File.join root_path,'../test','resources','d10_error.txt'),self.test_data_bytes.to_s)
272
272
  #puts self.test_data_bytes.flatten.to_s
273
273
  self.data_buffer = ''
274
274
  unless self.headers.blank?
@@ -34,16 +34,6 @@ module UsbModule
34
34
 
35
35
  self.usb_response_bytes[-3] == 13
36
36
  end
37
-
38
- def add_result_by_count?(barcode,result)
39
- begin
40
-
41
-
42
- rescue => e
43
- puts e.to_s
44
- return true
45
- end
46
- end
47
37
  ## total times repeated, we can have that as max 3 times.
48
38
  ## so after that it wont add.
49
39
  ## so we keep a sorted set with the barcode and the count
@@ -76,14 +66,20 @@ module UsbModule
76
66
  end
77
67
 
78
68
  def parse_usb_response(string_data)
69
+ #puts "string data is:"
70
+ #puts string_data.to_s
79
71
  string_data.bytes.to_a.each do |byte|
80
72
  self.usb_response_bytes.push(byte)
81
73
  end
82
- #puts "self usb response bytes:"
83
- #puts self.usb_response_bytes.to_s
74
+ parse_bytes
75
+ #puts self.usb_response_bytes.pack('c*')
76
+ end
77
+
78
+ def parse_bytes(bytes=nil)
79
+ self.usb_response_bytes ||= bytes
84
80
  if interpret?
85
81
  #puts "interpret"
86
- barcodes = []
82
+ existing_barcodes = []
87
83
  if kk = self.usb_response_bytes[13..-4]
88
84
  ## its going fresh -> old
89
85
  ## so if the barcode has already come
@@ -108,7 +104,7 @@ module UsbModule
108
104
  order.id = bar_code
109
105
  order.results = []
110
106
  order.results << result
111
- #puts "barcode: #{bar_code}, result : #{result.value}"
107
+ puts "barcode: #{bar_code}, result : #{result.value}"
112
108
  patient.orders << order
113
109
  if add_result?(bar_code,result.value,existing_barcodes)
114
110
  #puts patient.to_json
@@ -125,7 +121,6 @@ module UsbModule
125
121
  else
126
122
  #puts "dont interpret"
127
123
  end
128
-
129
124
  end
130
125
 
131
126
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_astm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.3
4
+ version: 1.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bhargav Raut
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: retriable
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '3.1'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '3.1'
167
181
  description: This gem provides a server that can handle communication from medical
168
182
  instruments that send/receive information on the ASTM protocol.
169
183
  email: bhargav.r.raut@gmail.com
@@ -174,17 +188,23 @@ files:
174
188
  - lib/mappings.json
175
189
  - lib/publisher/adapter.rb
176
190
  - lib/publisher/google_lab_interface.rb
191
+ - lib/publisher/pf_download_exception.rb
177
192
  - lib/publisher/pf_lab_interface.rb
193
+ - lib/publisher/pf_update_exception.rb
178
194
  - lib/publisher/poller.rb
179
195
  - lib/publisher/real_time_db.rb
196
+ - lib/publisher/stream_module.rb
180
197
  - lib/ruby_astm.rb
181
198
  - lib/ruby_astm/HL7/hl7_header.rb
182
199
  - lib/ruby_astm/HL7/hl7_observation.rb
183
200
  - lib/ruby_astm/HL7/hl7_order.rb
184
201
  - lib/ruby_astm/HL7/hl7_patient.rb
185
202
  - lib/ruby_astm/astm_server.rb
203
+ - lib/ruby_astm/custom/esr.rb
186
204
  - lib/ruby_astm/custom/siemens_abg_electrolyte_module.rb
187
205
  - lib/ruby_astm/custom/siemens_abg_electrolyte_server.rb
206
+ - lib/ruby_astm/custom/siemens_dimension_exl_module.rb
207
+ - lib/ruby_astm/custom/siemens_dimension_exl_server.rb
188
208
  - lib/ruby_astm/frame.rb
189
209
  - lib/ruby_astm/header.rb
190
210
  - lib/ruby_astm/lab_interface.rb
@@ -214,7 +234,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
214
234
  version: '0'
215
235
  requirements: []
216
236
  rubyforge_project:
217
- rubygems_version: 2.6.14.4
237
+ rubygems_version: 2.7.8
218
238
  signing_key:
219
239
  specification_version: 4
220
240
  summary: A Ruby gem to interface with Medical instruments that work on the ASTM protocol