diff --git a/edi_835_parser/elements/contact_communication_number_qualifier.py b/edi_835_parser/elements/contact_communication_number_qualifier.py new file mode 100644 index 0000000..a456bf2 --- /dev/null +++ b/edi_835_parser/elements/contact_communication_number_qualifier.py @@ -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) diff --git a/edi_835_parser/loops/claim.py b/edi_835_parser/loops/claim.py index d042e7d..fd43cef 100644 --- a/edi_835_parser/loops/claim.py +++ b/edi_835_parser/loops/claim.py @@ -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, ) @@ -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()) @@ -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] @@ -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 @@ -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: diff --git a/edi_835_parser/loops/organization.py b/edi_835_parser/loops/organization.py index d2a4f89..5241ea0 100644 --- a/edi_835_parser/loops/organization.py +++ b/edi_835_parser/loops/organization.py @@ -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: diff --git a/edi_835_parser/loops/service.py b/edi_835_parser/loops/service.py index 35ee47d..f5327d8 100644 --- a/edi_835_parser/loops/service.py +++ b/edi_835_parser/loops/service.py @@ -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: diff --git a/edi_835_parser/segments/payer_contact.py b/edi_835_parser/segments/payer_contact.py index bb026d8..b1ff507 100644 --- a/edi_835_parser/segments/payer_contact.py +++ b/edi_835_parser/segments/payer_contact.py @@ -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 @@ -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] @@ -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()) diff --git a/output/remits_poc/remit_payers/sample_835.txt b/output/remits_poc/remit_payers/sample_835.txt index e2876f1..779c7f7 100644 --- a/output/remits_poc/remit_payers/sample_835.txt +++ b/output/remits_poc/remit_payers/sample_835.txt @@ -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| diff --git a/tests/conftest.py b/tests/conftest.py index 47a153f..b6b71c7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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) diff --git a/tests/loops/test_claim.py b/tests/loops/test_claim.py index 1c3d771..221459b 100644 --- a/tests/loops/test_claim.py +++ b/tests/loops/test_claim.py @@ -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' diff --git a/tests/segments/test_payer_contact.py b/tests/segments/test_payer_contact.py new file mode 100644 index 0000000..4bb8e3e --- /dev/null +++ b/tests/segments/test_payer_contact.py @@ -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*nvmmis.edisupport@dxc.com' + payer_contact = PayerContact(segment) + + assert payer_contact.phone_number == '8776383472' + assert payer_contact.email == 'nvmmis.edisupport@dxc.com' + + +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' diff --git a/tests/test_edi_835_files/exhaustive_sample.txt b/tests/test_edi_835_files/exhaustive_sample.txt new file mode 100644 index 0000000..c88df6c --- /dev/null +++ b/tests/test_edi_835_files/exhaustive_sample.txt @@ -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~ diff --git a/tests/test_edi_835_parser.py b/tests/test_edi_835_parser.py index 410373b..8b9ed8b 100644 --- a/tests/test_edi_835_parser.py +++ b/tests/test_edi_835_parser.py @@ -1,4 +1,5 @@ from decimal import Decimal +import logging import os import subprocess @@ -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