Skip to content

Commit 2d8a628

Browse files
authored
Merge pull request #9 from CloudCray/location_limits
Eligibility or Benefit Additional Info (Place of Service)
2 parents 59b01d9 + 73bdc17 commit 2d8a628

File tree

14 files changed

+5150
-1513
lines changed

14 files changed

+5150
-1513
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
TigerShark is an X12 EDI message parser that can be tailored to
22
a specific partner in the health care payment ecosystem.
33

4+
Version 0.3.2
5+
-------------
6+
7+
* Support for EligibilityOrBenefitAdditionalInformation - `additional_information` property added to EligibilityOrBenefit to handle `III*ZZ*` segments
8+
* Adds `place_of_service` enum for corresponding `additional_information` property
9+
* Cleans up unittest warnings (`assertEquals` replaced with `assertEqual`)
10+
* More descriptive and reliable `.to_dict` and `.to_json` methods.
11+
412
Version 0.3.1
513
-------------
614

@@ -248,5 +256,12 @@ the tests that currently exist, run the following in the current directory.
248256
python -m unittest discover
249257
```
250258

259+
To limit tests to the `tests` directory (ignoring all tests in the `web` directory), run the following:
260+
261+
262+
```sh
263+
python -m unittest discover -s 'tests'
264+
```
265+
251266
Note that if you first `cd tests` and then run the unit tests, they will fail
252267
because the tests expect certain files to be in certain paths.

tests/271-example-3.txt

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
ISA|00| |00| |ZZ|ZIRMED |ZZ|10864 |160405|1354|}|00501|000184077|1|P|^~
2+
GS|HB|ZIRMED|10864|20160301|1354|184065|X|005010X279A1~
3+
ST|271|0001|005010X279A1~
4+
BHT|0022|11|94309|20160301|134403~
5+
HL|1||20|1~
6+
NM1|PR|2|MEDICA COM|||||PI|123456789~
7+
HL|2|1|21|1~
8+
NM1|1P|1|James|Barraret|J.|||XX|1234567894~
9+
HL|3|2|22|0~
10+
TRN|2|434343434|9ZIRMEDCOM|ELR ID~
11+
TRN|1|422422424|9ZIRMEDCOM|ELI ID~
12+
NM1|IL|1|SMITH|JOHN||||MI|1234567899~
13+
REF|18|0404044~
14+
REF|6P|030030001000120|NEVADA EXCHANGE~
15+
N3|4040 VILLAGE AB~
16+
N4|KANSAS CITY|MO|64108~
17+
DMG|D8|19430813|M~
18+
INS|Y|18|001|25~
19+
DTP|346|D8|20160201~
20+
DTP|472|D8|20160601~
21+
DTP|356|D8|20141208~
22+
EB|L|EMP|30|EP~
23+
MSG|PCP SELECTION NOT REQUIRED~
24+
EB|W~
25+
LS|2120~
26+
NM1|PR|2|Mala Compania~
27+
N3|PO Box 983322~
28+
N4|El Fixato|TX|68887~
29+
LE|2120~
30+
EB|1|EMP|30|EP|Open Access Elect Choice~
31+
EB|G|IND|30||||2100|||||Y~
32+
MSG|MAXIMUM SAVINGS,QUALIFIED HEALTH PLAN DESIGNATED PROVIDERS~
33+
MSG|INT MED AND RX~
34+
EB|G|IND|30|||29|2100|||||Y~
35+
EB|F|EMP|30|||||||||Y~
36+
MSG|PLAN REQUIRES PRECERT~
37+
EB|F|EMP|30~
38+
MSG|COMMERCIAL~
39+
EB|F|EMP|30|||32~
40+
MSG|UNLIMITED LIFETIME BENEFITS~
41+
EB|1|EMP|1}33}47}48}50}86}98}UC}AL}MH}88||Open Access Elect Choice~
42+
EB|A|EMP|33|||||0||||Y~
43+
MSG|MAXIMUM SAVINGS,QUALIFIED HEALTH PLAN DESIGNATED PROVIDERS~
44+
MSG|CHIROPRACTOR VISIT OR EVALUATION IN OFFICE~
45+
MSG|LAB PERFORMED BY CHIROPRACTOR IN OFFICE~
46+
MSG|XRAY BY CHIROPRACTOR IN OFFICE~
47+
EB|A|EMP|86|||||0||||Y~
48+
MSG|MAXIMUM SAVINGS,QUALIFIED HEALTH PLAN DESIGNATED PROVIDERS~
49+
MSG|EMERGENCY ROOM PHYSICIAN~
50+
MSG|EMERGENCY USE OF EMERGENCY ROOM~
51+
MSG|URGENT CARE~
52+
EB|A|EMP|98|||||.1||||Y~
53+
MSG|MAXIMUM SAVINGS,QUALIFIED HEALTH PLAN DESIGNATED PROVIDERS~
54+
MSG|GYN VISIT,COINS APPLIES TO OUT OF POCKET~
55+
MSG|SPECIALIST VISIT OR EVALUATION,COINS APPLIES TO OUT OF POCKET~
56+
MSG|PRIMARY CARE VISIT OR EVALUATION,COINS APPLIES TO OUT OF POCKET~
57+
III|ZZ|11~
58+
EB|B|EMP|33||||20|||||Y~
59+
MSG|MAXIMUM SAVINGS,QUALIFIED HEALTH PLAN DESIGNATED PROVIDERS~
60+
MSG|CHIROPRACTOR VISIT OR EVALUATION IN OFFICE,COPAY INCLUDED IN OOP~
61+
MSG|LAB PERFORMED BY CHIROPRACTOR IN OFFICE,COPAY INCLUDED IN OOP~
62+
MSG|XRAY BY CHIROPRACTOR IN OFFICE,COPAY INCLUDED IN OOP~
63+
EB|F|IND|33|||||||||Y~
64+
HSD|VS|35|||21|1~
65+
MSG|SHT TRM RHB OP,MANIPULATION BY CHIROPRACTOR,OCCUPATIONAL THERAPY BY CHIROPRACTOR,PHYSICAL THERAPY BY CHIROPRACTOR~
66+
EB|F|IND|33|||||||||Y~
67+
HSD|VS|35|||29~
68+
MSG|SHT TRM RHB OP~
69+
EB|I|EMP|86|||||||||Y~
70+
MSG|NON EMERGENCY USE OF EMERGENCY ROOM/EXCLUSION~
71+
EB|I|EMP|86}UC|||||||||Y~
72+
MSG|NON URGENT SERVICES AT AN URGENT CARE FACILITY/EXCLUSION~
73+
EB|F|EMP|1}33}48}50}86}98}UC~
74+
MSG|PLAN INCLUDES NAP~
75+
EB|I|EMP|1}33}48}50}86}98}UC|||||||||N~
76+
SE|74|0001~
77+
GE|1|184065~
78+
IEA|1|000184077~

tests/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@
3939
from tigershark import X12_4010_X096A1
4040
from tigershark import X12_4010_X098A1
4141
from tigershark import X12_5010_X221A1
42+
from tigershark import X12_5010_X279A1
4243

4344
# (Transaction Set, X12VersionTuple) -> test file name
4445
TEST_FILE_MAP = {
4546
('271', X12_4010_X092A1): '271-dependent-benefits.txt',
4647
('271', X12_4010_X092A1): '271-example-2.txt',
48+
('271', X12_5010_X279A1): '271-example-3.txt',
4749
('271', X12_4010_X092A1): '271-example-dependent-rejection.txt',
4850
('271', X12_4010_X092A1): '271-example.txt',
4951
('271', X12_4010_X092A1): '271-related-entity.txt',
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import unittest
2+
import logging
3+
import sys
4+
5+
6+
from tigershark.facade import f271
7+
from tigershark.parsers import M271_5010_X279_A1
8+
9+
10+
class TestBenefitAdditionalInfo(unittest.TestCase):
11+
def test_benefit_additional_info(self):
12+
m = M271_5010_X279_A1.parsed_271
13+
with open('tests/271-example-3.txt') as f:
14+
parsed = m.unmarshall(f.read().strip())
15+
f = f271.F271_5010(parsed)
16+
17+
subscriber = f.facades[0].subscribers[0]
18+
benefit_row = subscriber.eligibility_or_benefit_information[11]
19+
additional_information = benefit_row.additional_information[0]
20+
self.assertEqual(additional_information, ('11', 'Office'))
21+
self.assertEqual(len(benefit_row.messages), 4)
22+
23+
24+
if __name__ == "__main__":
25+
logging.basicConfig(
26+
stream=sys.stderr,
27+
level=logging.INFO,
28+
)
29+
unittest.main()

tests/test_json_parsing.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,38 @@ def test_json_271(self):
2828
except TypeError:
2929
self.fail(".to_json() raised TypeError")
3030

31+
def test_json_271_segments(self):
32+
m = M271_4010_X092_A1.parsed_271
33+
with open('tests/271-example-2.txt') as f:
34+
parsed = m.unmarshall(f.read().strip())
35+
f = f271.F271_4010(parsed)
36+
js_data = f.to_dict()
37+
self.assertEqual(js_data["transaction_set_identifier_code"], "271")
38+
39+
source = js_data["facades"][0]["source"]
40+
self.assertEqual(source["loopName"], "2000A")
41+
42+
subscriber = source["receivers"][0]["subscribers"][0]
43+
self.assertEqual(subscriber["personal_information"]["name"]["first_name"], "JANET")
44+
self.assertEqual(
45+
subscriber["personal_information"]["demographic_information"]["gender"],
46+
{"code": "F", "label": "Female"}
47+
)
48+
49+
elig_info = subscriber["eligibility_or_benefit_information"]
50+
self.assertEqual(len(elig_info), 109)
51+
52+
benefit = elig_info[14]
53+
self.assertEqual(benefit["coverage_information"]["benefit_amount"], 1500)
54+
self.assertEqual(
55+
benefit["coverage_information"]["coverage_level"],
56+
{"code": "IND", "label": "Individual"}
57+
)
58+
self.assertEqual(
59+
benefit["coverage_information"]["information_type"],
60+
{"code": "G", "label": "Out of Pocket (Stop Loss)"}
61+
)
62+
3163
def test_json_835_5010(self):
3264
m = M835_5010_X221_A1.parsed_835
3365
with open('tests/5010-835-example-1.txt') as f:
@@ -48,6 +80,7 @@ def test_json_271_5010(self):
4880
except TypeError:
4981
self.fail(".to_json() raised TypeError")
5082

83+
5184
if __name__ == "__main__":
5285
logging.basicConfig(
5386
stream=sys.stderr,

tests/test_navigation.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,30 @@ def setUp(self):
3030

3131
def testChild(self):
3232
loops = self.message.child("LOOP")
33-
self.assertEquals(1, len(loops))
33+
self.assertEqual(1, len(loops))
3434
segs = self.message.child("SEGMENT")
35-
self.assertEquals(0, len(segs))
35+
self.assertEqual(0, len(segs))
3636
isaList = self.message.child("LOOP", name="ISA")
37-
self.assertEquals(1, len(isaList))
37+
self.assertEqual(1, len(isaList))
3838
ieaList = isaList[0].child("SEGMENT", name="IEA")
39-
self.assertEquals(1, len(ieaList))
39+
self.assertEqual(1, len(ieaList))
4040

4141
def testDescendant(self):
4242
geList = self.message.descendant("SEGMENT", name="GE")
43-
self.assertEquals(1, len(geList))
43+
self.assertEqual(1, len(geList))
4444

4545
def testParent(self):
4646
geList = self.message.descendant("SEGMENT", name="GE")
47-
self.assertEquals(1, len(geList))
47+
self.assertEqual(1, len(geList))
4848
ge = geList[0]
49-
self.assertEquals(0, len(ge.parent("SEGMENT")))
49+
self.assertEqual(0, len(ge.parent("SEGMENT")))
5050
parentList = ge.parent("LOOP")
51-
self.assertEquals(1, len(parentList))
52-
self.assertEquals("GS", parentList[0].name)
51+
self.assertEqual(1, len(parentList))
52+
self.assertEqual("GS", parentList[0].name)
5353

5454
def testAncestor(self):
5555
geList = self.message.descendant("SEGMENT", name="GE")
56-
self.assertEquals(1, len(geList))
56+
self.assertEqual(1, len(geList))
5757
ge = geList[0]
5858
isaList = ge.ancestor("LOOP", name="ISA")
59-
self.assertEquals(1, len(isaList))
59+
self.assertEqual(1, len(isaList))

tests/test_parse.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ def testBadLoop(self):
5454
Message("bad", Properties(desc="bad")))
5555

5656
def testMessage(self):
57-
self.assertEquals(parse_278, loop2000F.message)
58-
self.assertEquals("278", loop2000A.message.name)
57+
self.assertEqual(parse_278, loop2000F.message)
58+
self.assertEqual("278", loop2000A.message.name)
5959

6060

6161
class TestSegmentMatch(unittest.TestCase):
@@ -87,14 +87,14 @@ def setUp(self):
8787

8888
def testTokenize(self):
8989
_, _, _, tokens = parse_278.tokenize(self.msg1)
90-
self.assertEquals(28, len(tokens))
91-
self.assertEquals(17, len(tokens[0])) # The ISA Segment
92-
self.assertEquals("ISA", tokens[0][0])
93-
self.assertEquals("GS", tokens[1][0])
94-
self.assertEquals("ST", tokens[2][0])
95-
self.assertEquals("SE", tokens[-3][0])
96-
self.assertEquals("GE", tokens[-2][0])
97-
self.assertEquals("IEA", tokens[-1][0])
90+
self.assertEqual(28, len(tokens))
91+
self.assertEqual(17, len(tokens[0])) # The ISA Segment
92+
self.assertEqual("ISA", tokens[0][0])
93+
self.assertEqual("GS", tokens[1][0])
94+
self.assertEqual("ST", tokens[2][0])
95+
self.assertEqual("SE", tokens[-3][0])
96+
self.assertEqual("GE", tokens[-2][0])
97+
self.assertEqual("IEA", tokens[-1][0])
9898

9999
def testISAMatch(self):
100100
_, _, _, tokens = parse_278.tokenize(self.msg1)
@@ -104,7 +104,7 @@ def testISAMatch(self):
104104

105105
def testParse(self):
106106
message = parse_278.unmarshall(self.msg1)
107-
self.assertEquals(self.msg1, message.marshall())
107+
self.assertEqual(self.msg1, message.marshall())
108108

109109

110110
class TestVisitor1(unittest.TestCase):
@@ -143,7 +143,7 @@ def testPythonVisitorWorks(self):
143143
self.assertEqual(type(parse_278), Message)
144144
msg = parse_278.unmarshall(self.msg1)
145145
# XXX - check the resulting structure
146-
self.assertEquals(self.msg1, msg.marshall())
146+
self.assertEqual(self.msg1, msg.marshall())
147147

148148

149149
class TestSQLVisitor(unittest.TestCase):

tests/test_wsClaims.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,18 @@ def test_01_Load( self ):
6060
result= response.read()
6161
#print( result )
6262
self.client.close()
63-
self.assertEquals( "CREATED", response.reason )
63+
self.assertEqual( "CREATED", response.reason )
6464
object= json.loads(result)
65-
self.assertEquals( claimId, object['claim_id'] )
65+
self.assertEqual( claimId, object['claim_id'] )
6666
def test_02_Fetch( self ):
6767
claimId= self.claimDict["CLAIM-ID"]
6868
self.client.request( 'GET', "/claim/{0}/".format(claimId), headers=self.headers )
6969
response= self.client.getresponse()
7070
result= response.read()
7171
self.client.close()
7272
object= json.loads(result)
73-
self.assertEquals( "OK", response.reason )
74-
self.assertEquals( self.claimText, object['claim'] )
73+
self.assertEqual( "OK", response.reason )
74+
self.assertEqual( self.claimText, object['claim'] )
7575
def test_03_Fetch( self ):
7676
claimId= '837_example'
7777
self.client.request( 'GET', "/claim_837/{0}/".format(claimId), headers=self.headers )
@@ -80,7 +80,7 @@ def test_03_Fetch( self ):
8080
self.client.close()
8181
object= json.loads(result)
8282
#print( result )
83-
self.assertEquals( "OK", response.reason )
83+
self.assertEqual( "OK", response.reason )
8484
print( object['claim'] )
8585

8686
def setUpModule():

tigershark/X12/parse.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ class Parser( object ):
402402
Message parsing breaks the source message down into
403403
a flat list of segment tokens. Then the structure defined
404404
by Segment, Element and Loop is imposed on those segment tokens.
405-
to build the X12Message from X12Loop and X12Segment.
405+
to build the X12Message from tigershark.X12Loop and X12Segment.
406406
407407
There are several properties:
408408
@@ -513,10 +513,14 @@ def getParts( self, segments, theLoop ):
513513
"""
514514
for part in self.structure:
515515
count= 0
516-
for subloop in part.parse( segments ):
517-
subloop.occurrence= count
518-
theLoop.addChild( subloop )
519-
count += 1
516+
segment_parts = part.parse( segments )
517+
try:
518+
for subloop in segment_parts:
519+
subloop.occurrence= count
520+
theLoop.addChild( subloop )
521+
count += 1
522+
except StopIteration:
523+
return
520524
def visit( self, visitor, indent=0 ):
521525
"""Iterate through the components."""
522526
try:
@@ -694,7 +698,7 @@ def parse( self, segments ):
694698
theSegment= self.theFactory.makeSegment( segments.pop(0), compPunct, self )
695699
yield theSegment
696700
elif not required:
697-
raise StopIteration
701+
return
698702
else:
699703
error= ParseError(
700704
"Could not unmarshall {seg_name} Segment, "\
@@ -756,7 +760,10 @@ def parse( self, segments ):
756760
structure=[s.name for s in self.structure]))
757761
theLoop = self.theFactory.makeLoop(self.name)
758762
self.getParts(segments, theLoop)
759-
yield theLoop
763+
try:
764+
yield theLoop
765+
except StopIteration:
766+
return
760767
elif self.structure[i].situational and \
761768
segments[0][0] in [s.name for s in self.structure]:
762769
# Absence of a situational segment shouldn't exit
@@ -765,7 +772,7 @@ def parse( self, segments ):
765772
i += 1
766773
else:
767774
# Nothing left to check, so stop the iteration
768-
raise StopIteration
775+
return
769776

770777
def match( self, candidate ):
771778
return self.structure[0].match( candidate )

tigershark/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
TigerShark - An X12 EDI message parser.
33
"""
44
__all__ = ['X12VersionTuple']
5-
__version__ = "0.3.1"
5+
__version__ = "0.3.2"
66
__authors__ = [
77
"Steven Buss <[email protected]>",
88
"Steven Lott <[email protected]>",

0 commit comments

Comments
 (0)