lis 0.2.2 → 0.2.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.
- 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
|