blue_button_parser 0.1.0
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/.document +5 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +114 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/blue_button_parser.gemspec +64 -0
- data/lib/blue_button_parser.rb +282 -0
- data/test/data/blue_button_example_data.txt +1479 -0
- data/test/data/expected_json_output.js +1062 -0
- data/test/helper.rb +19 -0
- data/test/test_blue_button_parser.rb +257 -0
- metadata +153 -0
data/test/helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
require 'json'
|
13
|
+
|
14
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
15
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
16
|
+
require 'blue_button_parser'
|
17
|
+
|
18
|
+
class Test::Unit::TestCase
|
19
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class BlueButtonParserTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_parse_section_breaks
|
6
|
+
str = <<-EOS
|
7
|
+
----------------------------- SECTION 1 ----------------------------
|
8
|
+
foo
|
9
|
+
------------------------- SECTION 2 -------------------------
|
10
|
+
Instructions: -- TAKE WITH FOOD --
|
11
|
+
|
12
|
+
---------------------------------
|
13
|
+
EOS
|
14
|
+
bbp = BlueButtonParser.new(str)
|
15
|
+
assert_equal ["SECTION 1", "SECTION 2"], bbp.data.keys, "parser should correctly construct sections in hash output"
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_parse_simple_key_value
|
19
|
+
str = <<-EOS
|
20
|
+
------- SECTION ---------
|
21
|
+
Zip/Post Code: 00001
|
22
|
+
EOS
|
23
|
+
expected = {"SECTION" => {"Zip/Post Code" => "00001"}}
|
24
|
+
bbp = BlueButtonParser.new(str)
|
25
|
+
assert_equal(expected, bbp.data, "should handle simple case with one key-value pair")
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_parse_multiple_key_values
|
29
|
+
str = <<-EOS
|
30
|
+
------- SECTION ---------
|
31
|
+
Name: MHVTESTVETERAN, ONE Date of Birth: 01 Mar 1948
|
32
|
+
EOS
|
33
|
+
expected = {"SECTION" => {"Name" => "MHVTESTVETERAN, ONE", "Date of Birth" => "01 Mar 1948"}}
|
34
|
+
config = {"SECTION" => {:same_line_keys => ["Name", "Date of Birth"]}}
|
35
|
+
bbp = BlueButtonParser.new(str, config)
|
36
|
+
assert_equal(expected, bbp.data, "should handle two key-value pairs in single line")
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_parse_two_sets_of_multiple_key_values_in_same_section
|
40
|
+
str = <<-EOS
|
41
|
+
------- SECTION ---------
|
42
|
+
Name: MHVTESTVETERAN, ONE Date of Birth: 01 Mar 1948
|
43
|
+
Gender: Male Blood Type: AB+ Organ Donor: Yes
|
44
|
+
EOS
|
45
|
+
expected = {"SECTION" => {"Name" => "MHVTESTVETERAN, ONE", "Date of Birth" => "01 Mar 1948", "Gender" => "Male", "Blood Type" => "AB+", "Organ Donor" => "Yes"}}
|
46
|
+
config = {"SECTION" => {:same_line_keys => [["Name", "Date of Birth"], ["Gender", "Blood Type", "Organ Donor"]]}}
|
47
|
+
bbp = BlueButtonParser.new(str, config)
|
48
|
+
assert_equal(expected, bbp.data, "should handle a section having two sets of key-value pairs in single lines")
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_parse_line_wrap_value_with_empty_line_delimiter
|
52
|
+
str = <<-EOS
|
53
|
+
------ SECTION ---------
|
54
|
+
Comments: BP taken standing. BP continues at goal. Doctor says to continue BP
|
55
|
+
medications as directed
|
56
|
+
|
57
|
+
EOS
|
58
|
+
expected = {"SECTION" => {"Comments" => "BP taken standing. BP continues at goal. Doctor says to continue BP \nmedications as directed"}}
|
59
|
+
bbp = BlueButtonParser.new(str)
|
60
|
+
assert_equal(expected, bbp.data, "should handle value that wraps lines")
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_parse_line_wrap_value_without_empty_line_delimiter
|
64
|
+
str = <<-EOS
|
65
|
+
------ SECTION ---------
|
66
|
+
Results: BP taken standing. BP continues at goal. Doctor says to continue BP
|
67
|
+
medications as directed
|
68
|
+
Status: pending
|
69
|
+
EOS
|
70
|
+
expected = {"SECTION" => {"Results" => "BP taken standing. BP continues at goal. Doctor says to continue BP \nmedications as directed", "Status" => "pending"}}
|
71
|
+
bbp = BlueButtonParser.new(str)
|
72
|
+
assert_equal(expected, bbp.data, "should handle value that wraps lines but does not end with empty line")
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_parse_line_wrap_value_with_dashed_line
|
76
|
+
# As seen in IMMUNIZATIONS/Reactions
|
77
|
+
str = <<-EOS
|
78
|
+
------ SECTION ---------
|
79
|
+
Reactions:
|
80
|
+
---------------------------------
|
81
|
+
Pain
|
82
|
+
|
83
|
+
EOS
|
84
|
+
expected = {"SECTION" => {"Reactions" => "Pain"}}
|
85
|
+
bbp = BlueButtonParser.new(str)
|
86
|
+
assert_equal(expected, bbp.data, "should handle value that wraps lines")
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_parse_line_wrap_value_with_keyish_item_in_value
|
90
|
+
str = <<-EOS
|
91
|
+
------ SECTION ---------
|
92
|
+
Note: This appointment has pre-appointment activity scheduled:
|
93
|
+
Lab: 27 Jan 2012 @ 1000
|
94
|
+
|
95
|
+
EOS
|
96
|
+
expected = {"SECTION" => {"Note" => "This appointment has pre-appointment activity scheduled: \n Lab: 27 Jan 2012 @ 1000"}}
|
97
|
+
bbp = BlueButtonParser.new(str)
|
98
|
+
assert_equal(expected, bbp.data, "should handle value that wraps lines, but has a key-like element in the wrapped text")
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_parse_collections_with_empty_line_delimiter
|
102
|
+
str = <<-EOS
|
103
|
+
------ SECTION ---------
|
104
|
+
Contact Name: Foo
|
105
|
+
Contact Email: Zip
|
106
|
+
|
107
|
+
Contact Name: Bar
|
108
|
+
Contact Email: Zap
|
109
|
+
|
110
|
+
EOS
|
111
|
+
expected = {"SECTION" => {"Contacts" => [
|
112
|
+
{"Contact Name" => "Foo", "Contact Email" => "Zip"},
|
113
|
+
{"Contact Name" => "Bar", "Contact Email" => "Zap"}] }}
|
114
|
+
config = {"SECTION" => {:collection => {"Contacts" => {:item_starts_with => "Contact Name"}}}}
|
115
|
+
bbp = BlueButtonParser.new(str, config)
|
116
|
+
assert_equal(expected, bbp.data, "should find collections")
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_parse_collections_with_empty_line_not_a_delimiter
|
120
|
+
# As seen in the "MEDICATIONS AND SUPPLEMENTS" section
|
121
|
+
str = <<-EOS
|
122
|
+
------ SECTION ---------
|
123
|
+
Contact Name: Foo
|
124
|
+
Contact Email: Zip
|
125
|
+
|
126
|
+
Contact Location: MA
|
127
|
+
|
128
|
+
Contact Name: Bar
|
129
|
+
Contact Email: Zap
|
130
|
+
|
131
|
+
Contact Location: CA
|
132
|
+
|
133
|
+
EOS
|
134
|
+
expected = {"SECTION" => {"Contacts" => [
|
135
|
+
{"Contact Name" => "Foo", "Contact Email" => "Zip", "Contact Location" => "MA"},
|
136
|
+
{"Contact Name" => "Bar", "Contact Email" => "Zap", "Contact Location" => "CA"}] }}
|
137
|
+
config = {"SECTION" => {:collection => {"Contacts" => {:item_starts_with => "Contact Name"}}}}
|
138
|
+
bbp = BlueButtonParser.new(str, config)
|
139
|
+
assert_equal(expected, bbp.data, "should find collections where you can't assume empty line is a delimiter")
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_columns_widths
|
143
|
+
bbp = BlueButtonParser.new("")
|
144
|
+
line = " Foo Bar Sup"
|
145
|
+
columns = ["Foo", "Bar", "Sup"]
|
146
|
+
expected_widths = [2, 6, 10]
|
147
|
+
assert_equal expected_widths, bbp.send(:column_widths, line, columns)
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_parse_table_line
|
151
|
+
bbp = BlueButtonParser.new("")
|
152
|
+
line = " a b c "
|
153
|
+
columns = ["Foo", "Bar", "Sup"]
|
154
|
+
widths = [2, 6, 10]
|
155
|
+
expected_output = {"Foo" => "a", "Bar" => "b", "Sup" => "c"}
|
156
|
+
assert_equal expected_output, bbp.send(:parse_table_line, line, columns, widths)
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_parse_tables
|
160
|
+
# Note that "Type" column header does not start at same character position as the row values!
|
161
|
+
str = <<-EOS
|
162
|
+
--------------------- SECTION ---------------------
|
163
|
+
|
164
|
+
VA Treating Facility Type
|
165
|
+
---------------------------- -----------
|
166
|
+
AUSTIN MHV OTHER
|
167
|
+
PORTLAND, OREGON VA MEDICAL CENTER VAMC
|
168
|
+
|
169
|
+
EOS
|
170
|
+
expected = {"SECTION" => {"Facilities" => [
|
171
|
+
{"VA Treating Facility" => "AUSTIN MHV", " Type" => "OTHER"},
|
172
|
+
{"VA Treating Facility" => "PORTLAND, OREGON VA MEDICAL CENTER", " Type" => "VAMC"}] }}
|
173
|
+
config = {"SECTION" => {:collection => {"Facilities" => {:table_columns => ["VA Treating Facility", " Type"]}}}}
|
174
|
+
bbp = BlueButtonParser.new(str, config)
|
175
|
+
assert_equal(expected, bbp.data, "should parse simple table")
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_parse_tables_with_missing_values
|
179
|
+
str = <<-EOS
|
180
|
+
---------------------------- SECTION -------------------------
|
181
|
+
|
182
|
+
Wellness Reminder Due Date Last Completed Location
|
183
|
+
----------------------------------------------------------------------------
|
184
|
+
Control of Your Cholesterol DUE NOW UNKNOWN PORTLAND, OR
|
185
|
+
Pneumonia Vaccine 06 Mar 2011 PORTLAND, OR
|
186
|
+
|
187
|
+
EOS
|
188
|
+
expected = {"SECTION" => {"Reminders" => [
|
189
|
+
{"Wellness Reminder" => "Control of Your Cholesterol", "Due Date" => "DUE NOW", "Last Completed" => "UNKNOWN", "Location" => "PORTLAND, OR"},
|
190
|
+
{"Wellness Reminder" => "Pneumonia Vaccine", "Due Date" => nil, "Last Completed" => "06 Mar 2011", "Location" => "PORTLAND, OR"}
|
191
|
+
]}}
|
192
|
+
config = {"SECTION" => {:collection => {"Reminders" => {:table_columns => ["Wellness Reminder", "Due Date", "Last Completed", "Location"]}}}}
|
193
|
+
bbp = BlueButtonParser.new(str, config)
|
194
|
+
assert_equal(expected, bbp.data, "should parse simple table with missing value")
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_parse_multiple_tables_with_same_columns
|
198
|
+
str = <<-EOS
|
199
|
+
---------------------------- SECTION -------------------------
|
200
|
+
|
201
|
+
-- Regular Active Service
|
202
|
+
Service Begin Date End Date Character of Service Rank
|
203
|
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
204
|
+
Army 06/11/2005 03/26/2007 Honorable COL
|
205
|
+
|
206
|
+
-- Reserve/Guard Association Periods
|
207
|
+
Service Begin Date End Date Character of Service Rank
|
208
|
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
209
|
+
Army Guard 01/11/1987 08/24/1993 Unknown
|
210
|
+
|
211
|
+
EOS
|
212
|
+
expected = {"SECTION" => {
|
213
|
+
"Regular Active Service"=> [{"Service" => "Army", "Begin Date" => "06/11/2005", "End Date" => "03/26/2007", "Character of Service" => "Honorable", "Rank" => "COL"}],
|
214
|
+
"Reserve/Guard Association Periods"=> [{"Service" => "Army Guard", "Begin Date" => "01/11/1987", "End Date" => "08/24/1993", "Character of Service" => "Unknown", "Rank" => nil}]
|
215
|
+
}}
|
216
|
+
config = {"SECTION" => {:collection => {
|
217
|
+
"Regular Active Service" => {:table_starts_with => "-- Regular Active Service", :table_columns => ["Service", "Begin Date", "End Date", "Character of Service", "Rank"] },
|
218
|
+
"Reserve/Guard Association Periods" => {:table_starts_with => "-- Reserve/Guard Association Periods", :table_columns => ["Service", "Begin Date", "End Date", "Character of Service", "Rank"] },
|
219
|
+
}}}
|
220
|
+
bbp = BlueButtonParser.new(str, config)
|
221
|
+
assert_equal(expected, bbp.data)
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_parse_entire_sample_blue_button_document
|
225
|
+
bbp = BlueButtonParser.new(File.read(File.dirname(__FILE__) + "/data/blue_button_example_data.txt"))
|
226
|
+
parsed_data = bbp.data
|
227
|
+
expected_data = JSON.parse(File.read(File.dirname(__FILE__) + "/data/expected_json_output.js"))
|
228
|
+
|
229
|
+
sections = [
|
230
|
+
"MY HEALTHEVET PERSONAL INFORMATION REPORT",
|
231
|
+
"DOWNLOAD REQUEST SUMMARY",
|
232
|
+
"MY HEALTHEVET ACCOUNT SUMMARY",
|
233
|
+
"DEMOGRAPHICS",
|
234
|
+
"HEALTH CARE PROVIDERS",
|
235
|
+
"TREATMENT FACILITIES",
|
236
|
+
"HEALTH INSURANCE",
|
237
|
+
"VA WELLNESS REMINDERS",
|
238
|
+
"VA APPOINTMENTS",
|
239
|
+
"VA MEDICATION HISTORY",
|
240
|
+
"MEDICATIONS AND SUPPLEMENTS",
|
241
|
+
"VA ALLERGIES",
|
242
|
+
"ALLERGIES/ADVERSE REACTIONS",
|
243
|
+
"MEDICAL EVENTS",
|
244
|
+
"IMMUNIZATIONS",
|
245
|
+
"VA LABORATORY RESULTS",
|
246
|
+
"LABS AND TESTS",
|
247
|
+
"VITALS AND READINGS",
|
248
|
+
"FAMILY HEALTH HISTORY",
|
249
|
+
"MILITARY HEALTH HISTORY",
|
250
|
+
]
|
251
|
+
|
252
|
+
sections.each do |section|
|
253
|
+
assert_equal expected_data[section], parsed_data[section], "parsed section does not match expected JSON for section '#{section}'"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blue_button_parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- PatientsLikeMe
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-12-22 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: shoulda
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: bundler
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 23
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 0
|
47
|
+
- 0
|
48
|
+
version: 1.0.0
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: jeweler
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ~>
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 7
|
60
|
+
segments:
|
61
|
+
- 1
|
62
|
+
- 6
|
63
|
+
- 4
|
64
|
+
version: 1.6.4
|
65
|
+
type: :development
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: rcov
|
69
|
+
prerelease: false
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
type: :development
|
80
|
+
version_requirements: *id004
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: json
|
83
|
+
prerelease: false
|
84
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: 3
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
type: :development
|
94
|
+
version_requirements: *id005
|
95
|
+
description: Converts a BlueButton free text data file to a structured data Hash
|
96
|
+
email: open_source@patientslikeme.com
|
97
|
+
executables: []
|
98
|
+
|
99
|
+
extensions: []
|
100
|
+
|
101
|
+
extra_rdoc_files:
|
102
|
+
- LICENSE.txt
|
103
|
+
- README.rdoc
|
104
|
+
files:
|
105
|
+
- .document
|
106
|
+
- Gemfile
|
107
|
+
- Gemfile.lock
|
108
|
+
- LICENSE.txt
|
109
|
+
- README.rdoc
|
110
|
+
- Rakefile
|
111
|
+
- VERSION
|
112
|
+
- blue_button_parser.gemspec
|
113
|
+
- lib/blue_button_parser.rb
|
114
|
+
- test/data/blue_button_example_data.txt
|
115
|
+
- test/data/expected_json_output.js
|
116
|
+
- test/helper.rb
|
117
|
+
- test/test_blue_button_parser.rb
|
118
|
+
has_rdoc: true
|
119
|
+
homepage: http://github.com/patientslikeme/blue_button_parser
|
120
|
+
licenses:
|
121
|
+
- MIT
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
hash: 3
|
133
|
+
segments:
|
134
|
+
- 0
|
135
|
+
version: "0"
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
hash: 3
|
142
|
+
segments:
|
143
|
+
- 0
|
144
|
+
version: "0"
|
145
|
+
requirements: []
|
146
|
+
|
147
|
+
rubyforge_project:
|
148
|
+
rubygems_version: 1.4.1
|
149
|
+
signing_key:
|
150
|
+
specification_version: 3
|
151
|
+
summary: Converts a BlueButton free text data file to a structured data Hash
|
152
|
+
test_files: []
|
153
|
+
|