Merged in bugfix/medicare-advantage-duals (pull request #674)

Bugfix/medicare advantage duals

* Add logging for debugging in crosswalk utilities and mapping functions

* Add debug logging for crosswalk processing and mapping in get_crosswalk_fields

* Add logging for dual LOB check prompts and responses in update_lob_for_duals

* Enhance dual LOB check logic to ensure PROGRAM and PRODUCT fields are empty before proceeding

* Update dual LOB check to use AARETE_DERIVED fields for validation

* Refactor logging in fill_na_mapping and get_crosswalk_fields for clarity and conciseness

* Remove debug logging from get_lob_relationship for cleaner output

* Remove debug logging from apply_crosswalk for cleaner execution


Approved-by: Katon Minhas
This commit is contained in:
Alex Galarce
2025-08-21 15:19:53 +00:00
parent bccd451f50
commit 7038a92d10
4 changed files with 42 additions and 17 deletions
@@ -1,3 +1,4 @@
import logging
import os
from collections import defaultdict
@@ -34,22 +35,25 @@ def fill_na_mapping(answer_dicts):
for answer_dict in answer_dicts:
# Fill AARETE_DERIVED_LOB from AARETE_DERIVED_PROGRAM
if string_utils.is_empty(answer_dict.get("AARETE_DERIVED_LOB")):
answer_dict["AARETE_DERIVED_LOB"] = fill_na_from_field(
filled_value = fill_na_from_field(
answer_dict,
"AARETE_DERIVED_LOB",
"AARETE_DERIVED_PROGRAM",
"constants/mappings/crosswalk_program_lob.json",
)
answer_dict["AARETE_DERIVED_LOB"] = filled_value
answer_dict["LOB_PROGRAM_RELATIONSHIP"] = "Exclusive"
# Fill AARETE_DERIVED_LOB from PRODUCT
if string_utils.is_empty(answer_dict.get("AARETE_DERIVED_LOB")):
answer_dict["AARETE_DERIVED_LOB"] = fill_na_from_field(
filled_value = fill_na_from_field(
answer_dict,
"AARETE_DERIVED_LOB",
"PRODUCT",
"constants/mappings/crosswalk_product_lob.json",
)
answer_dict["AARETE_DERIVED_LOB"] = filled_value
answer_dict["LOB_PRODUCT_RELATIONSHIP"] = "Exclusive"
return answer_dicts
@@ -75,7 +79,8 @@ def get_crosswalk_fields(answer_dicts):
from_field_value = answer_dict.get(from_field)
if from_field_value == "N/A": # Special handling for N/A
answer_dict[to_field.field_name] = "N/A"
to_field_value = "N/A"
answer_dict[to_field.field_name] = to_field_value
elif not string_utils.is_empty(from_field_value):
if from_field_value in crosswalk_mapping.values():
to_field_value = from_field_value
@@ -92,6 +97,7 @@ def get_crosswalk_fields(answer_dicts):
answer_dict[to_field.field_name] = to_field_value
just_mapped.append(to_field.field_name)
else: # Other is_empty cases besides N/A
answer_dict[to_field.field_name] = ""
to_field_value = ""
answer_dict[to_field.field_name] = to_field_value
return answer_dicts
@@ -1388,11 +1388,17 @@ def get_lob_relationship(answer_dicts, exhibit_dict, filename):
Helper function to prompt LLM for LOB relationship based on the field and exhibit text.
"""
programs = answer_dict.get(field)
dynamic_primary_values = f"LOB: {answer_dict.get("AARETE_DERIVED_LOB")}\nPROGRAM: {programs}"
dynamic_primary_values = (
f"LOB: {answer_dict.get("AARETE_DERIVED_LOB")}\nPROGRAM: {programs}"
)
prompt = prompt_templates.LOB_RELATIONSHIP(exhibit_text, dynamic_primary_values)
llm_answer_raw = llm_utils.invoke_claude(prompt, model_id="sonnet_latest", filename=filename)
llm_answer_final = string_utils.extract_text_from_delimiters(llm_answer_raw, Delimiter.PIPE)
llm_answer_raw = llm_utils.invoke_claude(
prompt, model_id="sonnet_latest", filename=filename
)
llm_answer_final = string_utils.extract_text_from_delimiters(
llm_answer_raw, Delimiter.PIPE
)
return llm_answer_final
for answer_dict in answer_dicts:
@@ -1401,13 +1407,13 @@ def get_lob_relationship(answer_dicts, exhibit_dict, filename):
exhibit_text = exhibit_dict.get(answer_dict.get("EXHIBIT_PAGE"))
# Process for program
if not string_utils.is_empty(answer_dict.get("AARETE_DERIVED_PROGRAM")):
answer_dict["LOB_PROGRAM_RELATIONSHIP"] = prompt_lob_relationship("AARETE_DERIVED_PROGRAM", exhibit_text)
answer_dict["LOB_PROGRAM_RELATIONSHIP"] = prompt_lob_relationship(
"AARETE_DERIVED_PROGRAM", exhibit_text
)
# Process for Product
if not string_utils.is_empty(answer_dict.get("AARETE_DERIVED_PRODUCT")):
answer_dict["LOB_PRODUCT_RELATIONSHIP"] = prompt_lob_relationship("AARETE_DERIVED_PRODUCT", exhibit_text)
answer_dict["LOB_PRODUCT_RELATIONSHIP"] = prompt_lob_relationship(
"AARETE_DERIVED_PRODUCT", exhibit_text
)
return answer_dicts
@@ -1,5 +1,6 @@
import ast
import hashlib
import logging
import os
import re
from datetime import datetime
@@ -581,11 +582,19 @@ def update_lob_for_duals(answer_dicts):
# Get SERVICE_TERM value
service_term = answer_dict.get("SERVICE_TERM", "").lower()
# Check if the SERVICE_TERM contains "Medicare" and "Medicaid" - case insensitive
if "medicare" in service_term and "medicaid" in service_term:
# AND check that there's no explicit PROGRAM or PRODUCT that would override this logic
if (
"medicare" in service_term
and "medicaid" in service_term
and string_utils.is_empty(answer_dict.get("AARETE_DERIVED_PROGRAM"))
and string_utils.is_empty(answer_dict.get("AARETE_DERIVED_PRODUCT"))
):
prompt = prompt_templates.DUAL_LOB_CHECK(service_term)
logging.debug(f"Prompt for dual LOB check: {prompt}")
claude_answer_raw = llm_utils.invoke_claude(
prompt, model_id="legacy_sonnet", filename="", max_tokens=200
)
logging.debug(f"Claude response for dual LOB check: {claude_answer_raw}")
claude_answer_extracted = string_utils.extract_text_from_delimiters(
claude_answer_raw, Delimiter.PIPE
)
+7 -3
View File
@@ -1,4 +1,5 @@
import ast
import logging
import src.utils.string_utils as string_utils
@@ -61,8 +62,11 @@ def apply_crosswalk(val: str, mapping: dict[str, str], default: str = "") -> str
final_mapped = [v for v in mapped_values if v and not string_utils.is_empty(v)]
if len(final_mapped) == 1:
return final_mapped[0]
result = final_mapped[0]
return result
elif len(final_mapped) > 1:
return "|".join(final_mapped)
result = "|".join(final_mapped)
return result
else:
return default if default else original_val
result = default if default else original_val
return result