lis 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/features/lis.feature +2 -2
- data/features/step_definitions/lis_steps.rb +8 -6
- data/lib/lis/commands/application.rb +2 -0
- data/lib/lis/messages/comment.rb +2 -0
- data/lib/lis/messages/header.rb +2 -0
- data/lib/lis/messages/order.rb +2 -0
- data/lib/lis/messages/patient.rb +3 -1
- data/lib/lis/messages/query.rb +2 -0
- data/lib/lis/messages/result.rb +2 -0
- data/lib/lis/messages/terminator.rb +2 -0
- data/lib/lis/messages.rb +2 -0
- data/lib/lis/transfer/application_protocol.rb +1 -0
- data/lib/lis/transfer/astm_e1394.rb +6 -5
- data/lib/lis/version.rb +3 -1
- data/lib/lis/worklist_manager_interface.rb +2 -0
- data/lis.gemspec +6 -6
- metadata +18 -10
- data/lib/lis/application_protocol.rb +0 -104
- data/lib/lis/packetized_protocol.rb +0 -119
data/features/lis.feature
CHANGED
@@ -7,7 +7,7 @@ Feature:
|
|
7
7
|
Given LIS Interface listening for messages
|
8
8
|
And the following requests are pending for DPC:
|
9
9
|
| id | patient_id | last_name | first_name | test_names |
|
10
|
-
| 123ABC | 98 |
|
10
|
+
| 123ABC | 98 | Müller | Rudolph | TSH, FT3, FT4, FOO, BAR, BAZ |
|
11
11
|
When receiving data
|
12
12
|
"""
|
13
13
|
1H|\^&||PASSWORD|DPC||Randolph^New^Jersey^07869||(201)927-2828|N81|Your System||P|1|19940407120613 51
|
@@ -17,7 +17,7 @@ Feature:
|
|
17
17
|
Then LIS should have sent test orders to client:
|
18
18
|
"""
|
19
19
|
1H|\^&|||LIS||||8N1|DPC||P|1|
|
20
|
-
2P|1||||
|
20
|
+
2P|1||||Müller^Rudolph||||||||
|
21
21
|
3O|1|123ABC||^^^TSH
|
22
22
|
4O|1|123ABC||^^^FT3
|
23
23
|
5O|1|123ABC||^^^FT4
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
require 'mocha'
|
2
4
|
require 'yaml'
|
3
5
|
|
@@ -8,7 +10,7 @@ Given /^LIS Interface listening for messages$/ do
|
|
8
10
|
@server = LIS::InterfaceServer.create(@io, "http://localhost/lis/")
|
9
11
|
|
10
12
|
stub_request(:post, /http:\/\/localhost\/lis\/result\/.*/).
|
11
|
-
with(:body => /.*/, :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/x-www-form-urlencoded'
|
13
|
+
with(:body => /.*/, :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/x-www-form-urlencoded'}).
|
12
14
|
to_return(:status => 200, :body => "", :headers => {})
|
13
15
|
|
14
16
|
@t = Thread.new do
|
@@ -49,17 +51,14 @@ end
|
|
49
51
|
#
|
50
52
|
Given /^the following requests are pending for (\w+):$/ do |device_name, table|
|
51
53
|
table.hashes.each do |patient|
|
52
|
-
p patient
|
53
|
-
|
54
54
|
body = { "patient" => { "last_name" => patient["last_name"],
|
55
55
|
"first_name" => patient["first_name"],
|
56
56
|
"id" => patient["patient_id"]},
|
57
57
|
"id" => patient["id"],
|
58
58
|
"types" => patient["test_names"].strip.split(/\s+/) }
|
59
|
-
p body
|
60
59
|
|
61
60
|
stub_request(:get, "http://localhost/lis/find_requests/#{device_name}-#{patient["id"]}").
|
62
|
-
with(:headers => {'Accept'=>'*/*'
|
61
|
+
with(:headers => {'Accept'=>'*/*'}).
|
63
62
|
to_return(:status => 200, :body => body.to_yaml, :headers => {})
|
64
63
|
|
65
64
|
end
|
@@ -69,11 +68,14 @@ end
|
|
69
68
|
|
70
69
|
Then /^LIS should have sent test orders to client:$/ do |text|
|
71
70
|
@data = @client.read_all
|
71
|
+
@data.force_encoding("utf-8") if @data.respond_to?(:force_encoding)
|
72
72
|
@packets = @data.split("\002").select { |s| s =~ /^\d[A-Z]/ }
|
73
|
-
@packets.zip(text.
|
73
|
+
@packets.zip(text.split(/\n/)) do |actual, expected|
|
74
|
+
@called = true
|
74
75
|
rx = Regexp.new("^" + Regexp.escape(expected.strip))
|
75
76
|
assert_match(rx, actual.gsub(/\r\003.*$/, "").strip)
|
76
77
|
end
|
78
|
+
assert_equal(true, @called)
|
77
79
|
end
|
78
80
|
|
79
81
|
|
data/lib/lis/messages/comment.rb
CHANGED
data/lib/lis/messages/header.rb
CHANGED
data/lib/lis/messages/order.rb
CHANGED
data/lib/lis/messages/patient.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
module LIS::Message
|
2
4
|
|
3
5
|
# = Patient Message
|
@@ -72,7 +74,7 @@ module LIS::Message
|
|
72
74
|
self.sequence_number = sequence_number
|
73
75
|
self.practice_assigned_patient_id = patient_id
|
74
76
|
self.patient_id = patient_id
|
75
|
-
self.name = [last_name
|
77
|
+
self.name = [last_name, first_name].join("^")
|
76
78
|
end
|
77
79
|
|
78
80
|
end
|
data/lib/lis/messages/query.rb
CHANGED
data/lib/lis/messages/result.rb
CHANGED
data/lib/lis/messages.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
module LIS::Transfer
|
2
4
|
|
3
5
|
module ASTM
|
@@ -34,14 +36,14 @@ module LIS::Transfer
|
|
34
36
|
|
35
37
|
def initialize(*args)
|
36
38
|
super(*args)
|
37
|
-
@
|
39
|
+
@data = StringScanner.new("")
|
38
40
|
@inside_transmission = false
|
39
41
|
end
|
40
42
|
|
41
43
|
def receive(data)
|
42
|
-
|
43
|
-
while
|
44
|
-
match =
|
44
|
+
@data.concat(data)
|
45
|
+
while @data.scan_until(RX)
|
46
|
+
match = @data.matched
|
45
47
|
case match
|
46
48
|
when ENQ then transmission_start
|
47
49
|
when EOT then transmission_end
|
@@ -51,7 +53,6 @@ module LIS::Transfer
|
|
51
53
|
write :ack
|
52
54
|
end
|
53
55
|
end
|
54
|
-
@memo = scanner.rest
|
55
56
|
nil
|
56
57
|
end
|
57
58
|
|
data/lib/lis/version.rb
CHANGED
data/lis.gemspec
CHANGED
@@ -20,12 +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
|
23
|
+
s.add_dependency "packet_io", "~> 0.4.0"
|
24
24
|
|
25
|
-
s.add_development_dependency("shoulda",
|
26
|
-
s.add_development_dependency("mocha",
|
27
|
-
s.add_development_dependency("yard",
|
28
|
-
s.add_development_dependency("cucumber",
|
29
|
-
s.add_development_dependency("webmock")
|
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")
|
30
30
|
end
|
31
31
|
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 17
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
8
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
9
|
+
- 3
|
10
|
+
version: 0.2.3
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Levin Alexander
|
@@ -14,7 +15,7 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2011-06-
|
18
|
+
date: 2011-06-11 00:00:00 +02:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
@@ -25,12 +26,12 @@ dependencies:
|
|
25
26
|
requirements:
|
26
27
|
- - ~>
|
27
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 15
|
28
30
|
segments:
|
29
31
|
- 0
|
30
32
|
- 4
|
31
33
|
- 0
|
32
|
-
|
33
|
-
version: 0.4.0.rc4
|
34
|
+
version: 0.4.0
|
34
35
|
type: :runtime
|
35
36
|
version_requirements: *id001
|
36
37
|
- !ruby/object:Gem::Dependency
|
@@ -41,6 +42,7 @@ dependencies:
|
|
41
42
|
requirements:
|
42
43
|
- - ~>
|
43
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 37
|
44
46
|
segments:
|
45
47
|
- 2
|
46
48
|
- 11
|
@@ -56,6 +58,7 @@ dependencies:
|
|
56
58
|
requirements:
|
57
59
|
- - ~>
|
58
60
|
- !ruby/object:Gem::Version
|
61
|
+
hash: 35
|
59
62
|
segments:
|
60
63
|
- 0
|
61
64
|
- 9
|
@@ -71,6 +74,7 @@ dependencies:
|
|
71
74
|
requirements:
|
72
75
|
- - ~>
|
73
76
|
- !ruby/object:Gem::Version
|
77
|
+
hash: 1
|
74
78
|
segments:
|
75
79
|
- 0
|
76
80
|
- 7
|
@@ -86,6 +90,7 @@ dependencies:
|
|
86
90
|
requirements:
|
87
91
|
- - ~>
|
88
92
|
- !ruby/object:Gem::Version
|
93
|
+
hash: 49
|
89
94
|
segments:
|
90
95
|
- 0
|
91
96
|
- 10
|
@@ -99,11 +104,14 @@ dependencies:
|
|
99
104
|
requirement: &id006 !ruby/object:Gem::Requirement
|
100
105
|
none: false
|
101
106
|
requirements:
|
102
|
-
- -
|
107
|
+
- - ~>
|
103
108
|
- !ruby/object:Gem::Version
|
109
|
+
hash: 7
|
104
110
|
segments:
|
105
|
-
-
|
106
|
-
|
111
|
+
- 1
|
112
|
+
- 6
|
113
|
+
- 4
|
114
|
+
version: 1.6.4
|
107
115
|
type: :development
|
108
116
|
version_requirements: *id006
|
109
117
|
description: ""
|
@@ -127,7 +135,6 @@ files:
|
|
127
135
|
- features/step_definitions/lis_steps.rb
|
128
136
|
- features/support/env.rb
|
129
137
|
- lib/lis.rb
|
130
|
-
- lib/lis/application_protocol.rb
|
131
138
|
- lib/lis/commands/application.rb
|
132
139
|
- lib/lis/interface_server.rb
|
133
140
|
- lib/lis/messages.rb
|
@@ -138,7 +145,6 @@ files:
|
|
138
145
|
- lib/lis/messages/query.rb
|
139
146
|
- lib/lis/messages/result.rb
|
140
147
|
- lib/lis/messages/terminator.rb
|
141
|
-
- lib/lis/packetized_protocol.rb
|
142
148
|
- lib/lis/transfer/application_protocol.rb
|
143
149
|
- lib/lis/transfer/astm_e1394.rb
|
144
150
|
- lib/lis/version.rb
|
@@ -167,6 +173,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
167
173
|
requirements:
|
168
174
|
- - ">="
|
169
175
|
- !ruby/object:Gem::Version
|
176
|
+
hash: 3
|
170
177
|
segments:
|
171
178
|
- 0
|
172
179
|
version: "0"
|
@@ -175,6 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
175
182
|
requirements:
|
176
183
|
- - ">="
|
177
184
|
- !ruby/object:Gem::Version
|
185
|
+
hash: 3
|
178
186
|
segments:
|
179
187
|
- 0
|
180
188
|
version: "0"
|
@@ -1,104 +0,0 @@
|
|
1
|
-
|
2
|
-
module LIS::Transfer
|
3
|
-
class ApplicationProtocol < 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
|
@@ -1,119 +0,0 @@
|
|
1
|
-
|
2
|
-
module LIS::Transfer
|
3
|
-
|
4
|
-
# splits a stream into lis packets and only lets packets through
|
5
|
-
# that are inside a session delimited by ENQ .. EOT
|
6
|
-
#
|
7
|
-
# check the checksum and do acknowledgement of messages
|
8
|
-
#
|
9
|
-
# forwards the following events:
|
10
|
-
#
|
11
|
-
# - :message, String :: when a message is received
|
12
|
-
# - :idle :: when a transmission is finished (after EOT is received)
|
13
|
-
#
|
14
|
-
class PacketizedProtocol < Base
|
15
|
-
ACK = "\006"
|
16
|
-
NAK = "\025"
|
17
|
-
ENQ = "\005"
|
18
|
-
EOT = "\004"
|
19
|
-
|
20
|
-
# format of a message
|
21
|
-
RX = /(?:
|
22
|
-
\005 | # ENQ - start a transaction
|
23
|
-
\004 | # EOT - ends a transaction
|
24
|
-
\005 | # ACK
|
25
|
-
\025 | # NAK
|
26
|
-
(?:\002 (.) (.*?) \015 \003 (.+?) \015 \012) # a message with a checksum
|
27
|
-
# | | `-- checksum
|
28
|
-
# | `------------------ message
|
29
|
-
# `---------------------- frame number
|
30
|
-
)
|
31
|
-
/xm
|
32
|
-
|
33
|
-
def initialize(*args)
|
34
|
-
super(*args)
|
35
|
-
@memo = ""
|
36
|
-
@inside_transmission = false
|
37
|
-
end
|
38
|
-
|
39
|
-
def receive(data)
|
40
|
-
scanner = StringScanner.new(@memo + data)
|
41
|
-
while scanner.scan_until(RX)
|
42
|
-
match = scanner.matched
|
43
|
-
case match
|
44
|
-
when ENQ then transmission_start
|
45
|
-
when EOT then transmission_end
|
46
|
-
when ACK, NAK then nil
|
47
|
-
else
|
48
|
-
received_message(match)
|
49
|
-
write :ack
|
50
|
-
end
|
51
|
-
end
|
52
|
-
@memo = scanner.rest
|
53
|
-
nil
|
54
|
-
end
|
55
|
-
|
56
|
-
def write(type, data = nil)
|
57
|
-
str = case type
|
58
|
-
when :ack then ACK
|
59
|
-
when :nak then NAK
|
60
|
-
when :begin then
|
61
|
-
@frame_number = 0
|
62
|
-
ENQ
|
63
|
-
when :idle then EOT
|
64
|
-
when :message then
|
65
|
-
@frame_number = (@frame_number + 1) % 8
|
66
|
-
self.class.wrap_message(data, @frame_number)
|
67
|
-
else
|
68
|
-
raise ArgumentError
|
69
|
-
end
|
70
|
-
super(str)
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
def self.message_from_string(string)
|
77
|
-
match = string.match(RX)
|
78
|
-
|
79
|
-
frame_number, data, checksum = match[1 .. 3]
|
80
|
-
|
81
|
-
expected_checksum = (frame_number + data).each_byte.inject(16) { |a,b| (a+b) % 0x100 }
|
82
|
-
actual_checksum = checksum.to_i(16)
|
83
|
-
|
84
|
-
raise "checksum mismatch" unless expected_checksum == actual_checksum
|
85
|
-
return [frame_number.to_i, data]
|
86
|
-
end
|
87
|
-
|
88
|
-
def self.wrap_message(string, frame_number)
|
89
|
-
frame_number = (frame_number % 8).to_s
|
90
|
-
checksum = (frame_number + string).each_byte.inject(16) { |a,b| (a+b) % 0x100 }
|
91
|
-
checksum = checksum.to_s(16).upcase.rjust(2,"0")
|
92
|
-
|
93
|
-
"\002#{frame_number}#{string}\015\003#{checksum}\015\012"
|
94
|
-
end
|
95
|
-
|
96
|
-
|
97
|
-
def received_message(message)
|
98
|
-
return false unless @inside_transmission
|
99
|
-
frame_number, message = *self.class.message_from_string(message)
|
100
|
-
forward(:message, message)
|
101
|
-
end
|
102
|
-
|
103
|
-
def transmission_start
|
104
|
-
return false if @inside_transmission
|
105
|
-
write :ack
|
106
|
-
forward :begin
|
107
|
-
@inside_transmission = true
|
108
|
-
true
|
109
|
-
end
|
110
|
-
|
111
|
-
def transmission_end
|
112
|
-
return false unless @inside_transmission
|
113
|
-
forward :idle
|
114
|
-
@inside_transmission = false
|
115
|
-
true
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
end
|