Merged in code-breakout-mismaps (pull request #419)
DAIP2-112 - fixed get code descriptions * indirect proc codes added * bug fix * bug fix * Merge branch 'main' into proc-code-indirect * description mapping handling X's and 0's * bug fix * Merge remote-tracking branch 'origin/proc-code-indirect' into code-breakout-mismaps * testing mismaps * revenue codes mapping for int values * added function to find ranges * removed test files * Merged main into code-breakout-mismaps * Merge branch 'main' into code-breakout-mismaps pulled latest changes from main * added unit test for find containing ranges * modified find_containing_ranges_test file * Merged main into code-breakout-mismaps * added embeddings to git ignore * updated find_containing_ranges_test.py Approved-by: Alex Galarce Approved-by: Katon Minhas
This commit is contained in:
committed by
Alex Galarce
parent
0fee01ace8
commit
576ea95b6d
@@ -95,3 +95,6 @@ __pycache__/
|
||||
|
||||
.env
|
||||
new/
|
||||
|
||||
# Embeddings
|
||||
fieldExtraction/embeddings/
|
||||
@@ -78,6 +78,60 @@ def crosswalk_levels():
|
||||
|
||||
return crosswalk_dict
|
||||
|
||||
def find_containing_ranges(input_range, code_mapping):
|
||||
"""
|
||||
Find which code ranges in the dictionary contain or overlap with the input range.
|
||||
|
||||
Args:
|
||||
input_range (str): Code range in format 'START-END' (e.g., '70010-79999')
|
||||
code_mapping (dict): Dictionary containing CPT codes with code ranges as keys and descriptions as values
|
||||
|
||||
Returns:
|
||||
list: List of dictionaries containing matching code ranges and descriptions
|
||||
"""
|
||||
# Parse the input range
|
||||
match = re.match(r'(\d+)-(\d+)', input_range)
|
||||
if not match:
|
||||
raise ValueError("Invalid input format. Expected format: 'START-END'")
|
||||
|
||||
input_start = int(match.group(1))
|
||||
input_end = int(match.group(2))
|
||||
|
||||
# Initialize results list
|
||||
results = []
|
||||
|
||||
# Process each code range in the dictionary
|
||||
for code_range, description in code_mapping.items():
|
||||
# Parse the code range from the dictionary
|
||||
range_match = re.match(r'(\d+)-(\d+)', code_range)
|
||||
if range_match:
|
||||
csv_start = int(range_match.group(1))
|
||||
csv_end = int(range_match.group(2))
|
||||
|
||||
# Check if there's any overlap between ranges
|
||||
if max(input_start, csv_start) <= min(input_end, csv_end):
|
||||
# The ranges overlap in some way
|
||||
results.append({
|
||||
'code_range': code_range,
|
||||
'description': description,
|
||||
'overlap_start': max(input_start, csv_start),
|
||||
'overlap_end': min(input_end, csv_end)
|
||||
})
|
||||
# Extract only the code_range values
|
||||
# code_ranges = [result['code_range'] for result in results]
|
||||
# If no single range contains all others, return the highest level ranges
|
||||
highest_level_ranges = []
|
||||
for result in results:
|
||||
is_highest = True
|
||||
for other in results:
|
||||
if result != other and int(result['overlap_start']) >= int(other['overlap_start']) and int(result['overlap_end']) <= int(other['overlap_end']):
|
||||
is_highest = False
|
||||
break
|
||||
if is_highest:
|
||||
highest_level_ranges.append(result['code_range'])
|
||||
return highest_level_ranges
|
||||
# return code_ranges
|
||||
|
||||
def code_primary(answer_dicts, filename):
|
||||
"""
|
||||
Breaks down the direct code information from CONTRACT_SERVICE_CD_OR_DESC and CONTRACT_REIMBURSEMENT_METHOD fields
|
||||
@@ -266,29 +320,58 @@ def get_code_description(code, code_mapping):
|
||||
code_list = ast.literal_eval(code)
|
||||
elif "," in code:
|
||||
code_list = code.split(",")
|
||||
elif "-" in code:
|
||||
code_list = find_containing_ranges(code, code_mapping.mapping)
|
||||
else:
|
||||
code_list = [code]
|
||||
code_list = [code] # added this line to handle range strings like 'J0120-J7175'
|
||||
elif isinstance(code, list):
|
||||
code_list = code
|
||||
else:
|
||||
code_list = [code]
|
||||
|
||||
|
||||
code_description = []
|
||||
for code in code_list:
|
||||
code_str = str(code).strip()
|
||||
description = code_mapping.mapping.get(code_str, "mismap")
|
||||
|
||||
|
||||
if string_utils.is_empty(description) or description == "mismap":
|
||||
# If mismap, prepend "0" and try again (useful for rev codes)
|
||||
code_str = "0" + code_str
|
||||
description = code_mapping.mapping.get(code_str, "mismap")
|
||||
if code_str.isdigit():
|
||||
description = code_mapping.mapping.get(int(code_str), "mismap")
|
||||
else:
|
||||
description = "mismap"
|
||||
|
||||
# If mismap, try to handle ranges
|
||||
if string_utils.is_empty(description) or description == "mismap":
|
||||
if "X" in code_str:
|
||||
base_code = code_str.replace("X", "")
|
||||
expanded_code_range = f"{base_code}0-{base_code}9"
|
||||
description = code_mapping.mapping.get(expanded_code_range, "mismap")
|
||||
# If mismap, try to handle numeric codes
|
||||
if string_utils.is_empty(description) or description == "mismap":
|
||||
numeric_codes_description = []
|
||||
for i in range(10):
|
||||
expanded_code = base_code + str(i)
|
||||
inner_description = code_mapping.mapping.get(expanded_code, "mismap")
|
||||
if inner_description != "mismap":
|
||||
numeric_codes_description.append(inner_description)
|
||||
description = ", ".join(numeric_codes_description)
|
||||
else:
|
||||
# If mismap, prepend "0" and try again (useful for rev codes)
|
||||
code_str = "0" + code_str
|
||||
description = code_mapping.mapping.get(code_str, "mismap")
|
||||
|
||||
# We need similar logic for Diag codes with X (e.g. 123X)
|
||||
# Could either: a) convert to list ([1230, 1231, ...]), b) convert to range (1230-1239), c) leave as is (123X) but have the descriptions be a list ["desc1","desc2", etc]
|
||||
# for i in range(10): code is '123' + i' map that code
|
||||
|
||||
if not string_utils.is_empty(description):
|
||||
code_description.append(description)
|
||||
code_description.append(description)
|
||||
|
||||
# Remove repeated values and retain unique values, excluding "mismap" if other values are present
|
||||
unique_descriptions = list(set(code_description))
|
||||
if "mismap" in unique_descriptions and len(unique_descriptions) > 1:
|
||||
unique_descriptions.remove("mismap")
|
||||
code_description = unique_descriptions
|
||||
|
||||
return ",".join(code_description)
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from src.investment.code_funcs import find_containing_ranges
|
||||
|
||||
def test_find_containing_ranges_valid_input():
|
||||
input_range = '70010-79999'
|
||||
code_mapping = {
|
||||
'70000-70050': 'Description 1',
|
||||
'70040-70060': 'Description 2',
|
||||
'70010-70020': 'Description 3',
|
||||
'80000-80050': 'Description 4'
|
||||
}
|
||||
expected_output = ['70000-70050', '70040-70060']
|
||||
assert find_containing_ranges(input_range, code_mapping) == expected_output
|
||||
|
||||
def test_find_containing_ranges_no_overlap():
|
||||
input_range = '70010-70020'
|
||||
code_mapping = {
|
||||
'70030-70040': 'Description 1',
|
||||
'70050-70060': 'Description 2',
|
||||
'80000-80050': 'Description 3'
|
||||
}
|
||||
expected_output = []
|
||||
assert find_containing_ranges(input_range, code_mapping) == expected_output
|
||||
|
||||
def test_find_containing_ranges_partial_overlap():
|
||||
input_range = '70010-70050'
|
||||
code_mapping = {
|
||||
'70000-70020': 'Description 1',
|
||||
'70040-70060': 'Description 2',
|
||||
'70050-70070': 'Description 3'
|
||||
}
|
||||
expected_output = ['70000-70020', '70040-70060']
|
||||
assert find_containing_ranges(input_range, code_mapping) == expected_output
|
||||
|
||||
def test_find_containing_ranges_exact_match():
|
||||
input_range = '70000-70050'
|
||||
code_mapping = {
|
||||
'70000-70050': 'Description 1',
|
||||
'70040-70060': 'Description 2',
|
||||
'70010-70020': 'Description 3'
|
||||
}
|
||||
expected_output = ['70000-70050']
|
||||
assert find_containing_ranges(input_range, code_mapping) == expected_output
|
||||
|
||||
def test_find_containing_ranges_empty_mapping():
|
||||
input_range = '70000-70050'
|
||||
code_mapping = {}
|
||||
expected_output = []
|
||||
assert find_containing_ranges(input_range, code_mapping) == expected_output
|
||||
|
||||
def test_find_containing_ranges_invalid_input():
|
||||
input_range = 'invalid-range'
|
||||
code_mapping = {
|
||||
'70000-70050': 'Description 1',
|
||||
'70040-70060': 'Description 2'
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
find_containing_ranges(input_range, code_mapping)
|
||||
Reference in New Issue
Block a user