diff --git a/.gitignore b/.gitignore index b8e278a..e71e4b4 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ streamlit/venv *.PDF *.TXT *.txt +*:Zone.Identifier *.tfplan *.pem textfiles/ diff --git a/fieldExtraction/src/config.py b/fieldExtraction/src/config.py index 68e7a80..b1732ea 100644 --- a/fieldExtraction/src/config.py +++ b/fieldExtraction/src/config.py @@ -217,7 +217,7 @@ if RUN_MODE == "local": elif RUN_MODE == "ec2": # Bedrock - config = Config(read_timeout=2000, max_pool_connections=60) + config = Config(read_timeout=4000, max_pool_connections=60) # Doubled read_timeout to 4000 BEDROCK_RUNTIME = boto3.client( service_name="bedrock-runtime", region_name="us-east-1", config=config ) diff --git a/fieldExtraction/src/investment/file_processing.py b/fieldExtraction/src/investment/file_processing.py index a4ad588..c176217 100644 --- a/fieldExtraction/src/investment/file_processing.py +++ b/fieldExtraction/src/investment/file_processing.py @@ -138,48 +138,57 @@ def run_one_to_one_prompts(filename, contract_text, text_dict, top_sheet_dict, d def run_one_to_n_prompts(filename, text_dict, exhibit_pages, exhibit_chunk_mapping, all_dataset): + + ################## IDENTIFY COMPENSATION EXHIBITS ################## + # Get all exhibit headers first + all_exhibit_headers = {page: one_to_n_funcs.get_exhibit_header(string_utils.get_exhibit_chunk(text_dict, exhibit_chunk_mapping, page), filename) + for page in exhibit_pages} + + # Then identify the compensation exhibits + reimbursement_exhibits = one_to_n_funcs.identify_reimbursement_exhibits(all_exhibit_headers, filename) + # print("Reimbursement Exhibits: ", reimbursement_exhibits) # Debugging line + ################## RUN PROMPTS ################## one_to_n_results = [] - for exhibit_page in exhibit_pages: + for exhibit_page in reimbursement_exhibits: exhibit_chunk = string_utils.get_exhibit_chunk(text_dict, exhibit_chunk_mapping, exhibit_page) - if string_utils.contains_reimbursement(exhibit_chunk): - ################## INITIALIZE FIELDS ################## - reimbursement_level_fields = FieldSet(relationship="one_to_n", field_type="reimbursement_level", file_path=FIELD_JSON_PATH) + ################## INITIALIZE FIELDS ################## + reimbursement_level_fields = FieldSet(relationship="one_to_n", field_type="reimbursement_level", file_path=FIELD_JSON_PATH) - ################## GET EXHIBIT HEADER ################## - exhibit_header = one_to_n_funcs.get_exhibit_header(exhibit_chunk, filename) + ################## GET EXHIBIT HEADER ################## + exhibit_header = one_to_n_funcs.get_exhibit_header(exhibit_chunk, filename) - ################## GET REIMBURSEMENT TIN/NPI ################## - tin_npi_answers, reimbursement_level_fields = reimbursement_tin_npi(exhibit_chunk, reimbursement_level_fields, filename) + ################## GET REIMBURSEMENT TIN/NPI ################## + tin_npi_answers, reimbursement_level_fields = reimbursement_tin_npi(exhibit_chunk, reimbursement_level_fields, filename) - ################## GET EXHIBIT-LEVEL ANSWERS ################## - exhibit_level_answers = one_to_n_funcs.get_exhibit_level_answers(exhibit_chunk, filename) - exhibit_level_answers['EXHIBIT_PAGE'] = exhibit_page - exhibit_level_answers['EXHIBIT_TITLE'] = exhibit_header + ################## GET EXHIBIT-LEVEL ANSWERS ################## + exhibit_level_answers = one_to_n_funcs.get_exhibit_level_answers(exhibit_chunk, filename) + exhibit_level_answers['EXHIBIT_PAGE'] = exhibit_page + exhibit_level_answers['EXHIBIT_TITLE'] = exhibit_header - ################## GET DYNAMIC-PRIMARY ANSWERS ################## - dynamic_to_exhibit_level_answers, dynamic_to_reimbursement_level_fields = dynamic_funcs.get_dynamic_answers(exhibit_chunk, - exhibit_header, - filename, - FieldSet(relationship="one_to_n", field_type="dynamic", file_path=config.FIELD_JSON_PATH)) - exhibit_level_answers.update(dynamic_to_exhibit_level_answers) - reimbursement_level_fields.combine(dynamic_to_reimbursement_level_fields, inplace=True) - - ################## GET DYNAMIC-CODE ANSWERS ################## - code_to_exhibit_level_answers, code_to_reimbursement_level_fields = dynamic_funcs.get_dynamic_answers(exhibit_chunk, - exhibit_header, - filename, - FieldSet(relationship="one_to_n", field_type="dynamic_code", file_path=config.FIELD_JSON_PATH)) - exhibit_level_answers.update(code_to_exhibit_level_answers) - reimbursement_level_fields.combine(code_to_reimbursement_level_fields, inplace=True) + ################## GET DYNAMIC-PRIMARY ANSWERS ################## + dynamic_to_exhibit_level_answers, dynamic_to_reimbursement_level_fields = dynamic_funcs.get_dynamic_answers(exhibit_chunk, + exhibit_header, + filename, + FieldSet(relationship="one_to_n", field_type="dynamic", file_path=config.FIELD_JSON_PATH)) + exhibit_level_answers.update(dynamic_to_exhibit_level_answers) + reimbursement_level_fields.combine(dynamic_to_reimbursement_level_fields, inplace=True) + + ################## GET DYNAMIC-CODE ANSWERS ################## + code_to_exhibit_level_answers, code_to_reimbursement_level_fields = dynamic_funcs.get_dynamic_answers(exhibit_chunk, + exhibit_header, + filename, + FieldSet(relationship="one_to_n", field_type="dynamic_code", file_path=config.FIELD_JSON_PATH)) + exhibit_level_answers.update(code_to_exhibit_level_answers) + reimbursement_level_fields.combine(code_to_reimbursement_level_fields, inplace=True) - ################## GET REIMBURSEMENT-LEVEL ANSWERS (INCLUDING DYNAMIC) ################## - reimbursement_level_answers = reimbursement_level(exhibit_chunk, filename, reimbursement_level_fields, all_dataset, exhibit_page) # Return list of dictionaries + ################## GET REIMBURSEMENT-LEVEL ANSWERS (INCLUDING DYNAMIC) ################## + reimbursement_level_answers = reimbursement_level(exhibit_chunk, filename, reimbursement_level_fields, all_dataset, exhibit_page) # Return list of dictionaries - ################# COMBINE ANSWERS ################## - full_answer_dict = combine_one_to_n_answers(exhibit_level_answers, reimbursement_level_answers, tin_npi_answers) - one_to_n_results += full_answer_dict + ################# COMBINE ANSWERS ################## + full_answer_dict = combine_one_to_n_answers(exhibit_level_answers, reimbursement_level_answers, tin_npi_answers) + one_to_n_results += full_answer_dict ################## Crosswalk Fields ################## one_to_n_results = aarete_derived.get_crosswalk_fields(one_to_n_results) diff --git a/fieldExtraction/src/investment/one_to_n_funcs.py b/fieldExtraction/src/investment/one_to_n_funcs.py index a11b61c..7feb9e2 100644 --- a/fieldExtraction/src/investment/one_to_n_funcs.py +++ b/fieldExtraction/src/investment/one_to_n_funcs.py @@ -13,6 +13,8 @@ from src.enums.delimiters import Delimiter from src.prompts.investment_prompts import Field, FieldSet, EXHIBIT_LEVEL from src.config import FIELD_JSON_PATH +import yaml # This is for experimental YAML parsing + METHODOLOGY_BREAKOUT_QUESTIONS = FieldSet( file_path=config.FIELD_JSON_PATH, field_type="methodology_breakout" ).print_prompt_dict() @@ -55,13 +57,55 @@ def get_exhibit_header(exhibit_chunk, filename): ) return llm_answer_final +def identify_reimbursement_exhibits(exhibit_headers: dict, filename: str) -> list[str]: + """Use LLM to identify which exhibits likely contain reimbursement terms. + + Args: + exhibit_headers (dict): Dictionary of exhibit headers to analyze, keyed by + string page number, valued by the exhibit header text. + filename (str): The name of the file being processed. + + Returns: + list[str]: List of page numbers (as strings) that likely contain reimbursement terms. + """ + # print(exhibit_headers) # Debugging line + # print(type(exhibit_headers)) # Debugging line + prompt = investment_prompts.IDENTIFY_REIMBURSEMENT_EXHIBITS_PROMPT(exhibit_headers, yaml_output=True) # Trying YAML output + # print(prompt) # Debugging line + llm_answer_raw = llm_utils.invoke_claude( + prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, 2000 + ) + + yaml_content = string_utils.extract_text_from_delimiters( + llm_answer_raw, Delimiter.TRIPLE_BACKTICK) + + # Check if the first line is just `yaml` and if so remove it + lines = yaml_content.strip().split('\n') + if lines[0].strip().lower() == 'yaml': + yaml_content = '\n'.join(lines[1:]) + + # print(llm_answer_raw) # Debugging line + # print("Extracted YAML content:\n", yaml_content) # Debugging line + try: + # Parse the YAML output into a dictionary + exhibits_dict = yaml.safe_load(yaml_content) + # YAML parsing has built-in type conversion for "NO" -> False, "YES" -> True + # So we'll check for boolean values and strings (just in case) + return [str(page) for page, value in exhibits_dict.items() # Convert back to string + if value is True or (isinstance(value, str) and value.upper() == "YES")] + except yaml.YAMLError as e: + print(f"Error parsing YAML: {e}") + print(f"Raw LLM output: {llm_answer_raw}") + return list(exhibit_headers.keys()) # Return all keys of the exhibit headers if parsing fails def get_reimbursement_primary(reimbursement_level_fields, exhibit_text, filename): field_prompts = reimbursement_level_fields.get_prompt_dict() prompt = investment_prompts.REIMBURSEMENT_LEVEL_PRIMARY(exhibit_text, field_prompts) + # print(prompt) # Debugging line llm_answer_raw = llm_utils.invoke_claude( prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, max_tokens=25000 ) + # print(llm_answer_raw) # Debugging line llm_answer_final = string_utils.universal_json_load(llm_answer_raw) return llm_answer_final diff --git a/fieldExtraction/src/prompts/investment_prompts.py b/fieldExtraction/src/prompts/investment_prompts.py index e99e7f5..62f90d9 100644 --- a/fieldExtraction/src/prompts/investment_prompts.py +++ b/fieldExtraction/src/prompts/investment_prompts.py @@ -309,6 +309,22 @@ Before returning any output, ensure that all instructions for inclusion were fol Enclose your final answer in |pipes|. """ +def IDENTIFY_REIMBURSEMENT_EXHIBITS_PROMPT(exhibit_headers: dict, yaml_output: bool = False): + formatted_headers = [f"Page {page}: {header}" for page, header in exhibit_headers.items()] + return f"""Review these exhibit headers and identify which ones are likely to contain reimbursement methodologies, payment terms, or compensation schedules. + +Look for keywords like "compensation", "payment", "rate", "reimbursement", "fee schedule", "pricing", or "financial" in the headers. + +For each header, return 'YES' if it's likely to contain reimbursement information, or 'NO' if it's not. + +Headers to review: +{"\n".join(formatted_headers)} + +Return your answer as a {"JSON object" if not yaml_output else "YAML list"} where keys are PAGE NUMBERS and values are 'YES' or 'NO'. +Feel free to explain your reasoning, but place your final {"JSON" if not yaml_output else "YAML"} output between triple backticks. +If you're uncertain about a header, err on the side of inclusion (YES). +""" + def EXHIBIT_LEVEL(context, fields): return f"""Extract attributes from a section of a contract.