2025-06-20 19:46:23 +00:00
|
|
|
import logging
|
2026-01-26 16:52:55 +00:00
|
|
|
from typing import TYPE_CHECKING
|
2025-06-20 19:46:23 +00:00
|
|
|
|
2025-08-05 20:46:19 +00:00
|
|
|
from src import config
|
2026-01-26 16:52:55 +00:00
|
|
|
from src.pipelines.shared.preprocessing import preprocessing_funcs
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from src.pipelines.shared.extraction.page_funcs import Page
|
2025-06-20 19:46:23 +00:00
|
|
|
|
2024-11-13 17:08:19 +00:00
|
|
|
|
2025-01-10 17:24:44 +00:00
|
|
|
def clean_text(contract_text):
|
2025-04-17 19:01:56 +00:00
|
|
|
|
2025-01-17 20:25:55 +00:00
|
|
|
contract_text = preprocessing_funcs.remove_page_indicators(contract_text)
|
2024-10-28 23:39:36 +00:00
|
|
|
contract_text = preprocessing_funcs.clean_law_symbols(contract_text)
|
2025-04-17 19:01:56 +00:00
|
|
|
|
2025-01-10 17:24:44 +00:00
|
|
|
return contract_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def split_text(contract_text):
|
|
|
|
|
"""
|
|
|
|
|
Splits the contract text into separate pages and filters pages needing quick review.
|
|
|
|
|
|
2025-06-20 19:46:23 +00:00
|
|
|
This function processes the input contract text by splitting it into individual pages
|
|
|
|
|
and then categorizes these pages. Pages representing a Quick Review or Cover Page section are separated from
|
2025-01-10 17:24:44 +00:00
|
|
|
the main text.
|
|
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
contract_text (str): The full text of the contract to be processed.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
tuple: A tuple containing three elements:
|
|
|
|
|
- dict: A dictionary with keys as page numbers (str) and values as the text of those pages.
|
|
|
|
|
- dict: A dictionary of pages that were identified Quick Review.
|
|
|
|
|
- int: The total number of pages in the contract text.
|
2024-10-28 23:39:36 +00:00
|
|
|
|
2025-01-10 17:24:44 +00:00
|
|
|
Notes:
|
2025-06-20 19:46:23 +00:00
|
|
|
- This function utilizes `preprocessing_funcs.split_text` to divide the contract text
|
2025-01-10 17:24:44 +00:00
|
|
|
into a dictionary with page numbers as keys.
|
2025-06-20 19:46:23 +00:00
|
|
|
- It uses `preprocessing_funcs.filter_quick_review` to separate out pages that need quick
|
2025-01-10 17:24:44 +00:00
|
|
|
review from the main text dictionary.
|
|
|
|
|
"""
|
2024-10-28 23:39:36 +00:00
|
|
|
text_dict = preprocessing_funcs.split_text(
|
|
|
|
|
contract_text
|
|
|
|
|
) # return a dictionary with keys - page_num (str), values as the page_text
|
2024-11-22 15:30:01 +00:00
|
|
|
text_dict, top_sheet_dict = preprocessing_funcs.filter_quick_review(text_dict)
|
2025-01-27 19:39:23 +00:00
|
|
|
return text_dict, top_sheet_dict
|
2025-01-10 17:24:44 +00:00
|
|
|
|
|
|
|
|
|
2026-01-26 16:52:55 +00:00
|
|
|
def split_text_with_pages(
|
|
|
|
|
contract_text: str,
|
|
|
|
|
) -> tuple[dict[str, "Page"], dict[str, str]]:
|
|
|
|
|
"""
|
|
|
|
|
Split text into pages and convert to Page objects with table splitting.
|
|
|
|
|
|
|
|
|
|
This function extends split_text() by converting the text dictionary to Page objects
|
|
|
|
|
and splitting large tables into sub-pages.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
contract_text: The full text of the contract to be processed
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
tuple: (pages_dict, top_sheet_dict) where:
|
|
|
|
|
- pages_dict: Dictionary mapping page numbers to Page objects
|
|
|
|
|
- top_sheet_dict: Dictionary of pages identified as Quick Review
|
|
|
|
|
"""
|
|
|
|
|
# First, split into text_dict (existing logic)
|
|
|
|
|
text_dict, top_sheet_dict = split_text(contract_text)
|
|
|
|
|
|
|
|
|
|
# Then, convert to Page objects and split large tables
|
|
|
|
|
pages_dict = preprocessing_funcs.split_large_tables(text_dict)
|
|
|
|
|
|
|
|
|
|
return pages_dict, top_sheet_dict
|
|
|
|
|
|
|
|
|
|
|
2025-08-05 20:46:19 +00:00
|
|
|
def one_to_n_exhibit_chunking(
|
2026-01-26 16:52:55 +00:00
|
|
|
text_dict=None,
|
|
|
|
|
pages_dict=None,
|
|
|
|
|
EXHIBIT_HEADER_MARKERS=None,
|
|
|
|
|
filename=None,
|
2025-08-05 20:46:19 +00:00
|
|
|
) -> tuple[dict, dict]:
|
2025-06-03 20:47:42 +00:00
|
|
|
"""
|
2026-01-26 16:52:55 +00:00
|
|
|
Process the text dictionary or pages dictionary to identify and chunk exhibits.
|
|
|
|
|
|
2025-06-03 20:47:42 +00:00
|
|
|
This function extracts exhibit pages, maps them to their respective chunks,
|
|
|
|
|
and identifies reimbursement exhibits using LLM.
|
|
|
|
|
|
|
|
|
|
Args:
|
2026-01-26 16:52:55 +00:00
|
|
|
text_dict: A dictionary where keys are page numbers and values are the text
|
|
|
|
|
on those pages (for backward compatibility)
|
|
|
|
|
pages_dict: A dictionary where keys are page numbers and values are Page objects
|
|
|
|
|
(preferred)
|
|
|
|
|
EXHIBIT_HEADER_MARKERS: List of markers to identify exhibit headers
|
|
|
|
|
filename: The name of the file being processed.
|
|
|
|
|
|
2025-06-03 20:47:42 +00:00
|
|
|
Returns:
|
|
|
|
|
tuple: A tuple containing:
|
2026-01-26 16:52:55 +00:00
|
|
|
- dict: A dictionary where keys are exhibit page numbers and values are
|
|
|
|
|
lists of page numbers that belong to that exhibit.
|
|
|
|
|
- dict: A dictionary where keys are exhibit page numbers and values are
|
|
|
|
|
the exhibit headers.
|
|
|
|
|
|
|
|
|
|
Note:
|
|
|
|
|
Either text_dict or pages_dict must be provided. If both are provided,
|
|
|
|
|
pages_dict takes precedence.
|
2025-06-03 20:47:42 +00:00
|
|
|
"""
|
2026-01-26 16:52:55 +00:00
|
|
|
# Determine which dictionary to use
|
|
|
|
|
if pages_dict is not None:
|
|
|
|
|
exhibit_pages, all_exhibit_headers = preprocessing_funcs.get_exhibit_pages(
|
|
|
|
|
pages_dict=pages_dict,
|
|
|
|
|
EXHIBIT_HEADER_MARKERS=EXHIBIT_HEADER_MARKERS,
|
|
|
|
|
filename=filename,
|
|
|
|
|
)
|
|
|
|
|
exhibit_pages, all_exhibit_headers = preprocessing_funcs.link_exhibit_pages(
|
|
|
|
|
all_exhibit_headers, pages_dict=pages_dict, filename=filename
|
|
|
|
|
)
|
|
|
|
|
exhibit_chunk_mapping = preprocessing_funcs.chunk_by_exhibit(
|
|
|
|
|
pages_dict=pages_dict, exhibit_pages=exhibit_pages
|
|
|
|
|
)
|
|
|
|
|
elif text_dict is not None:
|
|
|
|
|
exhibit_pages, all_exhibit_headers = preprocessing_funcs.get_exhibit_pages(
|
|
|
|
|
text_dict=text_dict,
|
|
|
|
|
EXHIBIT_HEADER_MARKERS=EXHIBIT_HEADER_MARKERS,
|
|
|
|
|
filename=filename,
|
|
|
|
|
)
|
|
|
|
|
exhibit_pages, all_exhibit_headers = preprocessing_funcs.link_exhibit_pages(
|
|
|
|
|
all_exhibit_headers, text_dict=text_dict, filename=filename
|
|
|
|
|
)
|
|
|
|
|
exhibit_chunk_mapping = preprocessing_funcs.chunk_by_exhibit(
|
|
|
|
|
text_dict=text_dict, exhibit_pages=exhibit_pages
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
raise ValueError("Either text_dict or pages_dict must be provided")
|
|
|
|
|
|
2025-12-10 20:01:52 +00:00
|
|
|
return exhibit_chunk_mapping, all_exhibit_headers
|
2025-08-27 17:42:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_headers_and_footers(text_dict: dict[str, str]) -> tuple[dict, str, str]:
|
|
|
|
|
"""
|
|
|
|
|
Returns a tuple of two lists: (header_markers, footer_markers).
|
|
|
|
|
|
|
|
|
|
header_markers: The longest substring that is found on EVERY page of the text_dict at the START of the page.
|
|
|
|
|
footer_markers: The longest substring that is found on EVERY page of the text_dict at the END of the page.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
text_dict (dict): A dictionary where keys are page numbers and values are the text of those pages.
|
|
|
|
|
Returns:
|
|
|
|
|
tuple: A tuple containing two strings:
|
|
|
|
|
- header_marker: A string representing the longest common substring found at the start of each page. Can be empty string
|
|
|
|
|
- footer_marker: A string representing the longest common substring found at the foot of each page. Can be empty string
|
|
|
|
|
"""
|
2025-08-29 14:38:15 +00:00
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
if not text_dict:
|
|
|
|
|
return {}, "", ""
|
2025-08-29 14:38:15 +00:00
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
# Get the pages as a list
|
|
|
|
|
pages = list(text_dict.values())
|
2025-08-29 14:38:15 +00:00
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
# If there's only one page, return empty strings and original dict
|
|
|
|
|
if len(pages) <= 1:
|
2025-08-29 14:38:15 +00:00
|
|
|
return text_dict.copy(), "", ""
|
|
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
# Split pages into words
|
|
|
|
|
pages_words = [page.split() for page in pages]
|
2025-08-29 14:38:15 +00:00
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
# Find header (common prefix words)
|
|
|
|
|
header_words = []
|
|
|
|
|
min_word_count = min(len(page_words) for page_words in pages_words)
|
2025-08-29 14:38:15 +00:00
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
for i in range(min_word_count):
|
|
|
|
|
word = pages_words[0][i]
|
|
|
|
|
if all(page_words[i] == word for page_words in pages_words[1:]):
|
|
|
|
|
header_words.append(word)
|
|
|
|
|
else:
|
|
|
|
|
break
|
2025-08-29 14:38:15 +00:00
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
# Find footer (common suffix words)
|
|
|
|
|
footer_words = []
|
|
|
|
|
for i in range(1, min_word_count + 1):
|
|
|
|
|
word = pages_words[0][-i]
|
|
|
|
|
if all(page_words[-i] == word for page_words in pages_words[1:]):
|
|
|
|
|
footer_words.insert(0, word) # Insert at beginning to maintain order
|
|
|
|
|
else:
|
|
|
|
|
break
|
2025-08-29 14:38:15 +00:00
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
# Convert back to strings with proper spacing
|
|
|
|
|
header_marker = " ".join(header_words)
|
|
|
|
|
footer_marker = " ".join(footer_words)
|
2025-08-29 14:38:15 +00:00
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
# Create a modified text_dict with headers and footers removed
|
|
|
|
|
modified_text_dict = {}
|
2025-08-29 14:38:15 +00:00
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
first_page = True
|
|
|
|
|
for page_num, page_text in text_dict.items():
|
|
|
|
|
modified_text = page_text
|
|
|
|
|
|
2025-08-29 14:38:15 +00:00
|
|
|
if not first_page: # Keep global header for the first page that it occurs
|
2025-08-27 17:42:35 +00:00
|
|
|
# Remove header if it exists
|
|
|
|
|
if header_marker:
|
2025-08-29 14:38:15 +00:00
|
|
|
modified_text = modified_text[len(header_marker) :].lstrip()
|
|
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
# Remove footer if it exists
|
|
|
|
|
if footer_marker:
|
2025-08-29 14:38:15 +00:00
|
|
|
modified_text = modified_text[: -len(footer_marker)].rstrip()
|
|
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
modified_text_dict[page_num] = modified_text
|
|
|
|
|
else:
|
|
|
|
|
first_page = False
|
|
|
|
|
modified_text_dict[page_num] = page_text
|
2025-08-29 14:38:15 +00:00
|
|
|
|
2025-08-27 17:42:35 +00:00
|
|
|
return modified_text_dict, header_marker, footer_marker
|