From 6744c57f95de19b2e25b9a420f19e06e72beeb71 Mon Sep 17 00:00:00 2001 From: Venkatakrishna Avula Date: Fri, 6 Mar 2026 16:11:59 +0000 Subject: [PATCH] Merged in bugfix/DAIP2-1870-dynamic-issues (pull request #892) Bugfix/DAIP2-1870 dynamic issues * updated list format appending * fill bill type from claim type cd * Fixed remaining format fixes * Merged DEV into bugfix/DAIP2-1870-dynamic-issues * changed crosswalk mapping from PRODUCT to AARETE_DERIVED_PRODUCT * Merged DEV into bugfix/DAIP2-1870-dynamic-issues * updating LOB field values as well from PRODUCT AND PROGRAM * updated apply crosswalk for reverse mapping * Merged DEV into bugfix/DAIP2-1870-dynamic-issues * added QHP and BHP * removed lob reverse mapping * Merged DEV into bugfix/DAIP2-1870-dynamic-issues * Remove Bill Type/Claim Type fill * Merged DEV into bugfix/DAIP2-1870-dynamic-issues * Black format * Merged DEV into bugfix/DAIP2-1870-dynamic-issues Approved-by: Katon Minhas --- src/codes/code_funcs.py | 7 +++- src/constants/mappings/crosswalk_program.json | 4 ++- .../mappings/crosswalk_program_lob.json | 4 ++- .../shared/postprocessing/aarete_derived.py | 6 ++-- .../postprocessing/postprocessing_funcs.py | 18 +++++++---- src/tests/test_code_funcs.py | 19 +++++++---- src/utils/crosswalk_utils.py | 32 +++++++++++++++---- 7 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/codes/code_funcs.py b/src/codes/code_funcs.py index b991be3..e8918b5 100644 --- a/src/codes/code_funcs.py +++ b/src/codes/code_funcs.py @@ -567,7 +567,12 @@ def fill_bill_type( bill_codes, bill_descs = [], [] for description in llm_answer_final: if description in BILL_TYPE_REVERSE_MAPPING: - bill_codes += BILL_TYPE_REVERSE_MAPPING[description] + codes = BILL_TYPE_REVERSE_MAPPING[description] + # Reverse mapping returns a list of codes for each description + if isinstance(codes, list): + bill_codes.extend(codes) + else: + bill_codes.append(codes) bill_descs.append(description) if bill_codes: diff --git a/src/constants/mappings/crosswalk_program.json b/src/constants/mappings/crosswalk_program.json index 27ad8ec..c6f9c1d 100644 --- a/src/constants/mappings/crosswalk_program.json +++ b/src/constants/mappings/crosswalk_program.json @@ -190,7 +190,9 @@ "NY State of Health" : "NYSOH", "Health and Recovery Plan (HARP)" : "HARP", "Child Health Plus (CHP)" : "CHPLUS", - "Essential Plan" : "EPNY" + "Essential Plan" : "EPNY", + "New York State Health Insurance Exchange Qualified Health Plan" : "QHP", + "Basic Health Plan" : "BHP" }, "NC" : { "NC Health Choice" : "CHIP" diff --git a/src/constants/mappings/crosswalk_program_lob.json b/src/constants/mappings/crosswalk_program_lob.json index 59b152d..0ec9edb 100644 --- a/src/constants/mappings/crosswalk_program_lob.json +++ b/src/constants/mappings/crosswalk_program_lob.json @@ -23,6 +23,7 @@ "SNAP" : "Medicaid", "TANF" : "Medicaid", "LTSS" : "Medicaid", + "BHP" : "Medicaid", "SNP" : "Duals", "CSNP" : "Duals", "DSNP" : "Duals", @@ -43,7 +44,8 @@ "SLVR" : "Marketplace", "BRNZ" : "Marketplace", "CONN" : "Marketplace", - "CATA" : "Marketplace" + "CATA" : "Marketplace", + "QHP" : "Marketplace" }, "state_mapping" : { "AL" : { diff --git a/src/pipelines/shared/postprocessing/aarete_derived.py b/src/pipelines/shared/postprocessing/aarete_derived.py index 0e77a55..4d91555 100644 --- a/src/pipelines/shared/postprocessing/aarete_derived.py +++ b/src/pipelines/shared/postprocessing/aarete_derived.py @@ -97,13 +97,13 @@ def fill_na_mapping(answer_dicts): # Update all_lob_values after processing all program LOB values all_lob_values.update(program_lob_values) - # Get AARETE_DERIVED_LOB from PRODUCT crosswalk (always check if PRODUCT exists) + # Get AARETE_DERIVED_LOB from AARETE_DERIVED_PRODUCT crosswalk (always check if PRODUCT exists) product_lob_values = set() - if not string_utils.is_empty(answer_dict.get("PRODUCT")): + if not string_utils.is_empty(answer_dict.get("AARETE_DERIVED_PRODUCT")): product_filled_value_list = fill_na_from_field( answer_dict, "AARETE_DERIVED_LOB", - "PRODUCT", + "AARETE_DERIVED_PRODUCT", "src/constants/mappings/crosswalk_product_lob.json", ) if ( diff --git a/src/pipelines/shared/postprocessing/postprocessing_funcs.py b/src/pipelines/shared/postprocessing/postprocessing_funcs.py index 5fd9bb6..281310c 100644 --- a/src/pipelines/shared/postprocessing/postprocessing_funcs.py +++ b/src/pipelines/shared/postprocessing/postprocessing_funcs.py @@ -1218,12 +1218,18 @@ def format_as_json_list(val): """ # 1. Handle actual lists or Nulls if isinstance(val, list): - filtered = [ - str(i).strip() - for i in val - if i is not None and not _is_empty_list_placeholder(i) - ] - return json.dumps(filtered) if filtered else "" + # Flatten nested lists before processing + flattened = [] + for i in val: + if i is None or _is_empty_list_placeholder(i): + continue + # If item is a list itself, extend (flatten) + if isinstance(i, list): + flattened.extend([str(x).strip() for x in i if x is not None]) + else: + flattened.append(str(i).strip()) + + return json.dumps(flattened) if flattened else "" if pd.isna(val) or val == "" or val == "[]": return "" diff --git a/src/tests/test_code_funcs.py b/src/tests/test_code_funcs.py index db80d50..bc7f615 100644 --- a/src/tests/test_code_funcs.py +++ b/src/tests/test_code_funcs.py @@ -34,8 +34,16 @@ class TestCodeFuncs(unittest.TestCase): self.constants.HCPCS_LEVEL2_MAPPING = {"J0002": "Advanced Drug"} self.constants.REV_MAPPING = {"456": "Advanced Revenue"} - self.constants.BILL_TYPE_MAPPING = {"11X": "Inpatient Hospital"} - self.constants.BILL_TYPE_REVERSE_MAPPING = {"Inpatient Hospital": "11X"} + # Bill type mappings matching the actual JSON structure + self.constants.BILL_TYPE_MAPPING = { + "011X": "Inpatient Hospital", + "012X": "Inpatient Hospital", + "018X": "Inpatient Hospital", + } + # Reverse mapping returns a list of codes for each description + self.constants.BILL_TYPE_REVERSE_MAPPING = { + "Inpatient Hospital": ["011X", "012X", "018X"] + } # Mock the embedding model and function mock_embedding = MagicMock() @@ -254,8 +262,6 @@ class TestCodeFuncs(unittest.TestCase): mock_invoke_claude.return_value = '["Inpatient Hospital"]' # Test with valid bill type - # Note: Current implementation has a bug where += on string splits it into chars - # The test expects the current (buggy) behavior: ['1', '1', 'X'] answer_dict = {} result = code_funcs.fill_bill_type( "INPATIENT SERVICE", @@ -264,8 +270,9 @@ class TestCodeFuncs(unittest.TestCase): self.constants.BILL_TYPE_REVERSE_MAPPING, ) - # Current buggy behavior: bill_codes += string splits into chars - self.assertEqual(result["BILL_TYPE_CD"], ["1", "1", "X"]) + # Should return all codes for "Inpatient Hospital" description + # Reverse mapping returns ["011X", "012X", "018X"] for "Inpatient Hospital" + self.assertEqual(result["BILL_TYPE_CD"], ["011X", "012X", "018X"]) self.assertEqual(result["BILL_TYPE_CD_DESC"], ["Inpatient Hospital"]) # Test with empty response diff --git a/src/utils/crosswalk_utils.py b/src/utils/crosswalk_utils.py index b55af9f..c7a62ed 100644 --- a/src/utils/crosswalk_utils.py +++ b/src/utils/crosswalk_utils.py @@ -5,13 +5,13 @@ import src.utils.string_utils as string_utils def apply_crosswalk( - val: str | list, mapping: dict[str, str], default: str = "N/A" + val: str | list, mapping: dict[str, str | list[str]], default: str = "N/A" ) -> list: """Apply a crosswalk to a value Args: val (str | list): Input value (can be string, list, or string representation of list) - mapping (dict[str, str]): Mapping dictionary + mapping (dict[str, str | list[str]]): Mapping dictionary (supports both regular and reverse mappings) default (str | None, optional): Default value; if None and val is not found in the mapping, val is returned. Defaults to "". Returns: @@ -54,11 +54,29 @@ def apply_crosswalk( if v_str in mapping: mapped_result = mapping[v_str] if mapped_result and not string_utils.is_empty(mapped_result): - mapped_values.add(mapped_result) - elif v_str in mapping.values(): - mapped_values.add( - v_str - ) # If the value is already a mapped value, add it directly + # Handle both string and list values in mapping (e.g., reverse mappings) + if isinstance(mapped_result, list): + for item in mapped_result: + if item and not string_utils.is_empty(item): + mapped_values.add(item) + else: + mapped_values.add(mapped_result) + else: + # Check if v_str is already a mapped value (in mapping.values()) + # Handle both str and list[str] values + found_in_values = False + for mapped_val in mapping.values(): + if isinstance(mapped_val, list): + if v_str in mapped_val: + if not string_utils.is_empty(v_str): + mapped_values.add(v_str) + found_in_values = True + break + elif v_str == mapped_val: + if not string_utils.is_empty(v_str): + mapped_values.add(v_str) + found_in_values = True + break # Convert to list and filter out empty values final_mapped = [v for v in mapped_values if v and not string_utils.is_empty(v)]