Merged in feature/carveout_ind (pull request #372)

Initialized CONTRACT_CARVEOUT_IND and CONTRACT_CARVEOUT_CD

* add carveout list

* Write carveout ind prompt and integrated

* Remove None carveouts

* Add TODO for carveout check

* Fill out remaining carveouts

* Merged main into feature/carveout_ind


Approved-by: Alex Galarce
This commit is contained in:
Katon Minhas
2025-01-30 23:00:05 +00:00
parent baba7b6552
commit c82088b8b1
3 changed files with 80 additions and 16 deletions
@@ -65,3 +65,20 @@ VALID_REIMBURSEMENT_METHOD = [
"DOFR"
]
VALID_CARVEOUTS = {
"UNLISTED_CODE" : "Describes payments for codes that are not listed on a fee schedule",
"NEVER_EVENT" : "Indicates that Never Events (e.g. sepsis acquired while inpatient) will not be covered.",
"EXPERIMENTAL_INVESTIGATIONAL" : "Describes payments for experimental and investigational procedures.",
"NOT_MEDICALLY_NECESSARY" : "Describes payments for procedures deemed not medically necessary (or medically unnecessary)",
"GLOBAL_SERVICES" : None,
"BUNDLED_CODES" : "Indicates that payment for bundles of codes shall be at a reduced rate.",
"MULTIPLE_PROCEDURE_REDUCTIONS" : 'Describes payments for multiple procedures or surgeries performed on the same day as each other.',
"MIDLEVELS" : None,
"TECHNICAL_COMPONENT" : "If Modifier TC (technical component) is present.",
"PROFESSIONAL_COMPONENT" : "If Modifier 26 (professional component) is present.",
"DIAGNOSTIC_PROCEDURES" : "Describes payments for diagnostic procedures.",
"PREADMISSION" : None,
"POST_DISCHARGE" : None,
"READMISSIONS" : "Describes payment for subsequent admission with the same diagnostic category of codes as the initial admission.",
}
@@ -143,27 +143,56 @@ def get_reimbursement_primary(reimbursement_level_fields, exhibit_text, filename
llm_answer_final = string_utils.universal_json_load(llm_answer_raw)
return llm_answer_final
def get_carveout_ind(reimbursement_primary_answers, filename):
already_seen = {}
carveout_answers = []
for answer_dict in reimbursement_primary_answers:
contract_service_cd, contract_reimbursement_method = answer_dict.get("CONTRACT_SERVICE_CD_OR_DESC"), answer_dict.get("CONTRACT_REIMBURSEMENT_METHOD")
if contract_service_cd and contract_reimbursement_method:
llm_answer_final = already_seen.setdefault(
contract_reimbursement_method,
string_utils.extract_text_from_delimiters(
llm_utils.invoke_claude(
investment_prompts.CARVEOUT_CHECK(contract_service_cd, contract_reimbursement_method),
config.MODEL_ID_CLAUDE35_SONNET,
filename
),
Delimiter.PIPE)
)
answer_dict['CONTRACT_CARVEOUT_CD'] = llm_answer_final
answer_dict['CONTRACT_CARVEOUT_IND'] = 'N' if 'N/A' in llm_answer_final else 'Y'
carveout_answers.append(answer_dict)
return carveout_answers
def get_methodology_breakout(reimbursement_primary_answers, filename):
already_seen = {}
methodology_breakout_questions = FieldSet(file_path=config.FIELD_JSON_PATH, field_type="methodology_breakout").get_prompt_dict()
methodology_breakout_answers = []
# TODO DAIP2-86: Add check if there is a CARVEOUT that would require a different methodology breakout
for answer_dict in reimbursement_primary_answers:
if "CONTRACT_REIMBURSEMENT_METHOD" in answer_dict.keys():
contract_reimbursement_method = answer_dict["CONTRACT_REIMBURSEMENT_METHOD"]
if contract_reimbursement_method not in already_seen.keys():
prompt = investment_prompts.REIMBURSEMENT_METHODOLOGY_BREAKOUT(answer_dict["CONTRACT_REIMBURSEMENT_METHOD"], methodology_breakout_questions)
llm_answer_raw = llm_utils.invoke_claude(prompt, config.MODEL_ID_CLAUDE35_SONNET, filename)
llm_answer_final = string_utils.universal_json_load(llm_answer_raw)
already_seen[contract_reimbursement_method] = llm_answer_final
else:
llm_answer_final = already_seen[contract_reimbursement_method]
for breakout_dict in llm_answer_final:
breakout_answer = {**answer_dict, **breakout_dict}
methodology_breakout_answers.append(breakout_answer)
contract_reimbursement_method = answer_dict.get("CONTRACT_REIMBURSEMENT_METHOD")
if contract_reimbursement_method:
llm_answer_final = already_seen.setdefault(
contract_reimbursement_method,
string_utils.universal_json_load(
llm_utils.invoke_claude(
investment_prompts.REIMBURSEMENT_METHODOLOGY_BREAKOUT(contract_reimbursement_method, methodology_breakout_questions),
config.MODEL_ID_CLAUDE35_SONNET,
filename
)
)
)
for breakout_dict in llm_answer_final:
methodology_breakout_answers.append({**answer_dict, **breakout_dict})
return methodology_breakout_answers
def reimbursement_level(exhibit_text, filename, reimbursement_level_fields):
"""
Processes reimbursement-level fields.
@@ -179,6 +208,8 @@ def reimbursement_level(exhibit_text, filename, reimbursement_level_fields):
"""
reimbursement_primary_answers = get_reimbursement_primary(reimbursement_level_fields, exhibit_text, filename) # Returns list of dicts
carveout_answers = get_carveout_ind(reimbursement_primary_answers, filename)
methodology_breakout_answers = get_methodology_breakout(reimbursement_primary_answers, filename)
return methodology_breakout_answers
@@ -1,4 +1,4 @@
from src.constants.investment_values import VALID_LOBS, VALID_NETWORKS, VALID_PROGRAMS, VALID_CONTRACT_CLAIM_TYPE, VALID_REIMBURSEMENT_METHOD
from src.constants.investment_values import VALID_LOBS, VALID_NETWORKS, VALID_PROGRAMS, VALID_CONTRACT_CLAIM_TYPE, VALID_REIMBURSEMENT_METHOD, VALID_CARVEOUTS
import src.constants.valid as valid
#####################################################################################
@@ -318,13 +318,29 @@ The term length appears in two places but I'm selecting...
"""
def CARVEOUT_CHECK(service, methodology):
carveout_definitions = {carveout : definition for carveout, definition in VALID_CARVEOUTS.items() if definition} # Get only not None
return f"""Examine the Service and Methodology combination below and determine if the terms fall under one of the following Carveout definitions:
Here are the Carveouts, with their definitions:
{carveout_definitions}
Here is the Service and Methodology:
Service: {service}
Methodology: {methodology}
Determine if the Service and Methodology match any of the Carveout descriptions.
If so, write the name of the Carveout. If they do not, simply write N/A. The answer will be somewhat explicitly written - do not attempt to force an answer if it is not the obvious answer.
Write your reasoning below, but enclose your final answer in |pipes|.
"""
#####################################################################################
####################################### LEGACY ######################################
#####################################################################################
def BOTTOM_UP_METHODOLOGY_BREAKOUT(d):
return f"""Analyze the full methodology text listed below:
{d['FULL_METHODOLOGY']}