Skip to content

Commit

Permalink
Merge pull request #21 from affect-therapeutics/rpo/add-ts3-support
Browse files Browse the repository at this point in the history
Adds PER support to the claim loop
  • Loading branch information
rposborne authored Oct 18, 2024
2 parents 28bf18f + 58690f9 commit 326f65e
Show file tree
Hide file tree
Showing 11 changed files with 250 additions and 9 deletions.
14 changes: 14 additions & 0 deletions edi_835_parser/elements/contact_communication_number_qualifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from edi_835_parser.elements import Element

contact_communication_number_qualifer = {
'FX': 'fax',
'TE': 'phone',
'EM': 'email',
'UR': 'url',
'EX': 'ext',
}


class ContactCommunicationNumberQualifier(Element):
def parser(self, value: str) -> str:
return contact_communication_number_qualifer.get(value, value)
16 changes: 15 additions & 1 deletion edi_835_parser/loops/claim.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from edi_835_parser.segments.date import Date as DateSegment
from edi_835_parser.segments.amount import Amount as AmountSegment
from edi_835_parser.segments.utilities import find_identifier
from edi_835_parser.segments.payer_contact import PayerContact as PayerContactSegment
from edi_835_parser.segments.inpatient_adjudication import (
InpatientAdjudication as InpatientAdjudicationSegment,
)
Expand Down Expand Up @@ -51,6 +52,7 @@ def __init__(
self.inpatient = inpatient
self.outpatient = outpatient
self.adjustments = adjustments if adjustments else []
self.contacts = []

def __repr__(self):
return '\n'.join(str(item) for item in self.__dict__.items())
Expand Down Expand Up @@ -135,6 +137,13 @@ def coverage_amount(self):
return amount.amount
return None

@property
def claim_payer_contact(self):
for contact in self.contacts:
if contact.code == 'payers_claim_office':
return contact
return None

@classmethod
def build(
cls, segment: str, segments: Iterator[str]
Expand Down Expand Up @@ -174,6 +183,11 @@ def build(
claim.amounts.append(amount)
segment = None

elif identifier == PayerContactSegment.identification:
contact = PayerContactSegment(segment)
claim.contacts.append(contact)
segment = None

elif identifier == InpatientAdjudicationSegment.identification:
inpatient = InpatientAdjudicationSegment(segment)
claim.inpatient = inpatient
Expand All @@ -193,8 +207,8 @@ def build(
return claim, segments, segment

else:
message = f'Identifier: {identifier} not handled in claim loop. {segment}'
segment = None
message = f'Identifier: {identifier} not handled in claim loop.'
Logger.logr.warning(message)

except StopIteration:
Expand Down
4 changes: 3 additions & 1 deletion edi_835_parser/loops/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ def build(
return organization, segments, segment

else:
message = (
f'Identifier: {identifier} not handled in organization loop. {segment}'
)
segment = None
message = f'Identifier: {identifier} not handled in organization loop.'
Logger.logr.warning(message)

except StopIteration:
Expand Down
2 changes: 1 addition & 1 deletion edi_835_parser/loops/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def build(
return service, segment, segments

else:
message = f'Identifier: {identifier} not handled in service loop.'
message = f'Identifier: {identifier} not handled in service loop. {segment}'
Logger.logr.warning(message)

except StopIteration:
Expand Down
61 changes: 61 additions & 0 deletions edi_835_parser/segments/payer_contact.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from edi_835_parser.elements.contact_communication_number_qualifier import (
ContactCommunicationNumberQualifier,
)
from edi_835_parser.elements.identifier import Identifier
from edi_835_parser.elements.contact_function_code import ContactFunctionCode
from edi_835_parser.segments.utilities import split_segment, get_element
Expand All @@ -8,6 +11,9 @@ class PayerContact:

identifier = Identifier()
code = ContactFunctionCode()
communication_no_or_url_qualifier = ContactCommunicationNumberQualifier()
communication_no_or_url_qualifier_2 = ContactCommunicationNumberQualifier()
communication_no_or_url_qualifier_3 = ContactCommunicationNumberQualifier()

def __init__(self, segment: str):
self.index = segment.split(':', 1)[0]
Expand All @@ -19,8 +25,63 @@ def __init__(self, segment: str):
self.identifier = segment[0]
self.code = segment[1]
self.name = get_element(segment, 2)

self.communication_no_or_url_qualifier = get_element(segment, 3)
self.communication_no_or_url = get_element(segment, 4)
self.communication_no_or_url_qualifier_2 = get_element(segment, 5)
self.communication_no_or_url_2 = get_element(segment, 6)
self.communication_no_or_url_qualifier_3 = get_element(segment, 7)
self.communication_no_or_url_3 = get_element(segment, 8)

self._comm_groups = [
(self.communication_no_or_url_qualifier, self.communication_no_or_url),
(self.communication_no_or_url_qualifier_2, self.communication_no_or_url_2),
(self.communication_no_or_url_qualifier_3, self.communication_no_or_url_3),
]

@property
def phone_number(self):
num = ''
ext = ''
for i, (qualifier, number) in enumerate(self._comm_groups):
if qualifier == 'phone':
num = number
if i + 1 < len(self._comm_groups) and self._comm_groups[i + 1][0] == 'ext':
ext = self._comm_groups[i + 1][1]
break

if ext:
return f'{num}x{ext}'
return num

@property
def fax_number(self):
qualifier = 'fax'
num = ''
ext = ''
for i, (q, number) in enumerate(self._comm_groups):
if q == qualifier:
num = number
# Check if the next qualifer is 'ext'
if i + 1 < len(self._comm_groups) and self._comm_groups[i + 1][0] == 'ext':
ext = self._comm_groups[i + 1][1]
break

if ext:
return f'{num}x{ext}'
return num

@property
def email(self):
for qualifier, email in self._comm_groups:
if qualifier == 'email':
return email

@property
def url(self):
for qualifier, url in self._comm_groups:
if qualifier == 'url':
return url

def __repr__(self):
return '\n'.join(str(item) for item in self.__dict__.items())
Expand Down
12 changes: 6 additions & 6 deletions output/remits_poc/remit_payers/sample_835.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
remit_key|file_name|edi_transaction_id_st02|payer_id|payer_name|payer_address_line1|payer_address_line2|payer_city|payer_state|payer_zip|payer_country|payer_contact_business|payer_contact_business_qualifier|payer_contact_business_name|payer_contact_technical|payer_contact_technical_qualifier|payer_contact_technical_name|payer_contact_web|payer_contact_web_qualifier|payer_contact_web_name|payer_id_add|created_at
22|sample_835.txt|1002||WSFG - BENEFITStest|400 BROADWAY||CINCINNATI|OH|45201||5136291100|TE|WESTERN SOUTHERN BENEFITS|5136291100|TE|WESTERN SOUTHERN BENEFITS||||31048|
37|sample_835.txt|1002||WSFG - BENEFITStest|400 BROADWAY||CINCINNATI|OH|45201||5136291100|TE|WESTERN SOUTHERN BENEFITS|5136291100|TE|WESTERN SOUTHERN BENEFITS||||31048|
78|sample_835.txt|1001||WSFG - BENEFITS|400 BROADWAY||CINCINNATI|OH|45201||5136291100|TE|WESTERN SOUTHERN BENEFITS|5136291100|TE|WESTERN SOUTHERN BENEFITS|FOR DISCOUNT QUESTIONS CALL ZELIS AT 1-888-266-3053. ALL OTHER CLAIM INQUIRIES CALL WESTERN SOUTHERN AT 513-629-1400. FOR DETAILED PAYMENT INFO, SEE THE EOP ON CHANGE HEALTHCARE WEBSITE.|UR||31048|
102|sample_835.txt|1001||WSFG - BENEFITS|400 BROADWAY||CINCINNATI|OH|45201||5136291100|TE|WESTERN SOUTHERN BENEFITS|5136291100|TE|WESTERN SOUTHERN BENEFITS|FOR DISCOUNT QUESTIONS CALL ZELIS AT 1-888-266-3053. ALL OTHER CLAIM INQUIRIES CALL WESTERN SOUTHERN AT 513-629-1400. FOR DETAILED PAYMENT INFO, SEE THE EOP ON CHANGE HEALTHCARE WEBSITE.|UR||31048|
129|sample_835.txt|1001||WSFG - BENEFITS|400 BROADWAY||CINCINNATI|OH|45201||5136291100|TE|WESTERN SOUTHERN BENEFITS|5136291100|TE|WESTERN SOUTHERN BENEFITS|FOR DISCOUNT QUESTIONS CALL ZELIS AT 1-888-266-3053. ALL OTHER CLAIM INQUIRIES CALL WESTERN SOUTHERN AT 513-629-1400. FOR DETAILED PAYMENT INFO, SEE THE EOP ON CHANGE HEALTHCARE WEBSITE.|UR||31048|
146|sample_835.txt|1001||WSFG - BENEFITS|400 BROADWAY||CINCINNATI|OH|45201||5136291100|TE|WESTERN SOUTHERN BENEFITS|5136291100|TE|WESTERN SOUTHERN BENEFITS|FOR DISCOUNT QUESTIONS CALL ZELIS AT 1-888-266-3053. ALL OTHER CLAIM INQUIRIES CALL WESTERN SOUTHERN AT 513-629-1400. FOR DETAILED PAYMENT INFO, SEE THE EOP ON CHANGE HEALTHCARE WEBSITE.|UR||31048|
22|sample_835.txt|1002||WSFG - BENEFITStest|400 BROADWAY||CINCINNATI|OH|45201||5136291100|phone|WESTERN SOUTHERN BENEFITS|5136291100|phone|WESTERN SOUTHERN BENEFITS||||31048|
37|sample_835.txt|1002||WSFG - BENEFITStest|400 BROADWAY||CINCINNATI|OH|45201||5136291100|phone|WESTERN SOUTHERN BENEFITS|5136291100|phone|WESTERN SOUTHERN BENEFITS||||31048|
78|sample_835.txt|1001||WSFG - BENEFITS|400 BROADWAY||CINCINNATI|OH|45201||5136291100|phone|WESTERN SOUTHERN BENEFITS|5136291100|phone|WESTERN SOUTHERN BENEFITS|FOR DISCOUNT QUESTIONS CALL ZELIS AT 1-888-266-3053. ALL OTHER CLAIM INQUIRIES CALL WESTERN SOUTHERN AT 513-629-1400. FOR DETAILED PAYMENT INFO, SEE THE EOP ON CHANGE HEALTHCARE WEBSITE.|url||31048|
102|sample_835.txt|1001||WSFG - BENEFITS|400 BROADWAY||CINCINNATI|OH|45201||5136291100|phone|WESTERN SOUTHERN BENEFITS|5136291100|phone|WESTERN SOUTHERN BENEFITS|FOR DISCOUNT QUESTIONS CALL ZELIS AT 1-888-266-3053. ALL OTHER CLAIM INQUIRIES CALL WESTERN SOUTHERN AT 513-629-1400. FOR DETAILED PAYMENT INFO, SEE THE EOP ON CHANGE HEALTHCARE WEBSITE.|url||31048|
129|sample_835.txt|1001||WSFG - BENEFITS|400 BROADWAY||CINCINNATI|OH|45201||5136291100|phone|WESTERN SOUTHERN BENEFITS|5136291100|phone|WESTERN SOUTHERN BENEFITS|FOR DISCOUNT QUESTIONS CALL ZELIS AT 1-888-266-3053. ALL OTHER CLAIM INQUIRIES CALL WESTERN SOUTHERN AT 513-629-1400. FOR DETAILED PAYMENT INFO, SEE THE EOP ON CHANGE HEALTHCARE WEBSITE.|url||31048|
146|sample_835.txt|1001||WSFG - BENEFITS|400 BROADWAY||CINCINNATI|OH|45201||5136291100|phone|WESTERN SOUTHERN BENEFITS|5136291100|phone|WESTERN SOUTHERN BENEFITS|FOR DISCOUNT QUESTIONS CALL ZELIS AT 1-888-266-3053. ALL OTHER CLAIM INQUIRIES CALL WESTERN SOUTHERN AT 513-629-1400. FOR DETAILED PAYMENT INFO, SEE THE EOP ON CHANGE HEALTHCARE WEBSITE.|url||31048|
11 changes: 11 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,14 @@ def sample_935_with_interests():
def nevada_medicaid_sample():
path = current_path + '/test_edi_835_files/nevada_medicaid.txt'
return edi_835_parser.parse(path)


@pytest.fixture
def exhaustive_sample_path():
return current_path + '/test_edi_835_files/exhaustive_sample.txt'


@pytest.fixture
def exhaustive_sample():
path = current_path + '/test_edi_835_files/exhaustive_sample.txt'
return edi_835_parser.parse(path)
15 changes: 15 additions & 0 deletions tests/loops/test_claim.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,18 @@ def test_medicaid_sample(nevada_medicaid_sample):

claim_loop = get_claim_by_control_number(nevada_medicaid_sample, '77777777')
assert claim_loop.claim.charge_amount == '72232'


def test_can_parse_a_claim_contact(exhaustive_sample):
claim_loop = get_claim_by_control_number(exhaustive_sample, 'X')
assert claim_loop.contacts[0].name == 'XXXXX'
assert claim_loop.contacts[0].communication_no_or_url_qualifier == 'phone'
assert claim_loop.contacts[0].communication_no_or_url == 'XXXX'


def test_reader_for_claim_payer_contacts(exhaustive_sample):
claim_loop = get_claim_by_control_number(exhaustive_sample, 'X')

assert claim_loop.claim_payer_contact.name == 'XXXXX'
assert claim_loop.claim_payer_contact.communication_no_or_url_qualifier == 'phone'
assert claim_loop.claim_payer_contact.communication_no_or_url == 'XXXX'
56 changes: 56 additions & 0 deletions tests/segments/test_payer_contact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from edi_835_parser.segments.payer_contact import PayerContact


def test_payer_contact_initialization():
segment = '0:PER*CX*XX*FX*XXXXX*FX*XXXXXX*EX*XXX'
payer_contact = PayerContact(segment)

assert payer_contact.index == '0'
assert payer_contact.identifier == 'PER'
assert payer_contact.code == 'payers_claim_office'
assert payer_contact.name == 'XX'

assert payer_contact.communication_no_or_url_qualifier == 'fax'
assert payer_contact.communication_no_or_url == 'XXXXX'

assert payer_contact.communication_no_or_url_qualifier_2 == 'fax'
assert payer_contact.communication_no_or_url_2 == 'XXXXXX'

assert payer_contact.communication_no_or_url_qualifier_3 == 'ext'
assert payer_contact.communication_no_or_url_3 == 'XXX'


def test_payer_contact_phone_number():
segment = '0:PER*CX*WESTERN SOUTHERN BENEFITS*TE*5136291100'
payer_contact = PayerContact(segment)

assert payer_contact.phone_number == '5136291100'


def test_payer_contact_phone_number_with_ext():
segment = '0:PER*CX*WESTERN SOUTHERN BENEFITS*TE*5136291100*EX*123*FX*5136291109'
payer_contact = PayerContact(segment)

assert payer_contact.phone_number == '5136291100x123'


def test_payer_contact_fax():
segment = '0:PER*CX*WESTERN SOUTHERN BENEFITS*TE*5136291100*FX*5136291109*EX*123'
payer_contact = PayerContact(segment)

assert payer_contact.fax_number == '5136291109x123'


def test_payer_contact_email():
segment = '0:PER*BL*Nevada Medicaid*TE*8776383472*EM*[email protected]'
payer_contact = PayerContact(segment)

assert payer_contact.phone_number == '8776383472'
assert payer_contact.email == '[email protected]'


def test_payer_contact_url():
segment = '0:PER*BL*PROVIDER SERVICES*TE*8003439000*UR*www.emedny.org'
payer_contact = PayerContact(segment)

assert payer_contact.url == 'www.emedny.org'
52 changes: 52 additions & 0 deletions tests/test_edi_835_files/exhaustive_sample.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
ST*835*0001~
BPR*U*00*C*NON*CTX*01*XXXXXXXX*DA*XXX*XXXXXXXXXX*XXXXXXXXX*01*XXX*DA*XXXX*20241019~
TRN*1*XXXX*XXXXXXXXXX*XXX~
CUR*PR*XXX~
REF*EV*XXXXX~
REF*F2*X~
DTM*405*20241019~
N1*PR*XXXXX*XV*XXX~
N3*XXXX*XXXX~
N4*XX*XX*XXX*XXX~
REF*2U*XXX~
PER*CX*XXXXXX*FX*XXX*TE*XXXXXX*EX*XXXXX~
PER*BL*XXXX*TE*XXXXX*TE*XX*EM*XXX~
PER*IC**UR*XXX~
N1*PE*XXXXXX*XV*XXX~
N3*XXXX*XXXX~
N4*XX*XX*XXXXXX*XXX~
REF*D3*XXX~
RDM*EM*XXXX*XXXX~
LX*000~
TS3*XXXXX*X*20241019*00000000*00000000000000********000000**000000000000**0000000000000*00000000000**0000000*00*00000*0000*000000000000~
TS2*0000000000*00000000000000*0000000000000*0000000000000*0000000000*0*00000000000000*0*000000*0000000000*0000000000*0000000000000*0000000000000*00*0000000000*00000000*00*0*00000000000~
CLP*X*2*000000000*0000000000*00000000*MA*XXXXXX*X*X**XX*000000000000000*0000000~
CAS*PR*XXX*00000000*000000000*XX*00000000*000000000000*XXX*00000000000*0000*XX*000000000*000000000000*XX*000000000000*0000000000000*XX*0000000*00000~
NM1*74*2*XX*X*XXXXX**XXXX*C*XXXXXXX~
NM1*PR*2*XXXX*****NI*XX~
NM1*TT*2*XX*****PI*XXXXXX~
NM1*IL*X*XXXXX*XXXXXX****XX*XXXXXXX~
NM1*GB*2*XXXXX*XX*XXX**XXXXXX*MI*XXX~
NM1*QC*1*X*XXX*XXXX**XX*MI*XXXX*XX~
NM1*82*1*XXXXX*XXXX*X**XXXX*MC*XX~
MIA*0000*00*0*00*XXXX*0000*0000000*000*000000*00*0000*00*0000000000000*00000000000000*000000000000000*0000000*0*000000000*000000*XXXXX*XXXXXX*XXX*X*0~
MOA*000*0000000000000*XXXXX*XXX*XXX*XXXXXX*XX*000000000*00~
REF*EA*X~
DTM*050*20241019~
DTM*036*20241019~
DTM*233*20241019~
PER*CX*XXXXX*TE*XXXX*FX*XXX*EX*XXXXXX~
AMT*ZN*0000~
QTY*CA*000000~
SVC*NU>XXXX>XX>XX>XX>XX*000000000000000*0*X*0000*HC>XXXX>XX>XX>XX>XX>XXXXX*00000000~
DTM*151*20241019~
CAS*OA*X*000000000000000*000000000000000*XXXXX*00*000000000*XXXXX*000000000000*0000*XXX*0000000*0*XXX*00000*0000000000*XXX*0000*00000000000~
REF*0K*XXXXX~
REF*6R*XXXXX~
REF*SY*XXX~
REF*APC*XXXXXX~
AMT*KH*0000000~
QTY*ZM*0~
LQ*HE*XXXX~
PLB*XXXX*20241019*GO>XXXX*0000000*XX>XXXXXX*00000*XX>XXX*00000000000000*XX>X*0000000000000*XX>XXXXXX*000000*XX>XX*0000000~
SE*52*0001~
16 changes: 16 additions & 0 deletions tests/test_edi_835_parser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from decimal import Decimal
import logging
import os
import subprocess

Expand Down Expand Up @@ -48,6 +49,21 @@ def test_total_interests(sample_935_with_interests):
assert sum_interests(sample_935_with_interests) == round(Decimal(10.3), 2)


def test_can_parse_edi_files_without_warnings(exhaustive_sample_path, caplog):
import edi_835_parser

with caplog.at_level(logging.INFO, logger='edi_835_parser'):
edi_835_parser.parse(exhaustive_sample_path)
assert 'Identifier: PER not handled in claim loop.' not in caplog.text

# These are not yet supported
# assert 'Identifier: CUR not handled in transaction loop.'
# assert 'Identifier: RDM not handled in organization loop.'
# assert 'Identifier: TS2 not handled in transaction loop. '
# assert 'Identifier: QTY not handled in claim loop.'
# assert 'Identifier: QTY not handled in service loop.'


def test_cli_output_snapshot():
OUTPUT_DIR = 'output/remits_poc'
# List all the files in the output directory recursively
Expand Down

0 comments on commit 326f65e

Please sign in to comment.