Skip to content

Commit

Permalink
Adds LX and Ref for transactions
Browse files Browse the repository at this point in the history
This resolves all stdout warnings of
Identifier: LX not handled in transaction loop.
Identifier: REF not handled in transaction loop.

While these fields don't seem super useful no warnings is quite nice.

This also adds a nevada medicaid sample found here https://www.medicaid.nv.gov/Downloads/provider/Sample_835_File.pdf

[AT-5936]

Signed-off-by: Russell Osborne <[email protected]>
  • Loading branch information
rposborne committed Sep 27, 2024
1 parent 86127e6 commit 6e1f542
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 96 deletions.
7 changes: 3 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
"python.testing.pytestEnabled": true,
"python.analysis.extraPaths": ["./tests"]
}
9 changes: 2 additions & 7 deletions edi_835_parser/elements/reference_qualifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,11 @@
'G2': 'Provider Commerical Number',
'HPI': 'Centers for Medicare and Medicaid Services National Provider Identifier',
'SY': 'Social Security Number',
# https://www.cms.gov/medicare/billing/electronicbillingeditrans/downloads/835-flatfile.pdf
'EV': 'Reference Identification Number',
}


# class ReferenceQualifier(Element):
#
# def parser(self, value: str) -> Code:
# description = reference_qualifiers.get(value, None)
# return Code(value, description)


class ReferenceQualifier(Element):
def parser(self, value: str) -> str:
return reference_qualifiers.get(value, value)
73 changes: 48 additions & 25 deletions edi_835_parser/loops/transaction.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import datetime
from typing import Iterator, Tuple, Optional, List

from edi_835_parser.loops.claim import Claim as ClaimLoop
from edi_835_parser.loops.organization import Organization as OrganizationLoop

from edi_835_parser.segments.header_number import HeaderNumber
from edi_835_parser.segments.utilities import find_identifier
from edi_835_parser.segments.date import Date as DateSegment
from edi_835_parser.segments.transaction import Transaction as TransactionSegment
Expand Down Expand Up @@ -33,24 +35,25 @@ class Transaction:
]

def __init__(
self,
transaction: TransactionSegment = None,
financial_information: FinancialInformationSegment = None,
trace_number: TraceNumberSegment = None,
provider_adjustments: List[ProviderAdjustmentSegment] = None,
provider_summary: ProviderSummarySegment = None,
date: DateSegment = None,
claims: List[ClaimLoop] = None,
organizations: List[OrganizationLoop] = None
self,
transaction: TransactionSegment = None,
financial_information: FinancialInformationSegment = None,
trace_number: TraceNumberSegment = None,
provider_adjustments: List[ProviderAdjustmentSegment] = None,
provider_summary: ProviderSummarySegment = None,
date: DateSegment = None,
claims: List[ClaimLoop] = None,
organizations: List[OrganizationLoop] = None,
):
self.transaction = transaction
self.financial_information = financial_information
self.trace_number = trace_number
self.provider_adjustments = provider_adjustments if provider_adjustments else []
self.provider_summary = provider_summary
self.date = date
self.claims = claims if claims else []
self.organizations = organizations if organizations else []
self.transaction = transaction
self.financial_information = financial_information
self.trace_number = trace_number
self.provider_adjustments = provider_adjustments if provider_adjustments else []
self.provider_summary = provider_summary
self.date = date
self.claims = claims if claims else []
self.organizations = organizations if organizations else []
self.references = []

def __repr__(self):
return '\n'.join(str(item) for item in self.__dict__.items())
Expand Down Expand Up @@ -123,6 +126,16 @@ def other_payee_identification(self) -> Optional[ReferenceSegment]:
if ref.qualifier == 'payee identification':
return ref

@property
def reference_identification_number(self) -> Optional[ReferenceSegment]:
for ref in self.references:
if ref.qualifier == 'Reference Identification Number':
return ref

@property
def production_date(self) -> datetime.datetime:
return self.date.date

@classmethod
def build(
cls, segment: str, segments: Iterator[str]
Expand Down Expand Up @@ -156,9 +169,9 @@ def build(
transaction.trace_number = trace_number
segment = None
elif identifier == ProviderAdjustmentSegment.identification:
provider_adjustment = ProviderAdjustmentSegment(segment)
transaction.provider_adjustments.append(provider_adjustment)
segment = None
provider_adjustment = ProviderAdjustmentSegment(segment)
transaction.provider_adjustments.append(provider_adjustment)
segment = None

elif identifier == ProviderAdjustmentSegment.identification:
provider_adjustment = ProviderAdjustmentSegment(segment)
Expand All @@ -170,18 +183,28 @@ def build(
transaction.provider_summary = provider_summary
segment = None

elif identifier == HeaderNumber.identification:
header_number = HeaderNumber(segment)
transaction.header_number = header_number
segment = None

elif identifier == ReferenceSegment.identification:
reference = ReferenceSegment(segment)
transaction.references.append(reference)
segment = None

elif identifier == DateSegment.identification:
date = DateSegment(segment)
transaction.date = date
segment = None
date = DateSegment(segment)
transaction.date = date
segment = None

elif identifier in cls.terminating_identifiers:
return transaction, segments, segment

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

except StopIteration:
return transaction, None, None
27 changes: 27 additions & 0 deletions edi_835_parser/segments/header_number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from edi_835_parser.elements.identifier import Identifier
from edi_835_parser.elements.integer import Integer
from edi_835_parser.segments.utilities import split_segment


class HeaderNumber:
identification = 'LX'

identifier = Identifier()
number = Integer()

def __init__(self, segment: str):
self.index = segment.split(':', 1)[0]
segment = segment.split(':', 1)[1]

self.segment = segment
segment = split_segment(segment)

self.identifier = segment[0]
self.number = segment[1]

def __repr__(self):
return '\n'.join(str(item) for item in self.__dict__.items())


if __name__ == '__main__':
pass
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ python = "^3.9"
pandas = "^2.0.3"
pytest = "^8.1.1"

[tool.poetry.dev-dependencies]
ruff = "*"

[build-system]
requires = ["poetry-core"]
Expand Down
15 changes: 5 additions & 10 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
sys.path.append(os.path.join(os.path.dirname(__file__), 'utils'))

import edi_835_parser # noqa: E402

Expand Down Expand Up @@ -34,13 +35,7 @@ def sample_935_with_interests():
return edi_835_parser.parse(path)


# @pytest.fixture
# def sample2_835():
# path = current_path + '/test_edi_835_files/sample2_835.txt'
# return edi_835_parser.parse(path)
#
#
# @pytest.fixture
# def sample3_835():
# path = current_path + '/test_edi_835_files/sample3_835.txt'
# return edi_835_parser.parse(path)
@pytest.fixture
def nevada_medicaid_sample():
path = current_path + '/test_edi_835_files/nevada_medicaid.txt'
return edi_835_parser.parse(path)
10 changes: 10 additions & 0 deletions tests/loops/test_claim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from utils import count_claims, count_transactions, count_services, get_claim_by_control_number


def test_medicaid_sample(nevada_medicaid_sample):
assert count_claims(nevada_medicaid_sample) == 3
assert count_transactions(nevada_medicaid_sample) == 3
assert count_services(nevada_medicaid_sample) == 0

claim_loop = get_claim_by_control_number(nevada_medicaid_sample, '77777777')
assert claim_loop.claim.charge_amount == '72232'
31 changes: 31 additions & 0 deletions tests/loops/test_transaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import datetime
from edi_835_parser.segments.address import Address
from edi_835_parser.segments.location import Location


def test_transaction_properties(sample_835):
transaction = sample_835.transactions[0]
assert transaction.transaction.transaction_set_identifier_code == '835'
assert transaction.transaction.transaction_set_control_no == '1002'


def test_transaction_address(sample_835):
transaction = sample_835.transactions[0]
assert isinstance(transaction.payer_address, Address)
assert transaction.payer_address.address_line1 == '400 BROADWAY'
assert isinstance(transaction.payer_location, Location)


def test_transaction_production_date(sample_835):
transaction = sample_835.transactions[0]
assert transaction.production_date == datetime.datetime(2022, 1, 5, 0, 0)


def test_transaction_reference_identification_number(sample_835):
transaction = sample_835.transactions[0]
assert transaction.reference_identification_number.value == 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'


def test_transaction_header_number(sample_835):
transaction = sample_835.transactions[0]
assert transaction.header_number.number == 1
1 change: 1 addition & 0 deletions tests/test_edi_835_files/nevada_medicaid.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ISA*00* *00* *ZZ*NVMED *ZZ*99999999*180613*1230*^*00501*100000300*0*P*:~GS*HP*NVMED*99999999*20180613*123021*100000300*X*005010X221A1~ST*835*0001~BPR*H*0*C*NON************20180615~TRN*1*100004762*1388600002~DTM*405*20180613~N1*PR*DIVISON OF HEALTH CARE FINANCING AND POLICY~N3*1100 East William Street Suite 101~N4*Carson*NV*89701~PER*BL*Nevada Medicaid*TE*8776383472*EM*[email protected]~N1*PE*SUMMER*XX*6666666666~REF*TJ*111111111~LX*1~CLP*77777777*4*72232*0**MC*6666666666666~CAS*OA*147*50016*0~CAS*CO*26*22216*0~NM1*QC*1*TOM*SMITH****MR*77777777777~NM1*74*1*ALAN*PARKER****C*88888888888~NM1*PR*2*PACIFI*****PI* 9999~NM1*GB*1*BARRY*CARRY****MI*666666666~REF*EA*8888888~DTM*232*20180314~DTM*233*20180317~SE*22*0001~ST*835*0002~BPR*H*0*C*NON************20180615~TRN*1*100004765*5555555555~DTM*405*20180613~N1*PR*DIVISON OF HEALTH CARE FINANCING AND POLICY~N3*1100 East William Street Suite 101~N4*Carson*NV*89701~PER*BL*Nevada Medicaid*TE*8776383472*EM*[email protected]~N1*PE*VALLEY*XX*6666666666~REF*TJ*530824679~LX*1~CLP*77777778*2*3002*0**MC*6666666666667~CAS*OA*176*3002*0~NM1*QC*1*BOB*THOMAS****MR*55555555555~NM1*74*1*ALAN*JACKSON****C*66666666666~REF*EA*8888888~DTM*232*20171001~DTM*233*20171002~CLP*77777779*4*41231.04*0**MC*6666666666668~CAS*OA*147*9365.04*0~CAS*CO*26*31866*0~NM1*QC*1*HELD*ALLEN****MR*77777777778~NM1*74*1*RYAN*LARRY****C*88888888889~NM1*PR*2*SENIOR*****PI* 8888~NM1*GB*1*MARY*JANE****MI*777777777~REF*EA*6047740~DTM*232*20180220~DTM*233*20180221~SE*29*0002~ST*835*0003~BPR*I*1812.27*C*CHK************20180727~TRN*1*000012382*5555555555~DTM*405*20180720~N1*PR*DIVISON OF HEALTH CARE FINANCING AND POLICY~N3*1100 East William Street Suite 101~N4*Carson*NV*89701~PER*BL*Nevada Medicaid*TE*8776383472*EM*[email protected]~N1*PE*SILVER*XX*7777777777~REF*TJ*666666666~PLB*8888888888*20181231*CT:888888888*-1092.46*CT:888888888*-719.81*CS:8888888888887*-181.55*CS:8888888888887*181.55*CS:8888888888888*-130*CS:8888888888888*130~SE*12*0003~GE*3*100000300~IEA*1*100000301~
57 changes: 7 additions & 50 deletions tests/test_edi_835_parser.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
from decimal import Decimal

def test_claim_count(
blue_cross_nc_sample,
emedny_sample,
sample_835
):
from utils import count_claims, count_transactions, count_services, sum_interests


def test_claim_count(blue_cross_nc_sample, emedny_sample, sample_835):
assert count_claims(blue_cross_nc_sample) == 1
assert count_claims(emedny_sample) == 3
assert count_claims(sample_835) == 6


def test_transaction_count(
blue_cross_nc_sample,
emedny_sample,
sample_835
):
def test_transaction_count(blue_cross_nc_sample, emedny_sample, sample_835):
assert count_transactions(blue_cross_nc_sample) == 1
assert count_transactions(emedny_sample) == 1
assert count_transactions(sample_835) == 2


def test_service_count(
blue_cross_nc_sample,
emedny_sample,
sample_835
):
def test_service_count(blue_cross_nc_sample, emedny_sample, sample_835):
assert count_services(blue_cross_nc_sample) == 3
assert count_services(emedny_sample) == 10
assert count_services(sample_835) == 12
Expand All @@ -51,39 +42,5 @@ def test_build_remit_service_lines(blue_cross_nc_sample, emedny_sample, sample_8
assert sample_835.build_remit_service_lines().shape[0] == 12


def test_total_interests(
sample_935_with_interests
):
def test_total_interests(sample_935_with_interests):
assert sum_interests(sample_935_with_interests) == round(Decimal(10.3), 2)


def count_claims(transaction_set) -> int:
count = 0
for transaction in transaction_set.transactions:
count += len(transaction.claims)
return count


def count_transactions(transaction_set) -> int:
count = 0
count += len(transaction_set.transactions)

return count

def count_services(transaction_set) -> int:
count = 0
for transaction in transaction_set.transactions:
for claim in transaction.claims:
count += len(claim.services)

return count

def sum_interests(transaction_set):
total_interest = 0
for transaction in transaction_set.transactions:
for claim in transaction.claims:
for amount in claim.amounts:
if amount.qualifier == "I":
total_interest += Decimal(amount.amount)

return total_interest
44 changes: 44 additions & 0 deletions tests/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from decimal import Decimal


def count_claims(transaction_set) -> int:
count = 0
for transaction in transaction_set.transactions:
count += len(transaction.claims)
return count


def count_transactions(transaction_set) -> int:
count = 0
count += len(transaction_set.transactions)

return count


def count_services(transaction_set) -> int:
count = 0
for transaction in transaction_set.transactions:
for claim in transaction.claims:
count += len(claim.services)

return count


def sum_interests(transaction_set):
total_interest = 0
for transaction in transaction_set.transactions:
for claim in transaction.claims:
for amount in claim.amounts:
if amount.qualifier == 'I':
total_interest += Decimal(amount.amount)

return total_interest


def get_claim_by_control_number(transaction_set, claim_id):
for transaction in transaction_set.transactions:
for claim in transaction.claims:
if claim.claim.patient_control_number == claim_id:
return claim

assert False, f'Claim with ID {claim_id} not found'

0 comments on commit 6e1f542

Please sign in to comment.