hippo 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ 0.2.2 - 2012/01/25
2
+ * Fix issue with segment parsing. Make sure to set each segments parent
3
+ properly once we identify that it belongs in the particular transaction
4
+ set. (This allows traversing via segment.parent.parent.)
5
+ * Add ancestors method to TransactionSet::Base,TransactionSet::RepeatingComponent,
6
+ and Segments::Base which returns an array containing each ancestor (parent)
7
+ from the current segment to the outermost containing element.
8
+ * Fix issue causing non-required fields to be blank filled.
9
+ * Refactor Field#string_value and Field#formatted_value to handle more situations.
10
+ * Add segments method to TransactionSet::Base,TransactionSet::RepeatingComponent,
11
+ and Segments::Base which returns a flattened array of segments within each
12
+ container. (Segment::Base simply returns [self].)
13
+ * Refactor TransactionSet::Base#segment_count to use new segments array.
14
+ * Add shortcut methods to Hippo::Parser for parse_file and parse_string. Now they
15
+ can be called without creating an instance of Parser. (i.e Hippo::Parser.parse_string(s))
16
+ * Fix issue causing access to empty composite fields to throw an error ([] for NilClass).
1
17
  0.2.1 - 2012/01/21
2
18
  * Fix issue preventing fixed width segments from printing
3
19
  properly. (Non set fields on ISA were empty not padded.)
data/README.md CHANGED
@@ -40,45 +40,45 @@ guide please review [test/test_hipaa_837.rb](/promedical/hippo/blob/master/test/
40
40
  Below is a small sample of how to create a transaction set.
41
41
 
42
42
  ```ruby
43
- ts = Hippo::TransactionSets::HIPAA_837::Base.new
44
-
45
- ts.ST do |st|
46
- st.TransactionSetControlNumber = '0021'
47
- st.ImplementationConventionReference = '005010X222A1'
48
- end
49
-
50
- ts.BHT do |bht|
51
- bht.TransactionSetPurposeCode = '00'
52
- bht.ReferenceIdentification = '244579'
53
- bht.Date = '20061015'
54
- bht.Time = '1023'
55
- bht.TransactionTypeCode = 'CH'
56
- end
57
-
58
- ts.L1000A do |l1000a|
59
- l1000a.NM1 do |nm1|
60
- nm1.EntityTypeQualifier = '2'
61
- nm1.NameLastOrOrganizationName = 'PREMIER BILLING SERVICE'
62
- nm1.IdentificationCode = 'TGJ23'
63
- end
64
-
65
- l1000a.PER do |per|
66
- per.Name = 'JERRY'
67
- per.CommunicationNumberQualifier_01 = 'TE'
68
- per.CommunicationNumber_01 = '3055552222'
69
- per.CommunicationNumberQualifier_02 = 'EX'
70
- per.CommunicationNumber_02 = '231'
71
- end
72
- end
73
-
74
- puts ts.to_s
75
-
76
- # Below is the output of ts.to_s (split onto separate lines for readability)
77
- #
78
- # ST*837*0021*005010X222A1~
79
- # BHT*0019*00*244579*20061015*1023*CH~
80
- # NM1*41*2*PREMIER BILLING SERVICE*****46*TGJ23~
81
- # PER*IC*JERRY*TE*3055552222*EX*231~
43
+ ts = Hippo::TransactionSets::HIPAA_837::Base.new
44
+
45
+ ts.ST do |st|
46
+ st.TransactionSetControlNumber = '0021'
47
+ st.ImplementationConventionReference = '005010X222A1'
48
+ end
49
+
50
+ ts.BHT do |bht|
51
+ bht.TransactionSetPurposeCode = '00'
52
+ bht.ReferenceIdentification = '244579'
53
+ bht.Date = '20061015'
54
+ bht.Time = '1023'
55
+ bht.TransactionTypeCode = 'CH'
56
+ end
57
+
58
+ ts.L1000A do |l1000a|
59
+ l1000a.NM1 do |nm1|
60
+ nm1.EntityTypeQualifier = '2'
61
+ nm1.NameLastOrOrganizationName = 'PREMIER BILLING SERVICE'
62
+ nm1.IdentificationCode = 'TGJ23'
63
+ end
64
+
65
+ l1000a.PER do |per|
66
+ per.Name = 'JERRY'
67
+ per.CommunicationNumberQualifier_01 = 'TE'
68
+ per.CommunicationNumber_01 = '3055552222'
69
+ per.CommunicationNumberQualifier_02 = 'EX'
70
+ per.CommunicationNumber_02 = '231'
71
+ end
72
+ end
73
+
74
+ puts ts.to_s
75
+
76
+ # Below is the output of ts.to_s (split onto separate lines for readability)
77
+ #
78
+ # ST*837*0021*005010X222A1~
79
+ # BHT*0019*00*244579*20061015*1023*CH~
80
+ # NM1*41*2*PREMIER BILLING SERVICE*****46*TGJ23~
81
+ # PER*IC*JERRY*TE*3055552222*EX*231~
82
82
  ```
83
83
 
84
84
  Transaction Set/Loop and Segment DSL
@@ -86,76 +86,76 @@ Transaction Set/Loop and Segment DSL
86
86
  Transaction Sets/Loops and Segments are defined with a very straight forward DSL.
87
87
 
88
88
  ```ruby
89
- module Hippo::Segments
90
- class TestSimpleSegment < Hippo::Segments::Base
91
- segment_identifier 'TSS'
92
-
93
- field :name => 'Field1'
94
- field :name => 'Field2'
95
- field :name => 'Field3'
96
- field :name => 'Field4'
97
- field :name => 'CommonName'
98
- field :name => 'CommonName'
99
- field :name => 'DateField', :datatype => :date
100
- field :name => 'TimeField', :datatype => :time
101
- field :name => 'IntegerField', :datatype => :integer
102
- field :name => 'DecimalField', :datatype => :decimal
103
- end
104
-
105
- class TestCompoundSegment < Hippo::Segments::Base
106
- segment_identifier 'TCS'
107
-
108
- composite_field 'CompositeField' do
109
- field :name => 'Field1'
110
- field :name => 'Field2'
111
- field :name => 'Field3'
112
- field :name => 'CompositeCommonName'
113
- end
114
-
115
- composite_field 'CompositeField' do
116
- field :name => 'Field4'
117
- field :name => 'Field5'
118
- field :name => 'Field6'
119
- field :name => 'CompositeCommonName'
120
- end
121
-
122
- field :name => 'Field7'
123
- end
89
+ module Hippo::Segments
90
+ class TestSimpleSegment < Hippo::Segments::Base
91
+ segment_identifier 'TSS'
92
+
93
+ field :name => 'Field1'
94
+ field :name => 'Field2'
95
+ field :name => 'Field3'
96
+ field :name => 'Field4'
97
+ field :name => 'CommonName'
98
+ field :name => 'CommonName'
99
+ field :name => 'DateField', :datatype => :date
100
+ field :name => 'TimeField', :datatype => :time
101
+ field :name => 'IntegerField', :datatype => :integer
102
+ field :name => 'DecimalField', :datatype => :decimal
103
+ end
104
+
105
+ class TestCompoundSegment < Hippo::Segments::Base
106
+ segment_identifier 'TCS'
107
+
108
+ composite_field 'CompositeField' do
109
+ field :name => 'Field1'
110
+ field :name => 'Field2'
111
+ field :name => 'Field3'
112
+ field :name => 'CompositeCommonName'
113
+ end
114
+
115
+ composite_field 'CompositeField' do
116
+ field :name => 'Field4'
117
+ field :name => 'Field5'
118
+ field :name => 'Field6'
119
+ field :name => 'CompositeCommonName'
124
120
  end
125
121
 
126
- module Hippo::TransactionSets
127
- module Test
128
- class Base < Hippo::TransactionSets::Base
129
-
130
- segment Hippo::Segments::TestSimpleSegment,
131
- :name => 'Test Simple Segment #1',
132
- :minimum => 1,
133
- :maximum => 5,
134
- :position => 50,
135
- :defaults => {
136
- 'TSS01' => 'Blah'
137
- }
138
-
139
- segment Hippo::Segments::TestCompoundSegment,
140
- :name => 'Test Compound Segment #2',
141
- :minimum => 1,
142
- :maximum => 1,
143
- :position => 100,
144
- :defaults => {
145
- 'Field7' => 'Preset Field 7'
146
- }
147
-
148
- segment Hippo::Segments::TestSimpleSegment,
149
- :name => 'Test Simple Segment #3',
150
- :minimum => 1,
151
- :maximum => 1,
152
- :position => 50,
153
- :defaults => {
154
- 'TSS01' => 'Last Segment'
155
- }
156
- end
157
- end
122
+ field :name => 'Field7'
123
+ end
124
+ end
125
+
126
+ module Hippo::TransactionSets
127
+ module Test
128
+ class Base < Hippo::TransactionSets::Base
129
+
130
+ segment Hippo::Segments::TestSimpleSegment,
131
+ :name => 'Test Simple Segment #1',
132
+ :minimum => 1,
133
+ :maximum => 5,
134
+ :position => 50,
135
+ :defaults => {
136
+ 'TSS01' => 'Blah'
137
+ }
138
+
139
+ segment Hippo::Segments::TestCompoundSegment,
140
+ :name => 'Test Compound Segment #2',
141
+ :minimum => 1,
142
+ :maximum => 1,
143
+ :position => 100,
144
+ :defaults => {
145
+ 'Field7' => 'Preset Field 7'
146
+ }
147
+
148
+ segment Hippo::Segments::TestSimpleSegment,
149
+ :name => 'Test Simple Segment #3',
150
+ :minimum => 1,
151
+ :maximum => 1,
152
+ :position => 50,
153
+ :defaults => {
154
+ 'TSS01' => 'Last Segment'
155
+ }
158
156
  end
157
+ end
158
+ end
159
159
  ```
160
160
 
161
161
  Quick Guide to Populating a Transaction Set
@@ -166,14 +166,14 @@ the fields.
166
166
  To create a transaction set simple choose the set you want and call new on it's Base class.
167
167
 
168
168
  ```ruby
169
- ts = Hippo::TransactionSets::Test::Base.new
169
+ ts = Hippo::TransactionSets::Test::Base.new
170
170
  ```
171
171
 
172
172
  The segments can be accessed directly from the created transaction set using the segment
173
173
  identifier.
174
174
 
175
175
  ```ruby
176
- ts.TCS
176
+ ts.TCS
177
177
  ```
178
178
 
179
179
  Since the TSS segment can be repeated we must call #build to generate a new
@@ -181,13 +181,13 @@ instance for each repeat. (You will be returned the first instance each time if
181
181
  do not call #build.)
182
182
 
183
183
  ```ruby
184
- tss = ts.TSS.build
184
+ tss = ts.TSS.build
185
185
 
186
- # or
186
+ # or
187
187
 
188
- ts.TSS.build do |tss|
189
- # do something here...
190
- end
188
+ ts.TSS.build do |tss|
189
+ # do something here...
190
+ end
191
191
  ```
192
192
 
193
193
  The code above produces the following string output (notice how the values from
@@ -195,7 +195,7 @@ The code above produces the following string output (notice how the values from
195
195
  that the segments were declared):
196
196
 
197
197
  ```ruby
198
- # ts.to_s => 'TSS*Blah~TCS***Preset Field 7~'
198
+ # ts.to_s => 'TSS*Blah~TCS***Preset Field 7~'
199
199
  ```
200
200
 
201
201
  You can set the field values on a given segment a few different ways.
@@ -204,18 +204,18 @@ First you must access the segment that the field belongs to. You can
204
204
  either access the fields directly on the segment or use the block syntax.
205
205
 
206
206
  ```ruby
207
- # this is one way to populate the fields
208
- ts.TCS.Field1 = 'Foo'
209
- ts.TSS.Field2 = 'Bar'
210
-
211
- # this is another way
212
- ts.TCS do |tcs|
213
- tcs.Field1 = 'Foo'
214
- end
215
-
216
- ts.TSS do |tss|
217
- tss.Field2 = 'Bar'
218
- end
207
+ # this is one way to populate the fields
208
+ ts.TCS.Field1 = 'Foo'
209
+ ts.TSS.Field2 = 'Bar'
210
+
211
+ # this is another way
212
+ ts.TCS do |tcs|
213
+ tcs.Field1 = 'Foo'
214
+ end
215
+
216
+ ts.TSS do |tss|
217
+ tss.Field2 = 'Bar'
218
+ end
219
219
  ```
220
220
 
221
221
  Once you have access to the segment you can set the field values by either
@@ -224,12 +224,12 @@ field name is used more than once in a segment or if you are accessing a
224
224
  composite field you can optionally pass the index of the field to access.
225
225
 
226
226
  ```ruby
227
- ts.TCS do |tcs|
228
- tcs.Field1 = 'Foo' # use the field name
229
- tcs.TCS01_01 = 'Bar' # use shorthand notation:
230
- # TCS01 refers to the first field within the current segment
231
- # _01 refers to the first field within the composite field
232
- end
227
+ ts.TCS do |tcs|
228
+ tcs.Field1 = 'Foo' # use the field name
229
+ tcs.TCS01_01 = 'Bar' # use shorthand notation:
230
+ # TCS01 refers to the first field within the current segment
231
+ # _01 refers to the first field within the composite field
232
+ end
233
233
  ```
234
234
 
235
235
  If you read the transaction set declaration from above you will notice that the TSS segment
@@ -239,11 +239,11 @@ but if you need to access the second instance of TSS in the transaction set you
239
239
  TSS_02 instead.
240
240
 
241
241
  ```ruby
242
- ts.TCS.Field1 = 'Foo'
243
- ts.TSS.Field2 = 'Bar'
244
- ts.TSS_02.Field2 = 'Baz'
242
+ ts.TCS.Field1 = 'Foo'
243
+ ts.TSS.Field2 = 'Bar'
244
+ ts.TSS_02.Field2 = 'Baz'
245
245
 
246
- # ts.to_s => 'TSS*Blah*Bar~TCS*Foo**Preset Field 7~TSS*Last Segment*Baz~'
246
+ # ts.to_s => 'TSS*Blah*Bar~TCS*Foo**Preset Field 7~TSS*Last Segment*Baz~'
247
247
  ```
248
248
 
249
249
  Obviously, this could get somewhat tedious when operating on a TransactionSet with many segments
@@ -252,25 +252,25 @@ on the name provided in the TransactionSet definition. You can either pass the
252
252
  a Regexp to search with.
253
253
 
254
254
  ```ruby
255
- ts.find_by_name('Test Simple Segment #1') do |tss|
256
- tss.Field2 = 'Baz'
257
- end
255
+ ts.find_by_name('Test Simple Segment #1') do |tss|
256
+ tss.Field2 = 'Baz'
257
+ end
258
258
 
259
- # which is essentially equivilent (because the search occurs in order of declaration)
260
- ts.find_by_name(/Segment/) do |tss|
261
- tss.Field2 = 'Baz'
262
- end
259
+ # which is essentially equivilent (because the search occurs in order of declaration)
260
+ ts.find_by_name(/Segment/) do |tss|
261
+ tss.Field2 = 'Baz'
262
+ end
263
263
 
264
- # ts.to_s => 'TSS*Blah*Baz~'
264
+ # ts.to_s => 'TSS*Blah*Baz~'
265
265
  ```
266
266
 
267
267
  The same technique can be used to reference fields within a segment that have the same name.
268
268
 
269
269
  ```ruby
270
- ts.TSS.CommonName = 'Value1'
271
- ts.TSS.CommonName_02 = 'Value2'
270
+ ts.TSS.CommonName = 'Value1'
271
+ ts.TSS.CommonName_02 = 'Value2'
272
272
 
273
- # ts.to_s => 'TSS*Blah*Bar***Value1*Value2~TCS*Foo**Preset Field 7~TSS*Last Segment*Baz~'
273
+ # ts.to_s => 'TSS*Blah*Bar***Value1*Value2~TCS*Foo**Preset Field 7~TSS*Last Segment*Baz~'
274
274
  ```
275
275
 
276
276
  Type Conversion
@@ -286,40 +286,72 @@ with a valid value for that particular data type.
286
286
  Just a few examples using the type conversion:
287
287
 
288
288
  ```ruby
289
- seg = Hippo::Segments::TSS.new # Please review definition from above.
290
-
291
- # Date fields:
292
- seg.DateField = Date.new(2012, 01, 20)
293
- seg.DateField = "20120120"
294
- seg.DateField = Time.new(2012, 01, 20, 10, 15, 20)
295
-
296
- # all of these formats result in the same internal representation
297
- puts seg.DateField.inspect # => #<Date: 2012-01-20 ((2455947j,0s,0n),+0s,2299161j)>
298
-
299
- # To set the field back to a blank/empty value simply assign it to nil
300
- seg.DateField = nil
301
-
302
- # Time fields:
303
- seg.TimeField = "0120" # => 1:20 am (HHMM)
304
- seg.TimeField = "012023" # => 1:20:23 am (HHMMSS)
305
- seg.TimeField = "01202322" # => 1:20:23.22 am (HHMMSSDD)
306
- seg.TimeField = Time.now
307
-
308
- # Integer fields:
309
- seg.IntegerField = "10" # => 10
310
- seg.IntegerField = 10 # => 10
311
- seg.IntegerField = "10blah" # => 10
312
-
313
- # Decimal fields:
314
- seg.DecimalField = "123.45" # => #<BigDecimal:7fe83c315750,'0.12345E3',18(18)>
315
- seg.DecimalField = 123.45 # => #<BigDecimal:7fe83c315750,'0.12345E3',18(18)>
316
- seg.DecimalField = 123 # => #<BigDecimal:7fe83b9dd4f8,'0.123E3',9(18)>
317
- seg.DecimalField = 123.0 # => #<BigDecimal:7fe83b9dd4f8,'0.123E3',9(18)>
289
+ seg = Hippo::Segments::TSS.new # Please review definition from above.
290
+
291
+ # Date fields:
292
+ seg.DateField = Date.new(2012, 01, 20)
293
+ seg.DateField = "20120120"
294
+ seg.DateField = Time.new(2012, 01, 20, 10, 15, 20)
295
+
296
+ # all of these formats result in the same internal representation
297
+ puts seg.DateField.inspect # => #<Date: 2012-01-20 ((2455947j,0s,0n),+0s,2299161j)>
298
+
299
+ # To set the field back to a blank/empty value simply assign it to nil
300
+ seg.DateField = nil
301
+
302
+ # Time fields:
303
+ seg.TimeField = "0120" # => 1:20 am (HHMM)
304
+ seg.TimeField = "012023" # => 1:20:23 am (HHMMSS)
305
+ seg.TimeField = "01202322" # => 1:20:23.22 am (HHMMSSDD)
306
+ seg.TimeField = Time.now
307
+
308
+ # Integer fields:
309
+ seg.IntegerField = "10" # => 10
310
+ seg.IntegerField = 10 # => 10
311
+ seg.IntegerField = "10blah" # => 10
312
+
313
+ # Decimal fields:
314
+ seg.DecimalField = "123.45" # => #<BigDecimal:7fe83c315750,'0.12345E3',18(18)>
315
+ seg.DecimalField = 123.45 # => #<BigDecimal:7fe83c315750,'0.12345E3',18(18)>
316
+ seg.DecimalField = 123 # => #<BigDecimal:7fe83b9dd4f8,'0.123E3',9(18)>
317
+ seg.DecimalField = 123.0 # => #<BigDecimal:7fe83b9dd4f8,'0.123E3',9(18)>
318
318
  ```
319
319
 
320
320
  __Please Note__: Due to issues with floating point representation of currency values we have
321
321
  chosen to use BigDecimal internally to store all fields with a decimal datatype.
322
322
 
323
+ Hierarchy Traversal
324
+ -------------------
325
+
326
+ There are times with a given transaction set that you may start with a given segment but need
327
+ to traverse up to a higher level loop/transaction set container. The best example of this is
328
+ when dealing with 997 or 999 acknowledgments. If there are errors in your original transmission
329
+ they are reported on the 997 and 999 as the segment number in error. We need to then take that
330
+ errored segment and figure out more context.
331
+
332
+ The first thing we have to do is find the segment in error. The 999 contains this in the IK3
333
+ segment of the 2100 - AK2/IK3 loop. Then we need to access those segments in the original
334
+ transmitted file. Finally, we need to access an ancestor that gives enough context to resolve
335
+ the error.
336
+
337
+ Here is a quick example:
338
+
339
+ ```ruby
340
+ ts_999 = Hippo::Parser.parse_file('location/to/999/file.999')
341
+ ts_837 = Hippo::Parser.parse_file('location/to/837/file.837')
342
+
343
+ # first lets get the index of all of the errored segments
344
+ error_indexes = ts_999.L2000AK2.map{|l| l.L2100AK2.map{|m| m.IK3.IK303}}.flatten
345
+ # or
346
+ error_indexes = ts_999.segments.select{|s| s.class.to_s =~ /IK3/}.collect{|s| s.IK303}
347
+
348
+ # now lets find those segments in the file being confirmed
349
+ errored_segments = ts_837.segments.values_at(error_indexes)
350
+
351
+ # and finally lets find the claim that they belong to
352
+ errored_claims = errored_segments.collect{|s| s.ancestors.select{|a| a.class.to_s =~ /L2000B/}}.flatten
353
+ ```
354
+
323
355
  For more example please review the test suite.
324
356
 
325
357
  License
data/lib/hippo/field.rb CHANGED
@@ -21,7 +21,7 @@ module Hippo
21
21
  when :decimal then BigDecimal.new(value.to_s)
22
22
  when :date then parse_date(value)
23
23
  when :time then parse_time(value)
24
- else value.to_s.strip
24
+ else parse_string(value)
25
25
  end
26
26
  end
27
27
 
@@ -31,31 +31,51 @@ module Hippo
31
31
  case datatype
32
32
  when :binary then value
33
33
  when :integer then value.to_s.rjust(minimum, '0')
34
- when :decimal then
35
- value ||= BigDecimal.new('0')
36
-
37
- value.to_s('F').sub(/\.0\z/,'').rjust(minimum, '0')
38
- when :date
39
- value ||= Date.today
40
-
41
- if maximum == 6
42
- value.strftime('%y%m%d')
43
- else
44
- value.strftime('%Y%m%d')
45
- end
46
- when :time
47
- value ||= Time.now
48
-
49
- if maximum == 4 || value.sec == 0
50
- value.strftime('%H%M')
51
- else
52
- value.strftime('%H%M%S')
53
- end
54
- else value.to_s.ljust(minimum)
34
+ when :decimal then generate_decimal(value)
35
+ when :date then generate_date(value)
36
+ when :time then generate_time(value)
37
+ else generate_string(value)
38
+ end
39
+ end
40
+
41
+ def generate_string(value)
42
+ if required
43
+ value.to_s.ljust(minimum)
44
+ else
45
+ value.to_s
46
+ end
47
+ end
48
+
49
+ def parse_string(value)
50
+ if value.to_s.empty? && !required
51
+ nil
52
+ else
53
+ value.to_s.strip
54
+ end
55
+ end
56
+
57
+ def generate_decimal(value)
58
+ value ||= BigDecimal.new('0')
59
+
60
+ value.to_s('F').sub(/\.0\z/,'').rjust(minimum, '0')
61
+ end
62
+
63
+ def generate_time(value)
64
+ value ||= Time.now
65
+
66
+ if maximum == 4 || value.sec == 0
67
+ value.strftime('%H%M')
68
+ else
69
+ value.strftime('%H%M%S')
55
70
  end
56
71
  end
57
72
 
58
73
  def parse_time(value)
74
+ if value == ''
75
+ invalid! if required
76
+ return nil
77
+ end
78
+
59
79
  case value.class.to_s
60
80
  when 'Time' then value
61
81
  when 'String'
@@ -73,7 +93,22 @@ module Hippo
73
93
  invalid!
74
94
  end
75
95
 
96
+ def generate_date(value)
97
+ value ||= Date.today
98
+
99
+ if maximum == 6
100
+ value.strftime('%y%m%d')
101
+ else
102
+ value.strftime('%Y%m%d')
103
+ end
104
+ end
105
+
76
106
  def parse_date(value)
107
+ if value == ''
108
+ invalid! if required
109
+ return nil
110
+ end
111
+
77
112
  case value.class.to_s
78
113
  when "Date" then value
79
114
  when "Time" then value.to_date
@@ -81,7 +116,8 @@ module Hippo
81
116
  format = case value
82
117
  when /\A\d{6}\z/ then '%y%m%d'
83
118
  when /\A\d{8}\z/ then '%Y%m%d'
84
- else invalid!
119
+ else
120
+ invalid!
85
121
  end
86
122
 
87
123
  Date.parse(value, format)
@@ -17,7 +17,7 @@ module Hippo
17
17
  end
18
18
  end
19
19
 
20
- def segments
20
+ def parsed_segments
21
21
  @segments ||= @unparsed_data.split(@segment_separator).collect do |segment_string|
22
22
  segment = Hippo::Segments.const_get(segment_string.split(@field_separator).first).new(:parent => self)
23
23
 
@@ -25,14 +25,16 @@ module Hippo
25
25
  end
26
26
  end
27
27
 
28
- def read(input)
28
+ def read(input = nil)
29
+ input ||= ''
30
+
29
31
  @unparsed_data = input.gsub(/[\a\e\f\n\r\t\v]/,'')
30
32
  parse_separators(@unparsed_data)
31
33
  end
32
34
 
33
35
  def parse(input)
34
36
  read(input)
35
- populate(segments)
37
+ populate(parsed_segments)
36
38
  self
37
39
  end
38
40
  end
data/lib/hippo/parser.rb CHANGED
@@ -6,6 +6,16 @@ module Hippo
6
6
  include Hippo::Separator
7
7
  include TransactionSet
8
8
 
9
+ def self.parse_file(input)
10
+ parser = new
11
+ parser.parse_string(input)
12
+ end
13
+
14
+ def self.parse_string(input)
15
+ parser = new
16
+ parser.parse_string(input)
17
+ end
18
+
9
19
  def initialize
10
20
  super
11
21
  end
@@ -13,10 +23,10 @@ module Hippo
13
23
  def initialize_transaction_set(index)
14
24
  {
15
25
  :segments => [],
16
- :ISA => find_first_segment(segments[0,index + 1], 'ISA', true),
17
- :GS => find_first_segment(segments[0,index + 1], 'GS', true),
18
- :GE => find_first_segment(segments[index + 1, segments.length - index + 1], 'GE'),
19
- :IEA => find_first_segment(segments[index + 1, segments.length - index + 1], 'IEA')
26
+ :ISA => find_first_segment(parsed_segments[0,index + 1], 'ISA', true),
27
+ :GS => find_first_segment(parsed_segments[0,index + 1], 'GS', true),
28
+ :GE => find_first_segment(parsed_segments[index + 1, parsed_segments.length - index + 1], 'GE'),
29
+ :IEA => find_first_segment(parsed_segments[index + 1, parsed_segments.length - index + 1], 'IEA')
20
30
  }
21
31
  end
22
32
 
@@ -24,7 +34,7 @@ module Hippo
24
34
  raw_transaction_sets = []
25
35
  inside_transaction = false
26
36
 
27
- segments.each_with_index do |segment, index|
37
+ parsed_segments.each_with_index do |segment, index|
28
38
  if segment.identifier == 'ST'
29
39
  raw_transaction_sets << initialize_transaction_set(index)
30
40
 
@@ -49,6 +49,7 @@ module Hippo::Segments
49
49
 
50
50
  def segment_identifier(id)
51
51
  @identifier = id
52
+ @fields = []
52
53
  end
53
54
 
54
55
  def segment_fixed_width
@@ -58,11 +59,27 @@ module Hippo::Segments
58
59
 
59
60
  attr_accessor :values, :parent
60
61
 
61
- # make totaling segment counts easier...
62
+ def segments
63
+ [self]
64
+ end
65
+
62
66
  def segment_count
63
- 1
67
+ segments.count
64
68
  end
65
69
 
70
+ def ancestors
71
+ if parent
72
+ parent.ancestors.flatten
73
+ else
74
+ []
75
+ end
76
+ end
77
+
78
+ def to_ary
79
+ nil
80
+ end
81
+ alias :to_a :to_ary
82
+
66
83
  def initialize(options = {})
67
84
  @parent = options.delete(:parent)
68
85
 
@@ -151,6 +168,7 @@ module Hippo::Segments
151
168
  end
152
169
  else
153
170
  if field.composite
171
+ self.values[field.composite_sequence] ||= {}
154
172
  self.values[field.composite_sequence][field.sequence]
155
173
  else
156
174
  self.values[field.sequence]
@@ -47,6 +47,8 @@ module Hippo::TransactionSets
47
47
  break unless segment
48
48
  break unless component.valid?(segment)
49
49
 
50
+ segment.parent = self
51
+
50
52
  if component.repeating?
51
53
  values[component.sequence] ||= component.initialize_component(self)
52
54
  values[component.sequence] << segment
@@ -99,9 +101,26 @@ module Hippo::TransactionSets
99
101
  @sequences[segment_identifier] += 1
100
102
  end
101
103
 
104
+ def segments
105
+ values.values.collect(&:segments).flatten
106
+ end
107
+
102
108
  def segment_count
103
- values.values.map(&:segment_count).inject(&:+)
109
+ segments.count
110
+ end
111
+
112
+ def ancestors
113
+ if parent
114
+ [parent, parent.ancestors].flatten
115
+ else
116
+ []
117
+ end
118
+ end
119
+
120
+ def to_ary
121
+ nil
104
122
  end
123
+ alias :to_a :to_ary
105
124
 
106
125
  def to_s
107
126
  output = ''
@@ -20,10 +20,22 @@ module Hippo::TransactionSets
20
20
  self.map(&:to_s).join
21
21
  end
22
22
 
23
+ def segments
24
+ return [] unless self.length != 0
25
+
26
+ self.map(&:segments).flatten
27
+ end
28
+
23
29
  def segment_count
24
- return 0 unless self.length != 0
30
+ segments.length
31
+ end
25
32
 
26
- self.map(&:segment_count).inject(&:+)
33
+ def ancestors
34
+ if parent
35
+ parent.ancestors.flatten
36
+ else
37
+ []
38
+ end
27
39
  end
28
40
 
29
41
  def method_missing(method_name, *args, &block)
data/lib/hippo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Hippo
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -0,0 +1,22 @@
1
+ ISA*00* *00* *ZZ*592015694 *ZZ*B9820 *120119*1701*^*00501*000000001*0*P*:~
2
+ GS*FA*09102*B9820*20120119*170146*1*X*005010X231A1~
3
+ ST*999*0001*005010X231A1~
4
+ AK1*HC*155*005010X222A1~
5
+ AK2*837*0002*005010X222A1~
6
+ IK5*A~
7
+ AK9*A*1*1*1~
8
+ SE*6*0001~
9
+ GE*1*1~
10
+ IEA*1*000000001~
11
+ ISA*00* *00* *ZZ*592015694 *ZZ*B9820 *120119*1703*^*00501*000000001*0*P*:~
12
+ GS*FA*09102*B9820*20120119*170300*1*X*005010X231A1~
13
+ ST*999*0001*005010X231A1~
14
+ AK1*HC*156*005010X222A1~
15
+ AK2*837*0002*005010X222A1~
16
+ IK3*N4*5074*2010*8~
17
+ IK4*3*26*2~
18
+ IK5*R*5~
19
+ AK9*R*1*1*0~
20
+ SE*8*0001~
21
+ GE*1*1~
22
+ IEA*1*000000001~
@@ -175,4 +175,10 @@ class TestSegmentsBase < MiniTest::Unit::TestCase
175
175
 
176
176
  assert_equal "ISA*00* *00* *ZZ*593208085 *ZZ*OVERRIDE *#{Date.today.strftime('%y%m%d')}*#{Time.now.strftime('%H%M')}*^*00501*000012345*1*T*:~", isa.to_s
177
177
  end
178
+
179
+ def test_access_empty_composite_returns_nil
180
+ seg = Hippo::Segments::TCS.new
181
+
182
+ assert_equal nil, seg.TCS01_01
183
+ end
178
184
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hippo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-01-21 00:00:00.000000000 Z
13
+ date: 2012-01-25 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: minitest
17
- requirement: &70214345868980 !ruby/object:Gem::Requirement
17
+ requirement: &2182290180 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '0'
23
23
  type: :development
24
24
  prerelease: false
25
- version_requirements: *70214345868980
25
+ version_requirements: *2182290180
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rake
28
- requirement: &70214345868480 !ruby/object:Gem::Requirement
28
+ requirement: &2182289680 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ~>
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: 0.9.2
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *70214345868480
36
+ version_requirements: *2182289680
37
37
  description: HIPAA Transaction Set Generator/Parser
38
38
  email:
39
39
  - robertj@promedicalinc.com
@@ -262,6 +262,7 @@ files:
262
262
  - samples/005010X222A1_commercial_health_insurance.edi
263
263
  - samples/005010X231A1_01.edi
264
264
  - samples/005010X231A1_02.edi
265
+ - samples/200823.EDI
265
266
  - test/test_helper.rb
266
267
  - test/test_hipaa_835.rb
267
268
  - test/test_hipaa_837.rb