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
This commit is contained in:
Katon Minhas
2025-10-09 18:41:36 +00:00
parent bb7404ed62
commit 3ca9d5b084
3 changed files with 33 additions and 27 deletions
@@ -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
@@ -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
@@ -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]