lis 0.2.1 → 0.2.2

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.
data/lib/lis/messages.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'date'
2
2
 
3
3
  module LIS::Message
4
+
5
+ # declarative definition of message structure
6
+ #
4
7
  module ClassMethods
5
8
  CONVERSION_WRITER = {
6
9
  :string => lambda { |s| s },
@@ -13,21 +16,6 @@ module LIS::Message
13
16
  @field_count
14
17
  end
15
18
 
16
- def from_string(message)
17
- type, data = parse(message)
18
- klass = (@@messages_by_type || {})[type]
19
- raise "unknown message type #{type.inspect}" unless klass
20
-
21
- obj = klass.allocate
22
-
23
- data = data.to_enum(:each_with_index).inject({}) do |h,(elem,idx)|
24
- h[idx+2] = elem; h
25
- end
26
-
27
- obj.instance_variable_set("@fields", data)
28
- obj
29
- end
30
-
31
19
  def default_fields
32
20
  arr = Array.new(@field_count)
33
21
  (0 .. @field_count).inject(arr) do |a,i|
@@ -40,6 +28,8 @@ module LIS::Message
40
28
  end
41
29
  end
42
30
 
31
+ # define a field of a message at a specific index and an optional type
32
+ #
43
33
  def has_field(idx, name = nil, opts={})
44
34
  set_index_for(name, idx) if name
45
35
  set_field_attributes(idx, { :name => name,
@@ -65,13 +55,8 @@ module LIS::Message
65
55
  end
66
56
  end
67
57
 
68
- def parse(string)
69
- type, data = string.scan(/^(.)\|(.*)$/)[0]
70
- data = data.split(/\|/)
71
-
72
- return [type, data]
73
- end
74
-
58
+ # registers a message with a specific type prefix to make {Message::Base.from_string} work
59
+ #
75
60
  def type_id(char = nil)
76
61
  return @@messages_by_type.find {|c,klass| klass == self }.first unless char
77
62
  @@messages_by_type ||= {}
@@ -100,11 +85,25 @@ module LIS::Message
100
85
  @field_names ||= {}
101
86
  @field_names[index] = hash
102
87
  end
88
+
89
+ private
90
+
91
+ def class_for_type(type)
92
+ klass = (@@messages_by_type || {})[type]
93
+ raise "unknown message type #{type.inspect}" unless klass
94
+ return klass
95
+ end
96
+
97
+ def parse(string)
98
+ type, data = string.scan(/^(.)\|(.*)$/)[0]
99
+ data = data.split(/\|/)
100
+
101
+ return [type, data]
102
+ end
103
103
  end
104
104
 
105
105
  class Base
106
106
  extend ClassMethods
107
- attr_accessor :frame_number
108
107
 
109
108
  has_field 2, :sequence_number, :type => :int, :default => 1
110
109
 
@@ -112,11 +111,36 @@ module LIS::Message
112
111
  self.class.type_id
113
112
  end
114
113
 
114
+ # serialize a Message object into a String
115
+ #
116
+ # message = Message.from_string("5L|1|N") #=> <LIS::Message>
117
+ # message.to_message #=> "5L|1|N"
118
+ #
115
119
  def to_message
116
120
  @fields ||= {}
117
121
  arr = Array.new(self.class.default_fields)
118
122
  type_id + @fields.inject(arr) { |a,(k,v)| a[k-1] = v; a }.join("|")
119
123
  end
120
- end
121
124
 
125
+ # instantiate a new Message from a string
126
+ #
127
+ # Message.from_string("5L|1|N") #=> <LIS::Message::Terminator>
128
+ #
129
+ # register subclasses by using {ClassMethods#type_id type_id}
130
+ #
131
+ def self.from_string(message)
132
+ type, data = parse(message)
133
+ klass = class_for_type(type)
134
+
135
+ obj = klass.allocate
136
+
137
+ data = data.to_enum(:each_with_index).inject({}) do |h,(elem,idx)|
138
+ h[idx+2] = elem; h
139
+ end
140
+
141
+ obj.instance_variable_set("@fields", data)
142
+ obj
143
+ end
144
+ end
122
145
  end
146
+
@@ -0,0 +1,104 @@
1
+
2
+ module LIS::Transfer
3
+ class ApplicationProtocol < ::PacketIO::Base
4
+ attr_reader :device_name
5
+
6
+ def on_result(&block)
7
+ @on_result_callback = block
8
+ end
9
+
10
+ def on_request(&block)
11
+ @on_request_callback = block
12
+ end
13
+
14
+ def received_header(message)
15
+ @patient_information_requests ||= {} # delete the list of patients
16
+ @device_name = message.sender_name
17
+ end
18
+
19
+ def result_for(patient, order, result)
20
+ @on_result_callback.call(@device_name, patient, order, result)
21
+ end
22
+
23
+ def received_patient_information(message)
24
+ @last_order = nil
25
+ @last_patient = message
26
+ end
27
+
28
+ def received_order_record(message)
29
+ @last_order = message
30
+ end
31
+
32
+ def received_result(message)
33
+ result_for(@last_patient, @last_order, message)
34
+ end
35
+
36
+ def received_request_for_information(message)
37
+ @patient_information_requests ||= {}
38
+ requests = @on_request_callback.call(@device_name, message.starting_range_id)
39
+ @patient_information_requests[message.sequence_number] = requests if requests
40
+ end
41
+
42
+ def send_pending_requests
43
+ sending_session(@patient_information_requests) do |patient_information|
44
+ patient_information.each do |sequence_nr, data|
45
+ write :message, LIS::Message::Patient.new(sequence_nr,
46
+ data["patient"]["number"],
47
+ data["patient"]["last_name"],
48
+ data["patient"]["first_name"]).to_message
49
+ data["types"].each do |request|
50
+ write :message, LIS::Message::Order.new(sequence_nr, data["id"], request).to_message
51
+ end
52
+ end
53
+ end
54
+ @patient_information_requests = {}
55
+ end
56
+
57
+ def initialize(*args)
58
+ super
59
+
60
+ @patient_information_requests = {}
61
+ @last_patient = nil
62
+ @last_order = nil
63
+ @handlers = {
64
+ LIS::Message::Header => :received_header,
65
+ LIS::Message::Patient => :received_patient_information,
66
+ LIS::Message::Order => :received_order_record,
67
+ LIS::Message::Result => :received_result,
68
+ LIS::Message::Query => :received_request_for_information
69
+ }
70
+ end
71
+
72
+ def receive(type, message = nil)
73
+ warn "[R] #{message}" if type == :message and $VERBOSE
74
+ case type
75
+ when :begin
76
+ @last_patient = nil
77
+ @last_order = nil
78
+ when :idle
79
+ send_pending_requests
80
+ when :message
81
+ @message = LIS::Message::Base.from_string(message)
82
+ handler = @handlers[@message.class]
83
+ send(handler, @message) if handler
84
+ end
85
+ end
86
+
87
+ def write(type, message=nil)
88
+ warn "[S] #{message}" if type == :message and $VERBOSE
89
+ super
90
+ end
91
+
92
+ # @yield data
93
+ def sending_session(data)
94
+ # don't send anything if there are no pending requests
95
+ return if data.nil? or data.empty?
96
+
97
+ write :begin
98
+ write :message, LIS::Message::Header.new("LIS", @device_name).to_message
99
+ yield data
100
+ write :message, LIS::Message::Terminator.new.to_message
101
+ write :idle
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,121 @@
1
+ module LIS::Transfer
2
+
3
+ module ASTM
4
+ end
5
+
6
+ # splits a stream into lis packets and only lets packets through
7
+ # that are inside a session delimited by ENQ .. EOT
8
+ #
9
+ # check the checksum and do acknowledgement of messages
10
+ #
11
+ # forwards the following events:
12
+ #
13
+ # - :message, String :: when a message is received
14
+ # - :idle :: when a transmission is finished (after EOT is received)
15
+ #
16
+ class ASTM::E1394 < ::PacketIO::Base
17
+ ACK = "\006"
18
+ NAK = "\025"
19
+ ENQ = "\005"
20
+ EOT = "\004"
21
+
22
+ # format of a message
23
+ RX = /(?:
24
+ \005 | # ENQ - start a transaction
25
+ \004 | # EOT - ends a transaction
26
+ \005 | # ACK
27
+ \025 | # NAK
28
+ (?:\002 (.) (.*?) \015 \003 (.+?) \015 \012) # a message with a checksum
29
+ # | | `-- checksum
30
+ # | `------------------ message
31
+ # `---------------------- frame number
32
+ )
33
+ /xm
34
+
35
+ def initialize(*args)
36
+ super(*args)
37
+ @memo = ""
38
+ @inside_transmission = false
39
+ end
40
+
41
+ def receive(data)
42
+ scanner = StringScanner.new(@memo + data)
43
+ while scanner.scan_until(RX)
44
+ match = scanner.matched
45
+ case match
46
+ when ENQ then transmission_start
47
+ when EOT then transmission_end
48
+ when ACK, NAK then nil
49
+ else
50
+ received_message(match)
51
+ write :ack
52
+ end
53
+ end
54
+ @memo = scanner.rest
55
+ nil
56
+ end
57
+
58
+ def write(type, data = nil)
59
+ str = case type
60
+ when :ack then ACK
61
+ when :nak then NAK
62
+ when :begin then
63
+ @frame_number = 0
64
+ ENQ
65
+ when :idle then EOT
66
+ when :message then
67
+ @frame_number = (@frame_number + 1) % 8
68
+ self.class.wrap_message(data, @frame_number)
69
+ else
70
+ raise ArgumentError
71
+ end
72
+ super(str)
73
+ end
74
+
75
+
76
+ private
77
+
78
+ def self.message_from_string(string)
79
+ match = string.match(RX)
80
+
81
+ frame_number, data, checksum = match[1 .. 3]
82
+
83
+ expected_checksum = (frame_number + data).each_byte.inject(16) { |a,b| (a+b) % 0x100 }
84
+ actual_checksum = checksum.to_i(16)
85
+
86
+ raise "checksum mismatch: expected %03x got %03x" % [expected_checksum, actual_checksum] unless expected_checksum == actual_checksum
87
+ return [frame_number.to_i, data]
88
+ end
89
+
90
+ def self.wrap_message(string, frame_number)
91
+ frame_number = (frame_number % 8).to_s
92
+ checksum = (frame_number + string).each_byte.inject(16) { |a,b| (a+b) % 0x100 }
93
+ checksum = checksum.to_s(16).upcase.rjust(2,"0")
94
+
95
+ "\002#{frame_number}#{string}\015\003#{checksum}\015\012"
96
+ end
97
+
98
+
99
+ def received_message(message)
100
+ return false unless @inside_transmission
101
+ frame_number, message = *self.class.message_from_string(message)
102
+ forward(:message, message)
103
+ end
104
+
105
+ def transmission_start
106
+ return false if @inside_transmission
107
+ write :ack
108
+ forward :begin
109
+ @inside_transmission = true
110
+ true
111
+ end
112
+
113
+ def transmission_end
114
+ return false unless @inside_transmission
115
+ forward :idle
116
+ @inside_transmission = false
117
+ true
118
+ end
119
+ end
120
+ end
121
+
@@ -0,0 +1,4 @@
1
+ module LIS
2
+ VERSION = "0.2.2"
3
+ end
4
+
@@ -6,6 +6,15 @@ class WorklistManagerInterface
6
6
  @endpoint = endpoint
7
7
  end
8
8
 
9
+ # expects all pending requests for the given device and barcode
10
+ #
11
+ # { "id" => "1234",
12
+ # "patient" => { "number" => 98,
13
+ # "last_name" => "Sierra",
14
+ # "first_name" => "Rudolph" },
15
+ # "types" => [ "TSH", "FT3", "FT4" ] }
16
+ #
17
+ #
9
18
  def load_requests(device_name, barcode)
10
19
  begin
11
20
  uri = URI.join(@endpoint,"find_requests/#{[device_name, barcode].join('-')}")
@@ -32,7 +41,7 @@ class WorklistManagerInterface
32
41
  "result_timestamp" => result.test_completed_at
33
42
  }
34
43
 
35
- p data
44
+ # FIXME: WTF: should not just catch everything
36
45
  begin
37
46
  res = Net::HTTP.post_form(URI.join(@endpoint, "result/#{[device_name, barcode].join('-')}"), data.to_hash)
38
47
  rescue Exception => e
data/lib/lis.rb CHANGED
@@ -1,16 +1,15 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
2
 
3
- module LIS
4
- VERSION = "0.2.1"
5
- end
3
+ require 'packet_io'
6
4
 
7
- require 'lis/io_listener.rb'
8
- require 'lis/messages.rb'
5
+ require 'lis/version'
9
6
 
7
+ require 'lis/messages.rb'
10
8
  Dir[File.join(File.dirname(__FILE__), 'lis/messages/**/*.rb')].each { |f| require f }
11
9
 
12
- require 'lis/packetized_protocol.rb'
13
- require 'lis/application_protocol.rb'
10
+ require 'lis/transfer/astm_e1394.rb'
11
+ require 'lis/transfer/application_protocol.rb'
12
+
14
13
  require 'lis/worklist_manager_interface.rb'
15
14
  require 'lis/interface_server.rb'
16
15
 
data/lis.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
- require 'lis'
3
+ require 'lis/version'
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = %q{lis}
@@ -20,9 +20,12 @@ 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.rc4"
24
+
23
25
  s.add_development_dependency("shoulda", ["~> 2.11.3"])
24
26
  s.add_development_dependency("mocha", ["~> 0.9.12"])
25
27
  s.add_development_dependency("yard", ["~> 0.7.1"])
26
28
  s.add_development_dependency("cucumber", ["~> 0.10.3"])
29
+ s.add_development_dependency("webmock")
27
30
  end
28
31
 
data/test/helper.rb CHANGED
@@ -2,7 +2,6 @@ require 'rubygems'
2
2
  require 'test/unit'
3
3
  require 'shoulda'
4
4
  require 'mocha'
5
- require 'lib/mock_server'
6
5
 
7
6
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
7
  $LOAD_PATH.unshift(File.dirname(__FILE__))
@@ -17,7 +17,7 @@ class TestPacketizedProtocol < Test::Unit::TestCase
17
17
  str = self.class.gsub_nonprintable(str)
18
18
  expected = self.class.gsub_nonprintable(expected)
19
19
 
20
- match = LIS::Transfer::PacketizedProtocol::RX.match(str)
20
+ match = LIS::Transfer::ASTM::E1394::RX.match(str)
21
21
  assert_not_nil match, expected
22
22
  assert_equal expected, match[0]
23
23
  end
@@ -39,7 +39,7 @@ class TestPacketizedProtocol < Test::Unit::TestCase
39
39
  setup do
40
40
  @sent = []
41
41
  @data = []
42
- @protocol = LIS::Transfer::PacketizedProtocol.new(nil, @sent)
42
+ @protocol = LIS::Transfer::ASTM::E1394.new(nil, @sent)
43
43
  @protocol.on_data do |*d|
44
44
  @data << d
45
45
  end
@@ -95,4 +95,4 @@ class TestPacketizedProtocol < Test::Unit::TestCase
95
95
  end
96
96
  end
97
97
 
98
- end
98
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 1
9
- version: 0.2.1
8
+ - 2
9
+ version: 0.2.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Levin Alexander
@@ -14,13 +14,29 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-05-30 00:00:00 +02:00
17
+ date: 2011-06-01 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: shoulda
21
+ name: packet_io
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 4
31
+ - 0
32
+ - rc4
33
+ version: 0.4.0.rc4
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: shoulda
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
24
40
  none: false
25
41
  requirements:
26
42
  - - ~>
@@ -31,11 +47,11 @@ dependencies:
31
47
  - 3
32
48
  version: 2.11.3
33
49
  type: :development
34
- version_requirements: *id001
50
+ version_requirements: *id002
35
51
  - !ruby/object:Gem::Dependency
36
52
  name: mocha
37
53
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
54
+ requirement: &id003 !ruby/object:Gem::Requirement
39
55
  none: false
40
56
  requirements:
41
57
  - - ~>
@@ -46,11 +62,11 @@ dependencies:
46
62
  - 12
47
63
  version: 0.9.12
48
64
  type: :development
49
- version_requirements: *id002
65
+ version_requirements: *id003
50
66
  - !ruby/object:Gem::Dependency
51
67
  name: yard
52
68
  prerelease: false
53
- requirement: &id003 !ruby/object:Gem::Requirement
69
+ requirement: &id004 !ruby/object:Gem::Requirement
54
70
  none: false
55
71
  requirements:
56
72
  - - ~>
@@ -61,11 +77,11 @@ dependencies:
61
77
  - 1
62
78
  version: 0.7.1
63
79
  type: :development
64
- version_requirements: *id003
80
+ version_requirements: *id004
65
81
  - !ruby/object:Gem::Dependency
66
82
  name: cucumber
67
83
  prerelease: false
68
- requirement: &id004 !ruby/object:Gem::Requirement
84
+ requirement: &id005 !ruby/object:Gem::Requirement
69
85
  none: false
70
86
  requirements:
71
87
  - - ~>
@@ -76,7 +92,20 @@ dependencies:
76
92
  - 3
77
93
  version: 0.10.3
78
94
  type: :development
79
- version_requirements: *id004
95
+ version_requirements: *id005
96
+ - !ruby/object:Gem::Dependency
97
+ name: webmock
98
+ prerelease: false
99
+ requirement: &id006 !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ type: :development
108
+ version_requirements: *id006
80
109
  description: ""
81
110
  email: mail@levinalex.net
82
111
  executables:
@@ -86,14 +115,12 @@ extensions: []
86
115
  extra_rdoc_files: []
87
116
 
88
117
  files:
89
- - .document
90
118
  - .gitignore
91
119
  - .yardopts
92
120
  - Gemfile
93
121
  - LICENSE
94
122
  - README.markdown
95
123
  - Rakefile
96
- - VERSION
97
124
  - bin/lis2http
98
125
  - features/communication basics.feature
99
126
  - features/lis.feature
@@ -103,7 +130,6 @@ files:
103
130
  - lib/lis/application_protocol.rb
104
131
  - lib/lis/commands/application.rb
105
132
  - lib/lis/interface_server.rb
106
- - lib/lis/io_listener.rb
107
133
  - lib/lis/messages.rb
108
134
  - lib/lis/messages/comment.rb
109
135
  - lib/lis/messages/header.rb
@@ -113,6 +139,9 @@ files:
113
139
  - lib/lis/messages/result.rb
114
140
  - lib/lis/messages/terminator.rb
115
141
  - lib/lis/packetized_protocol.rb
142
+ - lib/lis/transfer/application_protocol.rb
143
+ - lib/lis/transfer/astm_e1394.rb
144
+ - lib/lis/version.rb
116
145
  - lib/lis/worklist_manager_interface.rb
117
146
  - lis.gemspec
118
147
  - test/helper.rb
@@ -122,9 +151,8 @@ files:
122
151
  - test/messages/test_patients.rb
123
152
  - test/messages/test_terminator.rb
124
153
  - test/test_application_protocol.rb
125
- - test/test_io_listener.rb
154
+ - test/test_astm_e1394.rb
126
155
  - test/test_messages.rb
127
- - test/test_packetized_protocol.rb
128
156
  has_rdoc: true
129
157
  homepage: http://github.com/levinalex/lis
130
158
  licenses: []
@@ -169,6 +197,5 @@ test_files:
169
197
  - test/messages/test_patients.rb
170
198
  - test/messages/test_terminator.rb
171
199
  - test/test_application_protocol.rb
172
- - test/test_io_listener.rb
200
+ - test/test_astm_e1394.rb
173
201
  - test/test_messages.rb
174
- - test/test_packetized_protocol.rb
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.0