Merged in bugfix/dynamic-primary-issues (pull request #669)

Bugfix/dynamic primary issues

* add LOB_RELATIONSHIP prompt

* Add test script

* Integrate prompt

* Update one-to-one passing rules

* Merged main into bugfix/dynamic-primary-issues

* Remove promt

* replace quotes


Approved-by: Alex Galarce
This commit is contained in:
Katon Minhas
2025-08-19 16:55:09 +00:00
parent b9edfed44b
commit f6cefc62e3
5 changed files with 90 additions and 13 deletions
@@ -40,6 +40,7 @@ def fill_na_mapping(answer_dicts):
"AARETE_DERIVED_PROGRAM",
"constants/mappings/crosswalk_program_lob.json",
)
answer_dict["LOB_RELATIONSHIP"] = "Exclusive"
# Fill AARETE_DERIVED_LOB from PRODUCT
if string_utils.is_empty(answer_dict.get("AARETE_DERIVED_LOB")):
@@ -49,6 +50,7 @@ def fill_na_mapping(answer_dicts):
"PRODUCT",
"constants/mappings/crosswalk_product_lob.json",
)
answer_dict["LOB_RELATIONSHIP"] = "Exclusive"
return answer_dicts
+16 -13
View File
@@ -26,20 +26,23 @@ def prompt_dynamic(text, field_prompts, filename):
return llm_answer_final
def add_full_context_field(one_to_one_fields, field_to_add):
def add_full_context_field(one_to_one_fields, field_to_add, answer_dicts):
field_to_add.field_type = "full_context"
field_to_add.relationship = "one_to_one"
if field_to_add.allow_na:
field_to_add.prompt = (
field_to_add.prompt
+ " Only answer if the answer applies to the ENTIRE contract. Otherwise, answer N/A."
)
else:
field_to_add.prompt = (
field_to_add.prompt
+ " Do NOT leave this field N/A. Choose the most appropriate answer based on the context."
)
# Special Handling for Program and Product:
# If there are multiple non-empty AARETE_DERIVED_LOB values in the contract, don't ask for Program or Product full context
if field_to_add.field_name in ["PROGRAM", "PRODUCT"]:
lob_values = [answer_dict.get("AARETE_DERIVED_LOB", "") for answer_dict in answer_dicts]
non_empty_lobs = [lob for lob in lob_values if not string_utils.is_empty(lob)]
if len(non_empty_lobs) > 1:
return one_to_one_fields
field_to_add.prompt = (
field_to_add.prompt
+ prompt_templates.FULL_CONTEXT_ADDITIONAL_INSTRUCTIONS(field_to_add.allow_na)
)
one_to_one_fields.add_field(field_to_add)
return one_to_one_fields
@@ -94,7 +97,7 @@ def get_dynamic_one_to_one_fields(answer_dicts):
field_name=field.base_field,
)
one_to_one_fields = add_full_context_field(
one_to_one_fields, field_to_add
one_to_one_fields, field_to_add, answer_dicts
)
continue
@@ -113,7 +116,7 @@ def get_dynamic_one_to_one_fields(answer_dicts):
file_path=config.FIELD_JSON_PATH,
field_name=field.base_field,
)
one_to_one_fields = add_full_context_field(one_to_one_fields, field_to_add)
one_to_one_fields = add_full_context_field(one_to_one_fields, field_to_add, answer_dicts)
return one_to_one_fields
@@ -177,6 +177,9 @@ def run_one_to_n_prompts(filename, exhibit_dict, all_exhibit_headers, constants)
################## Crosswalk Fields ##################
one_to_n_results = aarete_derived.get_crosswalk_fields(one_to_n_results)
################## Determine LOB Relationship ##################
one_to_n_results = one_to_n_funcs.get_lob_relationship(one_to_n_results, exhibit_dict, filename)
################## Fill NA Mapping ##################
one_to_n_results = aarete_derived.fill_na_mapping(one_to_n_results)
@@ -1367,3 +1367,28 @@ def outlier_terms_breakout(outlier_term, filename: str) -> dict:
llm_response = llm_utils.invoke_claude(prompt, "sonnet_latest", filename)
outlier_answers = string_utils.universal_json_load(llm_response) or {}
return outlier_answers
def get_lob_relationship(answer_dicts, exhibit_dict, filename):
for answer_dict in answer_dicts:
# Process relationship if LOB is present and either Program or Product is present
if not string_utils.is_empty(answer_dict.get("AARETE_DERIVED_LOB")) and (not string_utils.is_empty(answer_dict.get("AARETE_DERIVED_PROGRAM")) or not string_utils.is_empty(answer_dict.get("AARETE_DERIVED_PRODUCT"))):
# Get values
exhibit_text = exhibit_dict.get(answer_dict.get("EXHIBIT_PAGE"))
programs_and_products = answer_dict.get("AARETE_DERIVED_PRODUCT", "") + "|" + answer_dict.get("AARETE_DERIVED_PROGRAM", "")
dynamic_primary_values = f"LOB: {answer_dict.get("AARETE_DERIVED_LOB")}\nPROGRAM: {programs_and_products}"
# Prompt LLM
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)
# Assign answer
answer_dict["LOB_RELATIONSHIP"] = llm_answer_final
return answer_dicts
@@ -1091,3 +1091,47 @@ Return your answers in valid JSON format using the exact field names from the qu
**Important:** Base your answers ONLY on the outlier terms provided. Do not make assumptions or add information not present in the contract language.
"""
def LOB_RELATIONSHIP(exhibit_text, dynamic_primary_values):
return f"""You will be presented with an exhibit from a Payer-Provider contract.
[BACKGROUND]
- In healthcare, there are generally multiple Programs for any given LOB.
- For example, under the Medicaid LOB, there can be the Programs CHIP, STAR, Medi-Cal, etc.
- The Exhibit shown contains a number of reimbursement terms, which apply to different LOB and Programs.
It was previously identified that the Exhibit contains the following LOB and Programs:
{dynamic_primary_values}
[INSTRUCTIONS]
- Determine if the Exhibit presents an "Inclusive" relationship or "Exclusive" relationship between LOB and Program.
- Inclusive Relationship: When the contract refers to an LOB, including (but not limited to) the programs in the list.
- Some examples of language indicating an inclusive relationship are: "Medicaid and CHIP", "Medicare, including Medicare Advantage", and "Duals, including all special needs programs"
- Exclusive Relationship: When the contract refers to an LOB and Programs, but the information applies to JUST those specific programs.
- Some examples of language indicating an exlusive relationship are "Medicaid: CHIP", "Medicare advantage only", and "Duals - only the following SNP programs"
- If the programs are represented with different sections of the Exhibit under the same higher-level LOB section, this is indicative of an Exclusive relationship.
- Closely read the text, then write either |Inclusive| or |Exclusive| to indicate the nature of the relationship.
[CONTEXT]
Here is the Exhibit to be analyzed:
{exhibit_text.replace('"', "'")}
[OUTPUT]
Briefly explain your reasoning, then put your final answer in |pipes|.
"""
def FULL_CONTEXT_ADDITIONAL_INSTRUCTIONS(allow_na):
if allow_na:
return " Only provide an answer if that answer clearly applies to the ENTIRE contract. Otherwise, answer N/A."
else:
return " Do NOT leave this field N/A. Choose the most appropriate answer based on the context."