From 3ca9d5b084119fc1bd8eac28c86a9dfcbd5fbd0f Mon Sep 17 00:00:00 2001 From: Katon Minhas Date: Thu, 9 Oct 2025 18:41:36 +0000 Subject: [PATCH] Merged in feature/update-exhibit-linking (pull request #732) Feature/update exhibit linking * Update exhibit linking * prompt_calls refactor * remove test Approved-by: Karan Desai --- .../src/investment/preprocessing_funcs.py | 28 ++++--------------- .../src/investment/prompt_calls.py | 22 +++++++++++++++ .../src/prompts/prompt_templates.py | 10 ++++--- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/fieldExtraction/src/investment/preprocessing_funcs.py b/fieldExtraction/src/investment/preprocessing_funcs.py index fbe681c..4e682fe 100644 --- a/fieldExtraction/src/investment/preprocessing_funcs.py +++ b/fieldExtraction/src/investment/preprocessing_funcs.py @@ -1,12 +1,8 @@ import logging import re -import src.utils.llm_utils as llm_utils import src.utils.string_utils as string_utils -from constants.delimiters import Delimiter -from src.investment import table_funcs -from src.prompts import prompt_templates - +from src.investment import table_funcs, prompt_calls def remove_page_indicators(contract_text: str) -> str: """Clean textract output by removing page number indicators in the form of "Page X of Y" @@ -132,20 +128,12 @@ def get_exhibit_pages( first_page = False else: if "." not in page_num or ".0" in page_num: - prompt = prompt_templates.EXHIBIT_HEADER( - page_content[0:400], EXHIBIT_HEADER_MARKERS - ) - claude_answer_raw = llm_utils.invoke_claude( - prompt, "sonnet_latest", filename, max_tokens=300 - ) - claude_answer_extracted = string_utils.extract_text_from_delimiters( - claude_answer_raw, Delimiter.PIPE - ) - if "N/A" in claude_answer_extracted: + exhibit_header = prompt_calls.prompt_exhibit_header(page_content, EXHIBIT_HEADER_MARKERS, filename) + if "N/A" in exhibit_header: is_exhibit = False else: exhibit_pages.append(page_num) - exhibit_header_dict[page_num] = claude_answer_extracted + exhibit_header_dict[page_num] = exhibit_header is_exhibit = True elif is_exhibit == True: exhibit_pages.append(page_num) @@ -212,13 +200,7 @@ def link_exhibit_pages( exhibit_is_different = "Y" previous_header = page_header else: - prompt = prompt_templates.EXHIBIT_LINKAGE(page_header, previous_header) - claude_answer_raw = llm_utils.invoke_claude( - prompt, "legacy_sonnet", filename - ) - exhibit_is_different = string_utils.extract_text_from_delimiters( - claude_answer_raw, Delimiter.PIPE - ) + exhibit_is_different = prompt_calls.prompt_exhibit_linkage(page_header, previous_header, filename) previous_header = page_header # If the prompt has identified that the current exhibit is meaningfully different than the previous diff --git a/fieldExtraction/src/investment/prompt_calls.py b/fieldExtraction/src/investment/prompt_calls.py index 0614a24..32f548f 100644 --- a/fieldExtraction/src/investment/prompt_calls.py +++ b/fieldExtraction/src/investment/prompt_calls.py @@ -477,3 +477,25 @@ def prompt_dynamic(text, field_prompts, filename): logging.debug(f"Dynamic answer for {filename}: {llm_answer_raw}") llm_answer_final = string_utils.universal_json_load(llm_answer_raw) return llm_answer_final + +def prompt_exhibit_linkage(page_header, previous_header, filename): + prompt = prompt_templates.EXHIBIT_LINKAGE(page_header, previous_header) + claude_answer_raw = llm_utils.invoke_claude( + prompt, "legacy_sonnet", filename + ) + exhibit_is_different = string_utils.extract_text_from_delimiters( + claude_answer_raw, Delimiter.PIPE + ) + return exhibit_is_different + +def prompt_exhibit_header(page_content, EXHIBIT_HEADER_MARKERS, filename): + prompt = prompt_templates.EXHIBIT_HEADER( + page_content[0:400], EXHIBIT_HEADER_MARKERS + ) + llm_answer_raw = llm_utils.invoke_claude( + prompt, "sonnet_latest", filename, max_tokens=300 + ) + llm_answer_extracted = string_utils.extract_text_from_delimiters( + llm_answer_raw, Delimiter.PIPE + ) + return llm_answer_extracted \ No newline at end of file diff --git a/fieldExtraction/src/prompts/prompt_templates.py b/fieldExtraction/src/prompts/prompt_templates.py index 09242bb..922b7ec 100644 --- a/fieldExtraction/src/prompts/prompt_templates.py +++ b/fieldExtraction/src/prompts/prompt_templates.py @@ -818,19 +818,20 @@ def EXHIBIT_HEADER(context, EXHIBIT_HEADER_MARKERS): Capture the complete hierarchy of document identifiers present in the text. The following keywords and phrases should be considered as exhibit/attachment headers: * {'\n* '.join(EXHIBIT_HEADER_MARKERS)} -Include the following when present: +Rules for Inclusion: +* Include ALL text that appears to be part of the header * Full header names and numbers/letters (e.g., "Attachment C: Commercial-Exchange") * Any other relevant subtitles and identifiers * Any words in the header that may specify what type of information is contained, such as "Compensation Schedule" or "Definitions". * Provider/Entity names when listed with exhibit information +* Keep exact capitalization and formatting as shown in document +* Include any information referring to the current exhibit's relationship with another exhibit (e.g. "Exhibit 1: Provider Roster (see Exhibit 2 for Compensation Schedule)") -- Rules for inclusion: +- Rules for Exclusion: * Do NOT abbreviate or summarize any part of the exhibit names * Do NOT include page numbers * Do NOT cherry-pick only certain parts - capture the complete hierarchy * Do NOT include docusign IDs and other metadata - * Include ALL text that appears to be part of the header - * Keep exact capitalization and formatting as shown in document * DO NOT include unrelated text written in lowercase or title case. If there is no Exhibit Header present, return |N/A|. Do not fill in values from the examples above, these are only examples. @@ -852,6 +853,7 @@ def EXHIBIT_LINKAGE(header1, header2): * The headers may refer to Attachment, Exhibit, Schedule, Addendum, or similar. If the highest-level number is identical, then the Headers ARE meaningfully different. For example, "Exhibit B-1" and "Exhibit B-2" are meaningfully different because they are both Exhibit B but have different sub-exhibits. * If the headers have the same numbers, but different descriptors, they are NOT meaningfully different. For example, "Attachment A" and "Attachment A: Professional" are NOT meaningfully different because they are both attachment A. * If one header is a continuation of the other (e.g. "Continued", "CONT'D"), they are NOT meaningfully different. +* If Header 1 directly references Header 2 in a way that links the two, then they are NOT meaningfully different. This linkage also applies if Header 2 references Header 1. * Return |Y| if the two Headers are meaningfully different. Return |N| if they are not. [CONTEXT]