From c82088b8b1fa19850cc4038d2a1f03bafef14e91 Mon Sep 17 00:00:00 2001 From: Katon Minhas Date: Thu, 30 Jan 2025 23:00:05 +0000 Subject: [PATCH] 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 --- .../src/constants/investment_values.py | 17 ++++++ .../src/investment/one_to_n_funcs.py | 57 ++++++++++++++----- .../src/prompts/investment_prompts.py | 22 ++++++- 3 files changed, 80 insertions(+), 16 deletions(-) diff --git a/fieldExtraction/src/constants/investment_values.py b/fieldExtraction/src/constants/investment_values.py index a02d8b6..a90976b 100644 --- a/fieldExtraction/src/constants/investment_values.py +++ b/fieldExtraction/src/constants/investment_values.py @@ -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.", +} + diff --git a/fieldExtraction/src/investment/one_to_n_funcs.py b/fieldExtraction/src/investment/one_to_n_funcs.py index d9e17bd..29189b6 100644 --- a/fieldExtraction/src/investment/one_to_n_funcs.py +++ b/fieldExtraction/src/investment/one_to_n_funcs.py @@ -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 diff --git a/fieldExtraction/src/prompts/investment_prompts.py b/fieldExtraction/src/prompts/investment_prompts.py index 1aef5d0..29ada4f 100644 --- a/fieldExtraction/src/prompts/investment_prompts.py +++ b/fieldExtraction/src/prompts/investment_prompts.py @@ -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']}