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.
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