refactor: complete JSON format standardization updates
- Updated all prompt_calls.py files (saas, bcbs_promise, clover) to use new parser pattern - Updated prompt_templates.py with JSON format instructions - Updated crosswalk_utils.py to handle JSON lists - Updated json_utils.py with improved parsing - Updated aarete_derived.py for JSON compatibility - Updated tin_npi_funcs.py and qa_qc_utils.py for JSON parsing - All changes align with Phase 4 completion of JSON standardization
This commit is contained in:
@@ -129,18 +129,22 @@ def prompt_reimbursement_primary(
|
|||||||
service_term = answer_dict["SERVICE_TERM"]
|
service_term = answer_dict["SERVICE_TERM"]
|
||||||
if isinstance(service_term, list):
|
if isinstance(service_term, list):
|
||||||
# If list, join with comma (shouldn't happen, but defensive)
|
# If list, join with comma (shouldn't happen, but defensive)
|
||||||
answer_dict["SERVICE_TERM"] = ", ".join(str(item) for item in service_term if item)
|
answer_dict["SERVICE_TERM"] = ", ".join(
|
||||||
|
str(item) for item in service_term if item
|
||||||
|
)
|
||||||
elif service_term is not None:
|
elif service_term is not None:
|
||||||
answer_dict["SERVICE_TERM"] = str(service_term)
|
answer_dict["SERVICE_TERM"] = str(service_term)
|
||||||
else:
|
else:
|
||||||
answer_dict["SERVICE_TERM"] = ""
|
answer_dict["SERVICE_TERM"] = ""
|
||||||
|
|
||||||
# REIMB_TERM should be a string per row
|
# REIMB_TERM should be a string per row
|
||||||
if "REIMB_TERM" in answer_dict:
|
if "REIMB_TERM" in answer_dict:
|
||||||
reimb_term = answer_dict["REIMB_TERM"]
|
reimb_term = answer_dict["REIMB_TERM"]
|
||||||
if isinstance(reimb_term, list):
|
if isinstance(reimb_term, list):
|
||||||
# If list, join with comma (shouldn't happen, but defensive)
|
# If list, join with comma (shouldn't happen, but defensive)
|
||||||
answer_dict["REIMB_TERM"] = ", ".join(str(item) for item in reimb_term if item)
|
answer_dict["REIMB_TERM"] = ", ".join(
|
||||||
|
str(item) for item in reimb_term if item
|
||||||
|
)
|
||||||
elif reimb_term is not None:
|
elif reimb_term is not None:
|
||||||
answer_dict["REIMB_TERM"] = str(reimb_term)
|
answer_dict["REIMB_TERM"] = str(reimb_term)
|
||||||
else:
|
else:
|
||||||
@@ -181,11 +185,16 @@ def prompt_methodology_breakout(
|
|||||||
# FEE_SCHEDULE should be a string per methodology breakout result
|
# FEE_SCHEDULE should be a string per methodology breakout result
|
||||||
if isinstance(methodology_breakout_answers, list):
|
if isinstance(methodology_breakout_answers, list):
|
||||||
for methodology_dict in methodology_breakout_answers:
|
for methodology_dict in methodology_breakout_answers:
|
||||||
if isinstance(methodology_dict, dict) and "FEE_SCHEDULE" in methodology_dict:
|
if (
|
||||||
|
isinstance(methodology_dict, dict)
|
||||||
|
and "FEE_SCHEDULE" in methodology_dict
|
||||||
|
):
|
||||||
fee_schedule = methodology_dict["FEE_SCHEDULE"]
|
fee_schedule = methodology_dict["FEE_SCHEDULE"]
|
||||||
if isinstance(fee_schedule, list):
|
if isinstance(fee_schedule, list):
|
||||||
# If list, take first element (shouldn't happen, but defensive)
|
# If list, take first element (shouldn't happen, but defensive)
|
||||||
methodology_dict["FEE_SCHEDULE"] = fee_schedule[0] if fee_schedule else ""
|
methodology_dict["FEE_SCHEDULE"] = (
|
||||||
|
fee_schedule[0] if fee_schedule else ""
|
||||||
|
)
|
||||||
elif fee_schedule is not None:
|
elif fee_schedule is not None:
|
||||||
methodology_dict["FEE_SCHEDULE"] = str(fee_schedule)
|
methodology_dict["FEE_SCHEDULE"] = str(fee_schedule)
|
||||||
else:
|
else:
|
||||||
@@ -211,7 +220,7 @@ def prompt_fee_schedule_breakout(
|
|||||||
# If list, take first element (shouldn't happen, but defensive)
|
# If list, take first element (shouldn't happen, but defensive)
|
||||||
fee_schedule = fee_schedule[0] if fee_schedule else ""
|
fee_schedule = fee_schedule[0] if fee_schedule else ""
|
||||||
fee_schedule = str(fee_schedule) if fee_schedule else ""
|
fee_schedule = str(fee_schedule) if fee_schedule else ""
|
||||||
|
|
||||||
prompt, _parser = prompt_templates.FEE_SCHEDULE_BREAKOUT(
|
prompt, _parser = prompt_templates.FEE_SCHEDULE_BREAKOUT(
|
||||||
reimbursement_method,
|
reimbursement_method,
|
||||||
fee_schedule,
|
fee_schedule,
|
||||||
|
|||||||
@@ -136,18 +136,22 @@ def prompt_reimbursement_primary(
|
|||||||
service_term = answer_dict["SERVICE_TERM"]
|
service_term = answer_dict["SERVICE_TERM"]
|
||||||
if isinstance(service_term, list):
|
if isinstance(service_term, list):
|
||||||
# If list, join with comma (shouldn't happen, but defensive)
|
# If list, join with comma (shouldn't happen, but defensive)
|
||||||
answer_dict["SERVICE_TERM"] = ", ".join(str(item) for item in service_term if item)
|
answer_dict["SERVICE_TERM"] = ", ".join(
|
||||||
|
str(item) for item in service_term if item
|
||||||
|
)
|
||||||
elif service_term is not None:
|
elif service_term is not None:
|
||||||
answer_dict["SERVICE_TERM"] = str(service_term)
|
answer_dict["SERVICE_TERM"] = str(service_term)
|
||||||
else:
|
else:
|
||||||
answer_dict["SERVICE_TERM"] = ""
|
answer_dict["SERVICE_TERM"] = ""
|
||||||
|
|
||||||
# REIMB_TERM should be a string per row
|
# REIMB_TERM should be a string per row
|
||||||
if "REIMB_TERM" in answer_dict:
|
if "REIMB_TERM" in answer_dict:
|
||||||
reimb_term = answer_dict["REIMB_TERM"]
|
reimb_term = answer_dict["REIMB_TERM"]
|
||||||
if isinstance(reimb_term, list):
|
if isinstance(reimb_term, list):
|
||||||
# If list, join with comma (shouldn't happen, but defensive)
|
# If list, join with comma (shouldn't happen, but defensive)
|
||||||
answer_dict["REIMB_TERM"] = ", ".join(str(item) for item in reimb_term if item)
|
answer_dict["REIMB_TERM"] = ", ".join(
|
||||||
|
str(item) for item in reimb_term if item
|
||||||
|
)
|
||||||
elif reimb_term is not None:
|
elif reimb_term is not None:
|
||||||
answer_dict["REIMB_TERM"] = str(reimb_term)
|
answer_dict["REIMB_TERM"] = str(reimb_term)
|
||||||
else:
|
else:
|
||||||
@@ -188,11 +192,16 @@ def prompt_methodology_breakout(
|
|||||||
# FEE_SCHEDULE should be a string per methodology breakout result
|
# FEE_SCHEDULE should be a string per methodology breakout result
|
||||||
if isinstance(methodology_breakout_answers, list):
|
if isinstance(methodology_breakout_answers, list):
|
||||||
for methodology_dict in methodology_breakout_answers:
|
for methodology_dict in methodology_breakout_answers:
|
||||||
if isinstance(methodology_dict, dict) and "FEE_SCHEDULE" in methodology_dict:
|
if (
|
||||||
|
isinstance(methodology_dict, dict)
|
||||||
|
and "FEE_SCHEDULE" in methodology_dict
|
||||||
|
):
|
||||||
fee_schedule = methodology_dict["FEE_SCHEDULE"]
|
fee_schedule = methodology_dict["FEE_SCHEDULE"]
|
||||||
if isinstance(fee_schedule, list):
|
if isinstance(fee_schedule, list):
|
||||||
# If list, take first element (shouldn't happen, but defensive)
|
# If list, take first element (shouldn't happen, but defensive)
|
||||||
methodology_dict["FEE_SCHEDULE"] = fee_schedule[0] if fee_schedule else ""
|
methodology_dict["FEE_SCHEDULE"] = (
|
||||||
|
fee_schedule[0] if fee_schedule else ""
|
||||||
|
)
|
||||||
elif fee_schedule is not None:
|
elif fee_schedule is not None:
|
||||||
methodology_dict["FEE_SCHEDULE"] = str(fee_schedule)
|
methodology_dict["FEE_SCHEDULE"] = str(fee_schedule)
|
||||||
else:
|
else:
|
||||||
@@ -218,7 +227,7 @@ def prompt_fee_schedule_breakout(
|
|||||||
# If list, take first element (shouldn't happen, but defensive)
|
# If list, take first element (shouldn't happen, but defensive)
|
||||||
fee_schedule = fee_schedule[0] if fee_schedule else ""
|
fee_schedule = fee_schedule[0] if fee_schedule else ""
|
||||||
fee_schedule = str(fee_schedule) if fee_schedule else ""
|
fee_schedule = str(fee_schedule) if fee_schedule else ""
|
||||||
|
|
||||||
prompt, _parser = prompt_templates.FEE_SCHEDULE_BREAKOUT(
|
prompt, _parser = prompt_templates.FEE_SCHEDULE_BREAKOUT(
|
||||||
reimbursement_method,
|
reimbursement_method,
|
||||||
fee_schedule,
|
fee_schedule,
|
||||||
|
|||||||
@@ -294,7 +294,9 @@ def merge_provider_info_with_hybrid_smart_chunking(one_to_one_results, filename)
|
|||||||
|
|
||||||
# Filter out records with NO_IDENTIFIERS_FOUND
|
# Filter out records with NO_IDENTIFIERS_FOUND
|
||||||
prov_info_json_list = [
|
prov_info_json_list = [
|
||||||
p for p in prov_info_json_list if "NO_IDENTIFIERS_FOUND" not in (p.get("NAME") or [])
|
p
|
||||||
|
for p in prov_info_json_list
|
||||||
|
if "NO_IDENTIFIERS_FOUND" not in (p.get("NAME") or [])
|
||||||
]
|
]
|
||||||
|
|
||||||
# Deduplicate provider_list by NAME, keeping records with actual TIN/NPI values
|
# Deduplicate provider_list by NAME, keeping records with actual TIN/NPI values
|
||||||
@@ -313,7 +315,7 @@ def merge_provider_info_with_hybrid_smart_chunking(one_to_one_results, filename)
|
|||||||
# Merge other provider information into one_to_one_results
|
# Merge other provider information into one_to_one_results
|
||||||
one_to_one_results["PROV_OTHER_TIN"] = other_tins if other_tins else []
|
one_to_one_results["PROV_OTHER_TIN"] = other_tins if other_tins else []
|
||||||
one_to_one_results["PROV_OTHER_NPI"] = other_npis if other_npis else []
|
one_to_one_results["PROV_OTHER_NPI"] = other_npis if other_npis else []
|
||||||
one_to_one_results["PROV_OTHER_NAME_FULL"] = other_names if other_names else []
|
one_to_one_results["PROV_OTHER_NAME_FULL"] = other_names if other_names else []
|
||||||
return one_to_one_results
|
return one_to_one_results
|
||||||
|
|
||||||
|
|
||||||
@@ -426,7 +428,7 @@ def deduplicate_providers(
|
|||||||
tuple(npis),
|
tuple(npis),
|
||||||
tuple(names),
|
tuple(names),
|
||||||
)
|
)
|
||||||
|
|
||||||
if key not in seen:
|
if key not in seen:
|
||||||
seen.add(key)
|
seen.add(key)
|
||||||
valid_providers.append(provider)
|
valid_providers.append(provider)
|
||||||
|
|||||||
@@ -50,7 +50,11 @@ def fill_na_mapping(answer_dicts):
|
|||||||
|
|
||||||
# Process each LOB value
|
# Process each LOB value
|
||||||
for lob_val in lob_items:
|
for lob_val in lob_items:
|
||||||
if isinstance(lob_val, str) and lob_val.strip() and lob_val.strip() != "N/A":
|
if (
|
||||||
|
isinstance(lob_val, str)
|
||||||
|
and lob_val.strip()
|
||||||
|
and lob_val.strip() != "N/A"
|
||||||
|
):
|
||||||
all_lob_values.add(lob_val.strip())
|
all_lob_values.add(lob_val.strip())
|
||||||
|
|
||||||
# Get AARETE_DERIVED_LOB from AARETE_DERIVED_PROGRAM crosswalk (always check if PROGRAM exists)
|
# Get AARETE_DERIVED_LOB from AARETE_DERIVED_PROGRAM crosswalk (always check if PROGRAM exists)
|
||||||
@@ -72,7 +76,9 @@ def fill_na_mapping(answer_dicts):
|
|||||||
elif isinstance(program_filled_value_list, str):
|
elif isinstance(program_filled_value_list, str):
|
||||||
# Handle pipe-delimited format (backward compatibility)
|
# Handle pipe-delimited format (backward compatibility)
|
||||||
if "|" in program_filled_value_list:
|
if "|" in program_filled_value_list:
|
||||||
program_lob_items = [v.strip() for v in program_filled_value_list.split("|")]
|
program_lob_items = [
|
||||||
|
v.strip() for v in program_filled_value_list.split("|")
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
program_lob_items = [program_filled_value_list]
|
program_lob_items = [program_filled_value_list]
|
||||||
else:
|
else:
|
||||||
@@ -80,7 +86,11 @@ def fill_na_mapping(answer_dicts):
|
|||||||
|
|
||||||
# Process each program LOB value
|
# Process each program LOB value
|
||||||
for program_lob_val in program_lob_items:
|
for program_lob_val in program_lob_items:
|
||||||
if isinstance(program_lob_val, str) and program_lob_val.strip() and program_lob_val.strip() != "N/A":
|
if (
|
||||||
|
isinstance(program_lob_val, str)
|
||||||
|
and program_lob_val.strip()
|
||||||
|
and program_lob_val.strip() != "N/A"
|
||||||
|
):
|
||||||
program_lob_values.add(program_lob_val.strip())
|
program_lob_values.add(program_lob_val.strip())
|
||||||
# Update all_lob_values after processing all program LOB values
|
# Update all_lob_values after processing all program LOB values
|
||||||
all_lob_values.update(program_lob_values)
|
all_lob_values.update(program_lob_values)
|
||||||
@@ -104,7 +114,9 @@ def fill_na_mapping(answer_dicts):
|
|||||||
elif isinstance(product_filled_value_list, str):
|
elif isinstance(product_filled_value_list, str):
|
||||||
# Handle pipe-delimited format (backward compatibility)
|
# Handle pipe-delimited format (backward compatibility)
|
||||||
if "|" in product_filled_value_list:
|
if "|" in product_filled_value_list:
|
||||||
product_lob_items = [v.strip() for v in product_filled_value_list.split("|")]
|
product_lob_items = [
|
||||||
|
v.strip() for v in product_filled_value_list.split("|")
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
product_lob_items = [product_filled_value_list]
|
product_lob_items = [product_filled_value_list]
|
||||||
else:
|
else:
|
||||||
@@ -112,7 +124,11 @@ def fill_na_mapping(answer_dicts):
|
|||||||
|
|
||||||
# Process each product LOB value
|
# Process each product LOB value
|
||||||
for product_lob_val in product_lob_items:
|
for product_lob_val in product_lob_items:
|
||||||
if isinstance(product_lob_val, str) and product_lob_val.strip() and product_lob_val.strip() != "N/A":
|
if (
|
||||||
|
isinstance(product_lob_val, str)
|
||||||
|
and product_lob_val.strip()
|
||||||
|
and product_lob_val.strip() != "N/A"
|
||||||
|
):
|
||||||
product_lob_values.add(product_lob_val.strip())
|
product_lob_values.add(product_lob_val.strip())
|
||||||
# Update all_lob_values after processing all product LOB values
|
# Update all_lob_values after processing all product LOB values
|
||||||
all_lob_values.update(product_lob_values)
|
all_lob_values.update(product_lob_values)
|
||||||
@@ -192,13 +208,17 @@ def get_crosswalk_fields(answer_dicts: list, constants: Constants):
|
|||||||
# Reverse mapping returns dict[str, list[str]], so .get() returns a list
|
# Reverse mapping returns dict[str, list[str]], so .get() returns a list
|
||||||
# We need to extend, not append, to avoid nested lists
|
# We need to extend, not append, to avoid nested lists
|
||||||
reverse_mapping = crosswalk.create_reverse_mapping()
|
reverse_mapping = crosswalk.create_reverse_mapping()
|
||||||
reverse_result = reverse_mapping.get(individual_from_field_value)
|
reverse_result = reverse_mapping.get(
|
||||||
|
individual_from_field_value
|
||||||
|
)
|
||||||
if reverse_result is not None:
|
if reverse_result is not None:
|
||||||
# reverse_result is a list, extend it
|
# reverse_result is a list, extend it
|
||||||
to_field_answer_list.extend(reverse_result)
|
to_field_answer_list.extend(reverse_result)
|
||||||
else:
|
else:
|
||||||
# If not found in reverse mapping, keep original value
|
# If not found in reverse mapping, keep original value
|
||||||
to_field_answer_list.append(individual_from_field_value)
|
to_field_answer_list.append(
|
||||||
|
individual_from_field_value
|
||||||
|
)
|
||||||
answer_dict[to_field_name] = to_field_answer_list
|
answer_dict[to_field_name] = to_field_answer_list
|
||||||
# update from field value to list if not already
|
# update from field value to list if not already
|
||||||
if not isinstance(from_field_value, list):
|
if not isinstance(from_field_value, list):
|
||||||
|
|||||||
@@ -890,7 +890,7 @@ Populate a JSON dictionary with the following fields:
|
|||||||
|
|
||||||
|
|
||||||
def FEE_SCHEDULE_BREAKOUT(
|
def FEE_SCHEDULE_BREAKOUT(
|
||||||
methodology:str, fee_schedule:str
|
methodology: str, fee_schedule: str
|
||||||
) -> Tuple[str, Callable[[str], dict]]:
|
) -> Tuple[str, Callable[[str], dict]]:
|
||||||
"""Returns ONLY dynamic content for fee schedule breakout.
|
"""Returns ONLY dynamic content for fee schedule breakout.
|
||||||
Call FEE_SCHEDULE_BREAKOUT_INSTRUCTION() separately for the cached instruction.
|
Call FEE_SCHEDULE_BREAKOUT_INSTRUCTION() separately for the cached instruction.
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import logging
|
|||||||
import src.utils.string_utils as string_utils
|
import src.utils.string_utils as string_utils
|
||||||
|
|
||||||
|
|
||||||
def apply_crosswalk(val: str | list, mapping: dict[str, str], default: str = "N/A") -> list:
|
def apply_crosswalk(
|
||||||
|
val: str | list, mapping: dict[str, str], default: str = "N/A"
|
||||||
|
) -> list:
|
||||||
"""Apply a crosswalk to a value
|
"""Apply a crosswalk to a value
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -253,4 +253,4 @@ def parse_json_dict_or_list(raw_llm_output: str) -> dict[str, Any] | list[Any]:
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"No valid JSON object found in LLM output. "
|
f"No valid JSON object found in LLM output. "
|
||||||
f"Output preview: {raw_llm_output[:200]}..."
|
f"Output preview: {raw_llm_output[:200]}..."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1101,7 +1101,9 @@ def format_or_preserve_tins(val: Any) -> Tuple[str, bool]:
|
|||||||
Tuple of (formatted_tins, has_invalid)
|
Tuple of (formatted_tins, has_invalid)
|
||||||
"""
|
"""
|
||||||
# Handle list/array values (e.g., from PROV_GROUP_TIN, PROV_OTHER_TIN)
|
# Handle list/array values (e.g., from PROV_GROUP_TIN, PROV_OTHER_TIN)
|
||||||
if isinstance(val, (list, tuple)) or (hasattr(val, '__iter__') and not isinstance(val, str)):
|
if isinstance(val, (list, tuple)) or (
|
||||||
|
hasattr(val, "__iter__") and not isinstance(val, str)
|
||||||
|
):
|
||||||
# Convert list to list of strings, filtering out empty values
|
# Convert list to list of strings, filtering out empty values
|
||||||
if len(val) == 0:
|
if len(val) == 0:
|
||||||
return ("", True)
|
return ("", True)
|
||||||
@@ -1121,10 +1123,10 @@ def format_or_preserve_tins(val: Any) -> Tuple[str, bool]:
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
# pd.isna() can't handle arrays, but we've already handled lists above
|
# pd.isna() can't handle arrays, but we've already handled lists above
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if val is None or str(val).strip() == "":
|
if val is None or str(val).strip() == "":
|
||||||
return ("", True)
|
return ("", True)
|
||||||
|
|
||||||
# Single value - convert to list for consistent processing
|
# Single value - convert to list for consistent processing
|
||||||
parts = [str(val).strip()]
|
parts = [str(val).strip()]
|
||||||
out, any_invalid = [], False
|
out, any_invalid = [], False
|
||||||
|
|||||||
Reference in New Issue
Block a user