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.
File without changes
@@ -49,8 +49,8 @@ class Google_Lab_Interface < Poller
49
49
  ## @param[String] mpg : path to mappings file. Defaults to nil.
50
50
  ## @param[String] credentials_path : the path to look for the credentials.json file, defaults to nil ,and will raise an error unless provided
51
51
  ## @param[String] token_path : the path where the oauth token will be stored, also defaults to the path of the gem : eg. ./token.yaml - be careful with write permissions, because token.yaml gets written to this path after the first authorization.
52
- def initialize(mpg=nil,credentials_path,token_path,script_id)
53
- super(mpg)
52
+ def initialize(mpg=nil,credentials_path,token_path,script_id,real_time_db)
53
+ super(mpg,real_time_db)
54
54
  self.credentials_path = credentials_path
55
55
  self.token_path = token_path
56
56
  self.script_id = script_id
@@ -0,0 +1,484 @@
1
+ require 'fileutils'
2
+ require 'publisher/poller'
3
+ require 'typhoeus'
4
+
5
+ class Pf_Lab_Interface < Poller
6
+
7
+ ORDERS = "orders"
8
+ ORDERS_SORTED_SET = "orders_sorted_set"
9
+ BARCODES = "barcodes"
10
+ BARCODE = "barcode"
11
+ BASE_URL = "http://localhost:3000/"
12
+ UPDATE_QUEUE = "update_queue"
13
+ ## will look back 12 hours if no previous request is found.
14
+ DEFAULT_LOOK_BACK_IN_SECONDS = 12*3600
15
+ ## time to keep old orders in memory
16
+ ## 48 hours, expressed as seconds.
17
+ DEFAULT_STORAGE_TIME_FOR_ORDERS_IN_SECONDS = 48*3600
18
+ ## 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"
21
+ LAST_REQUEST = "last_request"
22
+ FROM_EPOCH = "from_epoch"
23
+ TO_EPOCH = "to_epoch"
24
+ SIZE = "size"
25
+ SKIP = "skip"
26
+ ID = "id"
27
+ REPORTS = "reports"
28
+ TESTS = "tests"
29
+ RESULT_RAW = "result_raw"
30
+ CATEGORIES = "categories"
31
+ USE_CATEGORY_FOR_LIS = "use_category_for_lis"
32
+ LIS_CODE = "lis_code"
33
+ REQUIREMENTS = "requirements"
34
+ ITEMS = "items"
35
+ CODE = "code"
36
+ ORDERS_TO_UPDATE_PER_CYCLE = 10
37
+
38
+ attr_accessor :lis_security_key
39
+
40
+ ###################################################################
41
+ ##
42
+ ##
43
+ ## FLOW OF EVENTS IN THIS FILE
44
+ ##
45
+ ##
46
+ ###################################################################
47
+
48
+ ## STEP ONE:
49
+ ## PRE_POLL_LIS -> basically locks against multiple requests happening at the same time, only one request can go through at one time, this is not touched here, just inherits from poller.rb
50
+
51
+ ## poll_LIS_for_requisition ->
52
+ ## a. calls build_request
53
+ ## b. build_request -> checks if a previous request is still open (this is done simply by checking for a redis key called LAST_REQUEST, if its found, then the previous request is open.)
54
+
55
+ ## if the previous request is not open, creates a fresh request by using hte function #fresh_request_params -> this basically sets a hash with two keys : from_epoch (-> now minus some default interval), to_epoch (-> now), and these are used as the params, for the the typhoeus request.
56
+ ## the response to the request is expected to contain (i.e the backend must return)
57
+ ## "orders" => an array of orders
58
+ ## "skip" => how many results it was told to skip (in the case of a fresh request it will be 0)
59
+ ## "size" => the total size of the results that were got.
60
+ ## "from_epoch" => the from_epoch sent in the request.
61
+ ## "to_epoch" => the to_epoch sent in the request.
62
+
63
+ ## it takes each order, and adds it to the "orders" redis hash.
64
+ ## while adding the orders, it will add individual barcodes with their tests to a "barcodes" hash.
65
+ ## the functions dealing with this are add_order, add_barcode.
66
+ ## while deciding which barcode to add, the priority_category is chosen.
67
+
68
+ ## after this is done, it will look, whether the request is complete?
69
+ ## this means that the "skip" parameter + number of results returned is equal to the total "size" of all possible results.
70
+ ## if yes, then it deletes the last_request key totally, so that next time a new request is made.
71
+ ## if not, then it commits this last_request to the last_request key.
72
+ ## only thing is that we change the skip to be the earlier skip + the number of results returned, so that the next request sent will start from
73
+
74
+
75
+ ###################################################################
76
+ ##
77
+ ##
78
+ ## UTILITY METHOD FOR THE ORDER AND BARCODE HASHES ADD AND REMOVE
79
+ ##
80
+ ##
81
+ ###################################################################
82
+ def remove_order(order_id)
83
+ 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
89
+ end
90
+ $redis.hdel(ORDERS,order[ID])
91
+ $redis.zrem(ORDERS_SORTED_SET,order[ID])
92
+ end
93
+
94
+ def remove_barcode(barcode)
95
+ return if barcode.blank?
96
+ $redis.hdel(BARCODES,barcode)
97
+ end
98
+
99
+ ## @return[Hash] the entry at the barcode, or nil.
100
+ ## key (order_id)
101
+ ## value (array of tests registered on that barcode, the names of the tests are the machine codes, and not the lis_codes)
102
+ ## this key is generated originally in add_barcode
103
+ def get_barcode(barcode)
104
+ if barcode_hash = $redis.hget(BARCODES,barcode)
105
+ JSON.parse(barcode_hash).deep_symbolize_keys
106
+ else
107
+ nil
108
+ end
109
+ end
110
+
111
+ def get_order(order_id)
112
+ if order_string = $redis.hget(ORDERS,order_id)
113
+ JSON.parse(order_string).deep_symbolize_keys
114
+ else
115
+ nil
116
+ end
117
+ end
118
+
119
+ ## @param[Hash] req : the requirement hash.
120
+ ## @return[Hash] priority_category : the category which has been chosen as the top priority for the requirement.
121
+ def get_priority_category(req)
122
+ priority_category = req[CATEGORIES].select{|c|
123
+ c[USE_CATEGORY_FOR_LIS] == 1
124
+ }
125
+ if priority_category.blank?
126
+ priority_category = req[CATEGORIES][0]
127
+ else
128
+ priority_category = priority_category[0]
129
+ end
130
+ priority_category
131
+ end
132
+
133
+ ## @param[Hash] order : order object, as a hash.
134
+ def add_order(order)
135
+ ## this whole thing should be done in one transaction
136
+ order[REPORTS].each do |report|
137
+ test_machine_codes = report[TESTS].map{|c|
138
+ $inverted_mappings[c[LIS_CODE]]
139
+ }.compact.uniq
140
+ report[REQUIREMENTS].each do |req|
141
+ get_priority_category(req)[ITEMS].each do |item|
142
+ if !item[BARCODE].blank?
143
+ add_barcode(item[BARCODE],JSON.generate(
144
+ {
145
+ :order_id => order[ID],
146
+ :machine_codes => test_machine_codes
147
+ }
148
+ ))
149
+ elsif !item[CODE].blank?
150
+ add_barcode(item[CODE],JSON.generate({
151
+ :order_id => order[ID],
152
+ :machine_codes => test_machine_codes
153
+ }))
154
+ end
155
+ end
156
+ end
157
+ end
158
+ $redis.hset(ORDERS,order[ID],JSON.generate(order))
159
+ $redis.zadd(ORDERS_SORTED_SET,Time.now.to_i,order[ID])
160
+ end
161
+
162
+ ## start work on simple.
163
+
164
+ def update_order(order)
165
+ $redis.hset(ORDERS,order[ID],JSON.generate(order))
166
+ end
167
+
168
+ ## @param[Hash] order : the existing order
169
+ ## @param[Hash] res : the result from the machine, pertaining to this order.
170
+ ## @return[nil]
171
+ ## @working : updates the results from res, into the order at the relevant tests inside the order.
172
+ ## $MAPPINGS -> [MACHINE_CODE => LIS_CODE]
173
+ ## $INVERTED_MAPPINGS -> [LIS_CODE => MACHINE_CODE]
174
+ def add_test_result(order,res)
175
+ order[REPORTS.to_sym].each do |report|
176
+ report[TESTS.to_sym].each_with_index{|t,k|
177
+ if t[LIS_CODE.to_sym] == $mappings[res[:name]]
178
+ t[RESULT_RAW.to_sym] = res[:value]
179
+ end
180
+ }
181
+ end
182
+ end
183
+
184
+ def queue_order_for_update(order)
185
+ $redis.lpush(UPDATE_QUEUE,order[ID.to_sym])
186
+ end
187
+
188
+ def add_barcode(code,order_id)
189
+ $redis.hset(BARCODES,code,order_id)
190
+ end
191
+
192
+ def get_last_request
193
+ $redis.hgetall(LAST_REQUEST)
194
+ end
195
+
196
+ =begin
197
+ def delete_last_request
198
+ $redis.del(LAST_REQUEST)
199
+ end
200
+ =end
201
+ def all_hits_downloaded?(last_request)
202
+ last_request[FROM_EPOCH] == last_request[SIZE]
203
+ end
204
+
205
+ def fresh_request_params(from_epoch=nil)
206
+ params = {}
207
+ params[TO_EPOCH] = Time.now.to_i
208
+ params[FROM_EPOCH] = from_epoch || (params[TO_EPOCH] - DEFAULT_LOOK_BACK_IN_SECONDS)
209
+ params[SKIP] = 0
210
+ params
211
+ end
212
+
213
+ def build_request
214
+ last_request = get_last_request
215
+ params = nil
216
+ if last_request.blank?
217
+ params = fresh_request_params
218
+ else
219
+ if all_hits_downloaded?(last_request)
220
+ params = fresh_request_params(last_request[:to_epoch])
221
+ else
222
+ params = last_request
223
+ end
224
+ end
225
+ params.merge!(lis_security_key: self.lis_security_key)
226
+ Typhoeus::Request.new(POLL_URL_PATH,params: params)
227
+ end
228
+
229
+ ## commits the request params to redis.
230
+ ## the response hash is expected to have whatever parameters were sent into it in the request.
231
+ ## so it must always return:
232
+ ## a -> how many it was told to skip (SKIP)
233
+ ## b -> from_epoch : from which epoch it was queried.
234
+ ## c -> to_epoch : to which epoch it was queried.
235
+ def commit_request_params_to_redis(response_hash)
236
+ $redis.hset(LAST_REQUEST,SKIP,response_hash[SKIP].to_i + response_hash[ORDERS].size.to_i)
237
+ $redis.hset(LAST_REQUEST,SIZE,response_hash[SIZE].to_i)
238
+ $redis.hset(LAST_REQUEST,FROM_EPOCH,response_hash[FROM_EPOCH].to_i)
239
+ $redis.hset(LAST_REQUEST,TO_EPOCH,response_hash[TO_EPOCH].to_i)
240
+ end
241
+
242
+ # since we request only a certain set of orders per request
243
+ # we need to know if the earlier request has been completed
244
+ # or we still need to rerequest the same time frame again.
245
+ def request_size_completed?(response_hash)
246
+ response_hash[SKIP].to_i + response_hash[ORDERS].size >= response_hash[SIZE]
247
+ end
248
+ ###################################################################
249
+ ##
250
+ ##
251
+ ## ENDS.
252
+ ##
253
+ ##
254
+ ###################################################################
255
+
256
+
257
+ ###################################################################
258
+ ##
259
+ ##
260
+ ## METHODS OVERRIDDEN FROM THE BASIC POLLER.
261
+ ##
262
+ ##
263
+ ###################################################################
264
+ ## @param[String] mpg : path to mappings file. Defaults to nil.
265
+ ## @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)
267
+ super(mpg)
268
+ self.lis_security_key = lis_security_key
269
+ AstmServer.log("Initialized Lab Interface")
270
+ end
271
+
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
294
+ end
295
+ request.run
296
+ end
297
+
298
+ =begin
299
+ data = [
300
+ {
301
+ :id => "ARUBA",
302
+ :results => [
303
+ {
304
+ :name => "TLCparam",
305
+ :value => 10
306
+ },
307
+ {
308
+ :name => "Nparam",
309
+ :value => 23
310
+ },
311
+ {
312
+ :name => "ANCparam",
313
+ :value => 25
314
+ },
315
+ {
316
+ :name => "Lparam",
317
+ :value => 10
318
+ },
319
+ {
320
+ :name => "ALCparam",
321
+ :value => 44
322
+ },
323
+ {
324
+ :name => "Mparam",
325
+ :value => 55
326
+ },
327
+ {
328
+ :name => "AMCparam",
329
+ :value => 22
330
+ },
331
+ {
332
+ :name => "Eparam",
333
+ :value => 222
334
+ },
335
+ {
336
+ :name => "AECparam",
337
+ :value => 21
338
+ },
339
+ {
340
+ :name => "BASOparam",
341
+ :value => 222
342
+ },
343
+ {
344
+ :name => "ABCparam",
345
+ :value => 300
346
+ },
347
+ {
348
+ :name => "RBCparam",
349
+ :value => 2.22
350
+ },
351
+ {
352
+ :name => "HBparam",
353
+ :value => 19
354
+ },
355
+ {
356
+ :name => "HCTparam",
357
+ :value => 22
358
+ },
359
+ {
360
+ :name => "MCVparam",
361
+ :value => 222
362
+ },
363
+ {
364
+ :name => "MCHparam",
365
+ :value => 21
366
+ },
367
+ {
368
+ :name => "MCHCparam",
369
+ :value => 10
370
+ },
371
+ {
372
+ :name => "MCVparam",
373
+ :value => 222
374
+ },
375
+ {
376
+ :name => "RDWCVparam",
377
+ :value => 12
378
+ },
379
+ {
380
+ :name => "PCparam",
381
+ :value => 1.22322
382
+ }
383
+ ]
384
+ }
385
+ ]
386
+ =end
387
+
388
+ def process_update_queue
389
+ #puts "came to process update queue."
390
+ order_ids = []
391
+ ORDERS_TO_UPDATE_PER_CYCLE.times do |n|
392
+ order_ids << $redis.rpop(UPDATE_QUEUE)
393
+ end
394
+ #puts "order ids popped"
395
+ #puts order_ids.to_s
396
+ orders = order_ids.map{|c|
397
+ get_order(c)
398
+ }.compact
399
+
400
+
401
+ #puts "orders are:"
402
+ #puts orders.to_s
403
+
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'})
405
+
406
+
407
+ req.on_complete do |response|
408
+ if response.success?
409
+ response_body = response.body
410
+ orders = JSON.parse(response.body)["orders"]
411
+ orders.each do |order|
412
+ if order["errors"].blank?
413
+ else
414
+ puts "got an error for the order."
415
+ ## how many total error attempts to manage.
416
+ end
417
+ end
418
+ elsif response.timed_out?
419
+ AstmServer.log("got a time out")
420
+ elsif response.code == 0
421
+ AstmServer.log(response.return_message)
422
+ else
423
+ AstmServer.log("HTTP request failed: " + response.code.to_s)
424
+ end
425
+ end
426
+
427
+ req.run
428
+
429
+ end
430
+
431
+ ## removes any orders that start from
432
+ ## now - 4 days ago
433
+ ## now - 2 days ago
434
+ def remove_old_orders
435
+ stale_order_ids = $redis.zrangebyscore(ORDERS_SORTED_SET,(Time.now.to_i - DEFAULT_STORAGE_TIME_FOR_ORDERS_IN_SECONDS*2).to_s, (Time.now.to_i - DEFAULT_STORAGE_TIME_FOR_ORDERS_IN_SECONDS))
436
+ $redis.pipelined do
437
+ stale_order_ids.each do |order_id|
438
+ remove_order(order_id)
439
+ end
440
+ end
441
+ end
442
+
443
+ def update(data)
444
+ data.each do |result|
445
+ barcode = result[:id]
446
+ results = result[:results]
447
+ if barcode_hash = get_barcode(barcode)
448
+ if order = get_order(barcode_hash[:order_id])
449
+ ## update the test results, and add the order to the final update hash.
450
+ puts "order got from barcode is:"
451
+ puts order
452
+ machine_codes = barcode_hash[:machine_codes]
453
+ ## it has to be registered on this.
454
+ results.each do |res|
455
+ if machine_codes.include? res[:name]
456
+ ## so we need to update to the requisite test inside the order.
457
+ add_test_result(order,res)
458
+ ## commit to redis
459
+ ## and then
460
+ end
461
+ end
462
+ puts "came to queue order for update"
463
+ queue_order_for_update(order)
464
+ end
465
+ else
466
+ AstmServer.log("the barcode:#{barcode}, does not exist in the barcodes hash")
467
+ ## does not exist.
468
+ end
469
+ end
470
+
471
+ process_update_queue
472
+ remove_old_orders
473
+
474
+ end
475
+
476
+ def poll
477
+ pre_poll_LIS
478
+ poll_LIS_for_requisition
479
+ update_LIS
480
+ post_poll_LIS
481
+ end
482
+
483
+
484
+ end