lis 0.2.3 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - 2.0.0
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source :rubygems
1
+ source "https://rubygems.org"
2
2
  gemspec
@@ -13,7 +13,52 @@ available][spec])
13
13
  [spec]: http://www.google.com/search?q=dpc+lis+immulite+2000+filetype:pdf
14
14
 
15
15
 
16
+ ## Usage
17
+
18
+ * run the LIS server:
19
+
20
+ ```
21
+ $ gem install lis
22
+ $ lis help
23
+ $ lis -l /dev/ttyUSB0 -e http://worklist.example/lis server
24
+ ```
25
+
26
+ * now, whenever order requests arrive from the LIS hardware, lis2http will forward them to the specified HTTP endpoint:
27
+
28
+ ```
29
+ GET http://worklist.example/lis/{DEVICE_NAME}-{SPECIMEN_ID}
30
+ ```
31
+
32
+ * this should return basic patient information as well as test IDs for all pending requests:
33
+
34
+ ```
35
+ ---
36
+ id: '1234'
37
+ patient:
38
+ id: 98
39
+ last_name: Sierra
40
+ first_name: Rudolph
41
+ types:
42
+ - TSTID
43
+ - TSH
44
+ - FT3
45
+ - FT4
46
+ ```
47
+
48
+ * results are posted to the same URI as soon as they are received:
49
+
50
+ ```
51
+ POST http://worklist.example/lis/{DEVICE_NAME}-{SPECIMEN_ID}/{TEST_NAME}
52
+
53
+ ---
54
+ flags: N
55
+ result_timestamp: '1993-10-11T09:12:33+00:00'
56
+ status: F
57
+ test_name: TSTID
58
+ unit: mIU/mL
59
+ value: '8.2'
60
+ ```
16
61
 
17
62
  ## Copyright
18
63
 
19
- Copyright (c) 2010-2011 Levin Alexander. See LICENSE for details.
64
+ Copyright (c) 2010-2013 Levin Alexander. See LICENSE for details.
data/Rakefile CHANGED
@@ -7,9 +7,7 @@ require 'lis'
7
7
 
8
8
  require 'rake/testtask'
9
9
  Rake::TestTask.new(:test) do |test|
10
- test.libs << 'lib' << 'test'
11
- test.pattern = 'test/**/test_*.rb'
12
- test.verbose = true
10
+ test.libs << 'test'
13
11
  end
14
12
 
15
13
  require 'cucumber/rake/task'
data/bin/lis ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ require 'gli'
3
+
4
+ require 'lis'
5
+ require 'lis/cli'
6
+
7
+ exit LIS::CLI.run(ARGV.dup)
8
+
@@ -1,8 +1,4 @@
1
1
  Feature:
2
- In order to diagnose patients correctly
3
- As a doctor
4
- I want to be able to receive test results via LIS
5
-
6
2
  Scenario: LIS asking for pending requests
7
3
  Given LIS Interface listening for messages
8
4
  And the following requests are pending for DPC:
@@ -17,7 +13,7 @@ Feature:
17
13
  Then LIS should have sent test orders to client:
18
14
  """
19
15
  1H|\^&|||LIS||||8N1|DPC||P|1|
20
- 2P|1||||Müller^Rudolph||||||||
16
+ 2P|1|98||98|Müller^Rudolph||||||||
21
17
  3O|1|123ABC||^^^TSH
22
18
  4O|1|123ABC||^^^FT3
23
19
  5O|1|123ABC||^^^FT4
@@ -0,0 +1,6 @@
1
+ Feature: My bootstrapped app kinda works
2
+
3
+ Scenario: App just runs
4
+ When I get help for "lis"
5
+ Then the exit status should be 0
6
+
@@ -0,0 +1,6 @@
1
+ When /^I get help for "([^"]*)"$/ do |app_name|
2
+ @app_name = app_name
3
+ step %(I run `#{app_name} --help`)
4
+ end
5
+
6
+ # Add more step definitions here
@@ -1,16 +1,14 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require 'mocha'
4
- require 'yaml'
3
+
5
4
 
6
5
 
7
6
  Given /^LIS Interface listening for messages$/ do
8
7
  @client, @r, @w = PacketIO::Test::MockServer.build
9
8
  @io = PacketIO::IOListener.new(@r, @w)
10
- @server = LIS::InterfaceServer.create(@io, "http://localhost/lis/")
9
+ @server = LIS::InterfaceServer.create(@io, "http://localhost/lis")
11
10
 
12
- stub_request(:post, /http:\/\/localhost\/lis\/result\/.*/).
13
- with(:body => /.*/, :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/x-www-form-urlencoded'}).
11
+ stub_request(:post, /http:\/\/localhost\/lis\/.*?\/.*/).
14
12
  to_return(:status => 200, :body => "", :headers => {})
15
13
 
16
14
  @t = Thread.new do
@@ -36,7 +34,7 @@ end
36
34
  Then /^should have posted results:$/ do |table|
37
35
  table.hashes.each do |row|
38
36
  expected_body = ["test_name", "value", "unit", "status", "flags", "result_timestamp"].inject({}) { |h,k| h[k] = row[k]; h }
39
- assert_requested(:post, "http://localhost/lis/result/#{row["id"]}", :times => 1, :body => expected_body)
37
+ assert_requested(:post, "http://localhost/lis/#{row["id"]}/#{row["test_name"]}", :times => 1, :body => hash_including(expected_body))
40
38
  end
41
39
  end
42
40
 
@@ -53,16 +51,13 @@ Given /^the following requests are pending for (\w+):$/ do |device_name, table|
53
51
  table.hashes.each do |patient|
54
52
  body = { "patient" => { "last_name" => patient["last_name"],
55
53
  "first_name" => patient["first_name"],
56
- "id" => patient["patient_id"]},
54
+ "number" => patient["patient_id"]},
57
55
  "id" => patient["id"],
58
56
  "types" => patient["test_names"].strip.split(/\s+/) }
59
57
 
60
- stub_request(:get, "http://localhost/lis/find_requests/#{device_name}-#{patient["id"]}").
61
- with(:headers => {'Accept'=>'*/*'}).
58
+ stub_request(:get, "http://localhost/lis/#{device_name}-#{patient["id"]}").
62
59
  to_return(:status => 200, :body => body.to_yaml, :headers => {})
63
-
64
60
  end
65
-
66
61
  end
67
62
 
68
63
 
@@ -1,9 +1,16 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+
2
3
  require 'lis'
3
4
 
5
+ require 'mocha'
6
+ require 'yaml'
7
+ require 'aruba/cucumber'
8
+
4
9
  require 'packet_io/test/mock_server'
5
10
  require 'test/unit/assertions'
6
11
  require 'webmock/cucumber'
7
12
 
13
+ WebMock.disable_net_connect!
14
+
8
15
  World(Test::Unit::Assertions)
9
16
 
data/lib/lis.rb CHANGED
@@ -1,15 +1,16 @@
1
- $:.unshift File.dirname(__FILE__)
2
-
3
1
  require 'packet_io'
2
+ require 'yaml'
4
3
 
5
4
  require 'lis/version'
6
-
7
5
  require 'lis/messages.rb'
6
+ require 'lis/data'
7
+
8
8
  Dir[File.join(File.dirname(__FILE__), 'lis/messages/**/*.rb')].each { |f| require f }
9
9
 
10
10
  require 'lis/transfer/astm_e1394.rb'
11
11
  require 'lis/transfer/application_protocol.rb'
12
+ require 'lis/transfer/logging'
13
+ require 'lis/http_interface.rb'
12
14
 
13
- require 'lis/worklist_manager_interface.rb'
14
15
  require 'lis/interface_server.rb'
15
16
 
@@ -0,0 +1,47 @@
1
+ module LIS
2
+ module CLI
3
+ extend GLI::App
4
+ program_desc "LIS interface to Siemens Immulite 2000XPi or other similar analyzers"
5
+ version LIS::VERSION
6
+
7
+ flag ["config","c"], :default_value => File.join(ENV['HOME'],'.lis'),
8
+ :desc => "stores configuration and authentication data",
9
+ :arg_name => "FILE"
10
+
11
+ flag ["listen", "l"], :arg_name => "PORT", :desc => "which port to listen on", :default_value => "/dev/ttyUSB0"
12
+ flag ["endpoint", "e"], :arg_name => "URI", :desc => "HTTP endpoint", :default_value => "http://localhost/api/lis"
13
+ switch ["verbose", "v"], :desc => "Increase verbosity"
14
+
15
+ pre do |global_options,command,options,args|
16
+ $VERBOSE = global_options[:verbose]
17
+ true
18
+ end
19
+
20
+ desc "run the LIS server"
21
+ command "server" do |c|
22
+ c.action do |global_options,options,args|
23
+ port = global_options[:listen]
24
+ warn "configuring #{port}"
25
+ `stty -echo raw ospeed 9600 ispeed 9600 < #{port}`
26
+ warn "listening on: #{port}"
27
+ port = File.open(port, "w+")
28
+
29
+ LIS::InterfaceServer.listen(port, global_options[:endpoint]).run!
30
+ end
31
+ end
32
+
33
+ desc "load a list of test requests"
34
+ arg_name "DEVICE_NAME REQUEST_ID"
35
+ command "requests" do |c|
36
+ c.action do |global_options,options,args|
37
+
38
+ req = LIS::HTTPInterface.new(global_options[:endpoint]).load_requests(*args)
39
+ puts req.types
40
+ end
41
+
42
+
43
+ end
44
+
45
+
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ module LIS
2
+ module Data
3
+ class Request
4
+ def initialize(data={})
5
+ @data = data
6
+ end
7
+
8
+ def patient_id
9
+ @data["patient"]["number"]
10
+ end
11
+ def patient_last_name
12
+ @data["patient"]["last_name"]
13
+ end
14
+ def patient_first_name
15
+ @data["patient"]["first_name"]
16
+ end
17
+ def id
18
+ @data["id"]
19
+ end
20
+
21
+ def types
22
+ @data["types"]
23
+ end
24
+
25
+
26
+ def each_type
27
+ @data["types"].each do |t|
28
+ yield id, t
29
+ end
30
+ end
31
+
32
+ def to_hash
33
+ @data
34
+ end
35
+
36
+ def self.from_yaml(text, barcode)
37
+ data = YAML.load(text)
38
+ data["id"] = barcode
39
+
40
+ new(data)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,74 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rest-client'
4
+
5
+ class LIS::HTTPInterface
6
+ def initialize(endpoint)
7
+ @endpoint = endpoint
8
+ end
9
+
10
+ # expects all pending requests for the given device and barcode
11
+ #
12
+ # { "id" => "1234",
13
+ # "patient" => { "id" => 98,
14
+ # "last_name" => "Sierra",
15
+ # "first_name" => "Rudolph" },
16
+ # "types" => [ "TSH", "FT3", "FT4" ] }
17
+ #
18
+ def load_requests(device_name, barcode)
19
+ begin
20
+ result = RestClient.get(uri(device_name, barcode))
21
+ data = LIS::Data::Request.from_yaml(result.body, barcode)
22
+ rescue Exception => e
23
+ puts e
24
+ puts e.backtrace
25
+ data = nil
26
+ end
27
+
28
+ warn "data: #{data.inspect}" if $VERBOSE
29
+
30
+ data
31
+ end
32
+
33
+
34
+ def set_request_status(device_name, data)
35
+ # uri = URI.join(@endpoint, "result_status/#{[device_name, data.id].join('-')}")
36
+ # Net::HTTP.post_form(uri, data.to_hash)
37
+ end
38
+
39
+ def send_result(device_name, order, result)
40
+ barcode = order.specimen_id
41
+
42
+ data = {
43
+ "test_name" => order.universal_test_id,
44
+ "value" => result.result_value,
45
+ "unit" => result.unit,
46
+ "status" => result.result_status,
47
+ "flags" => result.abnormal_flags,
48
+ "result_timestamp" => result.test_completed_at,
49
+ "raw" => result.raw_data
50
+ }
51
+
52
+ # FIXME: WTF: should not just catch everything
53
+ begin
54
+ res = RestClient.post(uri(device_name, barcode, order.universal_test_id), data)
55
+ rescue Exception => e
56
+ puts "EXCEPTION"
57
+ p e
58
+ end
59
+ end
60
+
61
+
62
+ private
63
+
64
+ def uri(device_name, barcode, test_name = nil)
65
+ id = [device_name, barcode].join("-")
66
+
67
+ s = [@endpoint, id, test_name].compact.join("/")
68
+ warn "uri: #{s}" if $VERBOSE
69
+
70
+ s
71
+ end
72
+
73
+ end
74
+
@@ -10,17 +10,21 @@ module LIS
10
10
  end
11
11
 
12
12
 
13
- def initialize(server, http_endpoint, protocol_stack = [LIS::Transfer::ASTM::E1394, LIS::Transfer::ApplicationProtocol])
13
+ def initialize(server, http_endpoint)
14
14
  @server = server
15
- protocol = protocol_stack.inject(server) { |i,klass| klass.new(i) }
16
- interface = WorklistManagerInterface.new(http_endpoint)
17
15
 
16
+ protocol = LIS::Transfer::ApplicationProtocol.new(LIS::Transfer::Logging.new(LIS::Transfer::ASTM::E1394.new(server)))
17
+ interface = HTTPInterface.new(http_endpoint)
18
18
 
19
19
  protocol.on_request do |device_name, barcode|
20
+ warn "loading requests" if $VERBOSE
20
21
  interface.load_requests(device_name, barcode)
21
22
  end
22
- protocol.on_result do |*args|
23
- interface.send_result(*args)
23
+ protocol.on_result do |device_name, patient, order, result|
24
+ interface.send_result(device_name, order, result)
25
+ end
26
+ protocol.on_request_sent do |device_name, data|
27
+ interface.set_request_status(device_name, data)
24
28
  end
25
29
  end
26
30
 
@@ -38,6 +38,10 @@ module LIS::Message
38
38
  has_field 12, :test_started_at, :type => :timestamp
39
39
  has_field 13, :test_completed_at, :type => :timestamp
40
40
 
41
+ def raw_data
42
+ to_message
43
+ end
44
+
41
45
  def universal_test_id
42
46
  universal_test_id_internal.gsub(/\^/,"")
43
47
  end
@@ -12,6 +12,10 @@ module LIS::Transfer
12
12
  @on_request_callback = block
13
13
  end
14
14
 
15
+ def on_request_sent(&block)
16
+ @on_request_sent_callback = block
17
+ end
18
+
15
19
  def received_header(message)
16
20
  @patient_information_requests ||= {} # delete the list of patients
17
21
  @device_name = message.sender_name
@@ -42,14 +46,16 @@ module LIS::Transfer
42
46
 
43
47
  def send_pending_requests
44
48
  sending_session(@patient_information_requests) do |patient_information|
45
- patient_information.each do |sequence_nr, data|
49
+ patient_information.each do |sequence_nr, request_data|
46
50
  write :message, LIS::Message::Patient.new(sequence_nr,
47
- data["patient"]["number"],
48
- data["patient"]["last_name"],
49
- data["patient"]["first_name"]).to_message
50
- data["types"].each do |request|
51
- write :message, LIS::Message::Order.new(sequence_nr, data["id"], request).to_message
51
+ request_data.patient_id,
52
+ request_data.patient_last_name,
53
+ request_data.patient_first_name).to_message
54
+ request_data.each_type do |id, request|
55
+ write :message, LIS::Message::Order.new(sequence_nr, id, request).to_message
52
56
  end
57
+
58
+ @on_request_sent_callback.call(@device_name, request_data) if @on_request_sent_callback
53
59
  end
54
60
  end
55
61
  @patient_information_requests = {}
@@ -71,7 +77,6 @@ module LIS::Transfer
71
77
  end
72
78
 
73
79
  def receive(type, message = nil)
74
- warn "[R] #{message}" if type == :message and $VERBOSE
75
80
  case type
76
81
  when :begin
77
82
  @last_patient = nil
@@ -8,7 +8,7 @@ module LIS::Transfer
8
8
  # splits a stream into lis packets and only lets packets through
9
9
  # that are inside a session delimited by ENQ .. EOT
10
10
  #
11
- # check the checksum and do acknowledgement of messages
11
+ # checks the checksum and does acknowledgement of messages
12
12
  #
13
13
  # forwards the following events:
14
14
  #
@@ -81,7 +81,7 @@ module LIS::Transfer
81
81
 
82
82
  frame_number, data, checksum = match[1 .. 3]
83
83
 
84
- expected_checksum = (frame_number + data).each_byte.inject(16) { |a,b| (a+b) % 0x100 }
84
+ expected_checksum = compute_checksum(frame_number + data)
85
85
  actual_checksum = checksum.to_i(16)
86
86
 
87
87
  raise "checksum mismatch: expected %03x got %03x" % [expected_checksum, actual_checksum] unless expected_checksum == actual_checksum
@@ -90,12 +90,16 @@ module LIS::Transfer
90
90
 
91
91
  def self.wrap_message(string, frame_number)
92
92
  frame_number = (frame_number % 8).to_s
93
- checksum = (frame_number + string).each_byte.inject(16) { |a,b| (a+b) % 0x100 }
93
+ checksum = compute_checksum(frame_number + string)
94
94
  checksum = checksum.to_s(16).upcase.rjust(2,"0")
95
95
 
96
96
  "\002#{frame_number}#{string}\015\003#{checksum}\015\012"
97
97
  end
98
98
 
99
+ def self.compute_checksum(str)
100
+ str.each_byte.inject(16) { |a,b| (a+b) % 0x100 }
101
+ end
102
+
99
103
 
100
104
  def received_message(message)
101
105
  return false unless @inside_transmission
@@ -0,0 +1,27 @@
1
+
2
+ module LIS::Transfer
3
+ class Logging < ::PacketIO::Base
4
+ def initialize(*args)
5
+ @verbose = $VERBOSE
6
+ super
7
+ end
8
+
9
+ def receive(type, message=nil)
10
+ output("<", type, message)
11
+ super
12
+ end
13
+
14
+ def write(type, message=nil)
15
+ output(">", type, message)
16
+ super
17
+ end
18
+
19
+
20
+ private
21
+
22
+ def output(direction, type, message)
23
+ warn [direction, type.to_s, message].join(" ") if @verbose
24
+ end
25
+ end
26
+ end
27
+
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module LIS
4
- VERSION = "0.2.3"
4
+ VERSION = "0.4.3"
5
5
  end
6
6
 
@@ -20,12 +20,16 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ["lib"]
21
21
  s.summary = %q{LIS interface to Siemens Immulite 2000XPi or other similar analyzers}
22
22
 
23
- s.add_dependency "packet_io", "~> 0.4.0"
23
+ s.add_dependency "packet_io", ">= 0.4.2"
24
+ s.add_dependency "rest-client"
25
+ s.add_dependency "rake"
26
+ s.add_dependency "gli", ">= 2.0.0.rc5"
24
27
 
25
- s.add_development_dependency("shoulda", "~> 2.11.3")
26
- s.add_development_dependency("mocha", "~> 0.9.12")
27
- s.add_development_dependency("yard", "~> 0.7.1")
28
- s.add_development_dependency("cucumber", "~> 0.10.3")
29
- s.add_development_dependency("webmock", "~> 1.6.4")
28
+ s.add_development_dependency("shoulda", ">= 3.1.0")
29
+ s.add_development_dependency("mocha", ">= 0.12.0")
30
+ s.add_development_dependency("yard")
31
+ s.add_development_dependency("cucumber", ">= 1.2.0")
32
+ s.add_development_dependency("webmock", ">= 1.8.7")
33
+ s.add_development_dependency('aruba')
30
34
  end
31
35
 
@@ -1,12 +1,15 @@
1
1
  require 'rubygems'
2
2
  require 'test/unit'
3
+ require 'webmock/test_unit'
3
4
  require 'shoulda'
4
- require 'mocha'
5
+ require 'mocha/setup'
5
6
 
6
7
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
8
  $LOAD_PATH.unshift(File.dirname(__FILE__))
8
9
  require 'lis'
9
10
 
11
+ WebMock.disable_net_connect!
12
+
10
13
  class Test::Unit::TestCase
11
14
  end
12
15
 
@@ -0,0 +1,61 @@
1
+ require 'helper'
2
+
3
+ class TestHTTPInterface < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @interface = LIS::HTTPInterface.new("http://localhost/lis")
7
+ @device_name = "LIS1"
8
+ end
9
+
10
+
11
+ context "posting a result" do
12
+ setup do
13
+ @order = LIS::Message::Order.new(13, 12345, "TSTID")
14
+ @string = "R|1|^^^LH|8.2|mIU/mL|.7\.7^400\400|N|N|F||test|19931011091233|19931011091233|DPCCIRRUS"
15
+ @result = LIS::Message::Result.from_string(@string)
16
+ end
17
+
18
+ should "should post correct format to the HTTP endpoint" do
19
+ result_stub = stub_request(:post, "http://localhost/lis/LIS1-12345/TSTID").
20
+ with(:body => { "flags"=>"N",
21
+ "result_timestamp"=>"1993-10-11T09:12:33+00:00",
22
+ "status"=>"F",
23
+ "test_name"=>"TSTID",
24
+ "unit"=>"mIU/mL",
25
+ "value"=>"8.2",
26
+ "raw"=>@string}).
27
+ to_return(:status => 200, :body => "", :headers => {})
28
+
29
+ @interface.send_result(@device_name, @order, @result)
30
+ assert_requested(result_stub)
31
+ end
32
+ end
33
+
34
+ context "requesting test" do
35
+ setup do
36
+ @http_result = { "id" => "1234",
37
+ "patient" => { "number" => 98,
38
+ "last_name" => "Sierra",
39
+ "first_name" => "Rudolph" },
40
+ "types" => [ "TSH", "FT3", "FT4" ] }.to_yaml
41
+ end
42
+
43
+
44
+ should "return patient information and requests" do
45
+ stub_request( :get, "http://localhost/lis/LIS1-SOMETHING").to_return(:status => 301, :headers => { 'Location' => "http://localhost/lis/LIS1-1234" } )
46
+
47
+ result_stub = stub_request(:get, "http://localhost/lis/LIS1-1234")
48
+ result_stub.to_return( :status => 200, :body => @http_result, :headers => {})
49
+ data = @interface.load_requests(@device_name, "SOMETHING")
50
+
51
+ expected = { "id" => "SOMETHING",
52
+ "patient" => { "number" => 98,
53
+ "last_name" => "Sierra",
54
+ "first_name" => "Rudolph" },
55
+ "types" => [ "TSH", "FT3", "FT4" ] }
56
+
57
+ assert_equal expected, data.to_hash
58
+ end
59
+ end
60
+ end
61
+
@@ -70,4 +70,4 @@ class TestMessages < Test::Unit::TestCase
70
70
  end
71
71
  end
72
72
 
73
- end
73
+ end
metadata CHANGED
@@ -1,141 +1,201 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: lis
3
- version: !ruby/object:Gem::Version
4
- hash: 17
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 2
9
- - 3
10
- version: 0.2.3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.3
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Levin Alexander
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-06-11 00:00:00 +02:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
12
+ date: 2013-03-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
22
15
  name: packet_io
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.4.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.4.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: rest-client
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
23
39
  prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ~>
28
- - !ruby/object:Gem::Version
29
- hash: 15
30
- segments:
31
- - 0
32
- - 4
33
- - 0
34
- version: 0.4.0
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
35
54
  type: :runtime
36
- version_requirements: *id001
37
- - !ruby/object:Gem::Dependency
38
- name: shoulda
39
55
  prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- hash: 37
46
- segments:
47
- - 2
48
- - 11
49
- - 3
50
- version: 2.11.3
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: gli
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 2.0.0.rc5
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 2.0.0.rc5
78
+ - !ruby/object:Gem::Dependency
79
+ name: shoulda
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: 3.1.0
51
86
  type: :development
52
- version_requirements: *id002
53
- - !ruby/object:Gem::Dependency
54
- name: mocha
55
87
  prerelease: false
56
- requirement: &id003 !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ~>
60
- - !ruby/object:Gem::Version
61
- hash: 35
62
- segments:
63
- - 0
64
- - 9
65
- - 12
66
- version: 0.9.12
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: 3.1.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: mocha
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: 0.12.0
67
102
  type: :development
68
- version_requirements: *id003
69
- - !ruby/object:Gem::Dependency
70
- name: yard
71
103
  prerelease: false
72
- requirement: &id004 !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ~>
76
- - !ruby/object:Gem::Version
77
- hash: 1
78
- segments:
79
- - 0
80
- - 7
81
- - 1
82
- version: 0.7.1
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: 0.12.0
110
+ - !ruby/object:Gem::Dependency
111
+ name: yard
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
83
118
  type: :development
84
- version_requirements: *id004
85
- - !ruby/object:Gem::Dependency
86
- name: cucumber
87
119
  prerelease: false
88
- requirement: &id005 !ruby/object:Gem::Requirement
89
- none: false
90
- requirements:
91
- - - ~>
92
- - !ruby/object:Gem::Version
93
- hash: 49
94
- segments:
95
- - 0
96
- - 10
97
- - 3
98
- version: 0.10.3
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: cucumber
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: 1.2.0
99
134
  type: :development
100
- version_requirements: *id005
101
- - !ruby/object:Gem::Dependency
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: 1.2.0
142
+ - !ruby/object:Gem::Dependency
102
143
  name: webmock
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: 1.8.7
150
+ type: :development
103
151
  prerelease: false
104
- requirement: &id006 !ruby/object:Gem::Requirement
105
- none: false
106
- requirements:
107
- - - ~>
108
- - !ruby/object:Gem::Version
109
- hash: 7
110
- segments:
111
- - 1
112
- - 6
113
- - 4
114
- version: 1.6.4
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: 1.8.7
158
+ - !ruby/object:Gem::Dependency
159
+ name: aruba
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
115
166
  type: :development
116
- version_requirements: *id006
117
- description: ""
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ description: ''
118
175
  email: mail@levinalex.net
119
- executables:
120
- - lis2http
176
+ executables:
177
+ - lis
121
178
  extensions: []
122
-
123
179
  extra_rdoc_files: []
124
-
125
- files:
180
+ files:
126
181
  - .gitignore
182
+ - .travis.yml
127
183
  - .yardopts
128
184
  - Gemfile
129
185
  - LICENSE
130
186
  - README.markdown
131
187
  - Rakefile
132
- - bin/lis2http
188
+ - bin/lis
133
189
  - features/communication basics.feature
134
190
  - features/lis.feature
191
+ - features/lis_cli.feature
192
+ - features/step_definitions/lis_cli_steps.rb
135
193
  - features/step_definitions/lis_steps.rb
136
194
  - features/support/env.rb
137
195
  - lib/lis.rb
138
- - lib/lis/commands/application.rb
196
+ - lib/lis/cli.rb
197
+ - lib/lis/data.rb
198
+ - lib/lis/http_interface.rb
139
199
  - lib/lis/interface_server.rb
140
200
  - lib/lis/messages.rb
141
201
  - lib/lis/messages/comment.rb
@@ -147,63 +207,60 @@ files:
147
207
  - lib/lis/messages/terminator.rb
148
208
  - lib/lis/transfer/application_protocol.rb
149
209
  - lib/lis/transfer/astm_e1394.rb
210
+ - lib/lis/transfer/logging.rb
150
211
  - lib/lis/version.rb
151
- - lib/lis/worklist_manager_interface.rb
152
212
  - lis.gemspec
153
213
  - test/helper.rb
154
- - test/lib/mock_server.rb
155
214
  - test/messages/test_header.rb
156
215
  - test/messages/test_order.rb
157
216
  - test/messages/test_patients.rb
158
217
  - test/messages/test_terminator.rb
159
218
  - test/test_application_protocol.rb
160
219
  - test/test_astm_e1394.rb
220
+ - test/test_http_interface.rb
161
221
  - test/test_messages.rb
162
- has_rdoc: true
163
222
  homepage: http://github.com/levinalex/lis
164
223
  licenses: []
165
-
166
224
  post_install_message:
167
- rdoc_options:
225
+ rdoc_options:
168
226
  - --charset=UTF-8
169
- require_paths:
227
+ require_paths:
170
228
  - lib
171
- required_ruby_version: !ruby/object:Gem::Requirement
229
+ required_ruby_version: !ruby/object:Gem::Requirement
172
230
  none: false
173
- requirements:
174
- - - ">="
175
- - !ruby/object:Gem::Version
176
- hash: 3
177
- segments:
231
+ requirements:
232
+ - - ! '>='
233
+ - !ruby/object:Gem::Version
234
+ version: '0'
235
+ segments:
178
236
  - 0
179
- version: "0"
180
- required_rubygems_version: !ruby/object:Gem::Requirement
237
+ hash: 4116448878207311103
238
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
239
  none: false
182
- requirements:
183
- - - ">="
184
- - !ruby/object:Gem::Version
185
- hash: 3
186
- segments:
187
- - 0
188
- version: "0"
240
+ requirements:
241
+ - - ! '>='
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
189
244
  requirements: []
190
-
191
245
  rubyforge_project:
192
- rubygems_version: 1.3.7
246
+ rubygems_version: 1.8.25
193
247
  signing_key:
194
248
  specification_version: 3
195
249
  summary: LIS interface to Siemens Immulite 2000XPi or other similar analyzers
196
- test_files:
250
+ test_files:
197
251
  - features/communication basics.feature
198
252
  - features/lis.feature
253
+ - features/lis_cli.feature
254
+ - features/step_definitions/lis_cli_steps.rb
199
255
  - features/step_definitions/lis_steps.rb
200
256
  - features/support/env.rb
201
257
  - test/helper.rb
202
- - test/lib/mock_server.rb
203
258
  - test/messages/test_header.rb
204
259
  - test/messages/test_order.rb
205
260
  - test/messages/test_patients.rb
206
261
  - test/messages/test_terminator.rb
207
262
  - test/test_application_protocol.rb
208
263
  - test/test_astm_e1394.rb
264
+ - test/test_http_interface.rb
209
265
  - test/test_messages.rb
266
+ has_rdoc:
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'lis'
4
- require 'lis/commands/application.rb'
5
-
6
- LIS::Application.new(ARGV).run!
@@ -1,57 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'optparse'
4
-
5
- module LIS
6
- class Options < Hash
7
- attr_reader :opts
8
-
9
- def initialize(args)
10
- super()
11
-
12
- default_options = { :port => "/dev/ttyUSB0", :uri => "http://localhost/lis/" }
13
- self.merge!(default_options)
14
-
15
- @opts = OptionParser.new do |o|
16
- appname = File.basename($0)
17
- o.banner = "Usage: #{appname} [options]"
18
-
19
- o.on('-l, --listen PORT', 'which port to listen on (default: "/dev/ttyUSB0")') do |port|
20
- self[:port] = port
21
- end
22
- o.on('-e, --endpoint URI', 'HTTP endpoint (default: "http://localhost/lis/")') do |endpoint|
23
- self[:uri] = endpoint
24
- end
25
- o.on("-v", "--[no-]verbose", "Run verbosely") do |v|
26
- $VERBOSE = v
27
- self[:verbose] = v
28
- end
29
- end
30
-
31
- begin
32
- @opts.parse!(args)
33
- self[:project_name] = args.shift
34
- rescue OptionParser::ParseError => e
35
- self[:invalid_argument] = e.message
36
- end
37
- end
38
- end
39
-
40
- class Application
41
- def initialize(opts)
42
- @options = Options.new(opts)
43
-
44
- if @options[:invalid_argument]
45
- warn @options.opts
46
- exit 1
47
- end
48
- end
49
-
50
- def run!
51
- warn "listening on: #{@options[:port]}"
52
- port = File.open(@options[:port], "w+")
53
- LIS::InterfaceServer.listen(port, @options[:uri]).run!
54
- end
55
- end
56
-
57
- end
@@ -1,69 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'net/http'
4
- require 'yaml'
5
-
6
- class WorklistManagerInterface
7
- def initialize(endpoint)
8
- @endpoint = endpoint
9
- end
10
-
11
- # expects all pending requests for the given device and barcode
12
- #
13
- # { "id" => "1234",
14
- # "patient" => { "number" => 98,
15
- # "last_name" => "Sierra",
16
- # "first_name" => "Rudolph" },
17
- # "types" => [ "TSH", "FT3", "FT4" ] }
18
- #
19
- #
20
- def load_requests(device_name, barcode)
21
- begin
22
- uri = URI.join(@endpoint,"find_requests/#{[device_name, barcode].join('-')}")
23
- result = fetch_with_redirect(uri.to_s)
24
- data = YAML.load(result.body)
25
- data["id"] = barcode
26
- rescue Exception => e
27
- puts e
28
- puts e.backtrace
29
- data = nil
30
- end
31
-
32
- data
33
- end
34
-
35
- def send_result(device_name, patient, order, result)
36
- barcode = order.specimen_id
37
- data = {
38
- "test_name" => order.universal_test_id,
39
- "value" => result.result_value,
40
- "unit" => result.unit,
41
- "status" => result.result_status,
42
- "flags" => result.abnormal_flags,
43
- "result_timestamp" => result.test_completed_at
44
- }
45
-
46
- # FIXME: WTF: should not just catch everything
47
- begin
48
- res = Net::HTTP.post_form(URI.join(@endpoint, "result/#{[device_name, barcode].join('-')}"), data.to_hash)
49
- rescue Exception => e
50
- puts "EXCEPTION"
51
- p e
52
- end
53
- end
54
-
55
-
56
- private
57
-
58
- def fetch_with_redirect(uri_str, limit = 10)
59
- raise ArgumentError, 'too many HTTP redirects' if limit == 0
60
-
61
- response = Net::HTTP.get_response(URI.parse(uri_str))
62
- case response
63
- when Net::HTTPSuccess then response
64
- when Net::HTTPRedirection then fetch_with_redirect(response['location'], limit - 1)
65
- else
66
- response.error!
67
- end
68
- end
69
- end
@@ -1,50 +0,0 @@
1
- module Mock
2
- end
3
-
4
- class Mock::Server
5
- def initialize(read, write)
6
- @read, @write = read, write
7
- @queue = Queue.new
8
- @thread = Thread.new do
9
- parse_commands
10
- end
11
- end
12
-
13
- def write(string)
14
- @queue.push [:write, string]
15
- self
16
- end
17
-
18
- def read_all
19
- @read.readpartial(4096)
20
- end
21
-
22
- def wait(seconds = 0.02)
23
- @queue.push [:wait, seconds]
24
- self
25
- end
26
-
27
- def eof
28
- @queue.push [:close]
29
- end
30
-
31
-
32
- private
33
-
34
- def parse_commands
35
- loop do
36
- action, data = @queue.pop
37
-
38
- case action
39
- when :close
40
- @write.close
41
- break
42
- when :write
43
- @write.write(data)
44
- @write.flush
45
- when :wait
46
- sleep data
47
- end
48
- end
49
- end
50
- end