Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 66 additions & 13 deletions src/filler.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,58 @@
from pdfrw import PdfReader, PdfWriter
from pdfrw import PdfReader, PdfWriter, PdfName
from src.llm import LLM
from datetime import datetime


class Filler:
def __init__(self):
pass

def sort_annots_by_cluster(self, annots, y_tolerance=10.0):
"""
Groups annotations into 'rows' based on their Y-coordinate (within a tolerance).
Then sorts each row by the X-coordinate (left-to-right).
"""
# First, roughly sort them top-to-bottom
rough_sort = sorted(annots, key=lambda a: -float(a.Rect[1]))

rows = []
current_row = []
current_y = None

for annot in rough_sort:
y_val = -float(annot.Rect[1])

# If it's the first item, start the row
if current_y is None:
current_y = y_val
current_row.append(annot)
continue

# If the current item is within the Y-tolerance of the current row
if abs(y_val - current_y) <= y_tolerance:
current_row.append(annot)
else:
# We've moved to a new row. Save the current one and start fresh.
rows.append(current_row)
current_row = [annot]
current_y = y_val

# Don't forget the last row
if current_row:
rows.append(current_row)

# Now, sort each row left-to-right (by X coordinate)
final_sorted = []
for row in rows:
sorted_row = sorted(row, key=lambda a: float(a.Rect[0]))
final_sorted.extend(sorted_row)

return final_sorted


def fill_form(self, pdf_form: str, llm: LLM):
"""
Fill a PDF form with values from user_input using LLM.
Fields are filled in the visual order (top-to-bottom, left-to-right).
Fields are filled in visual order using clustered spatial sorting.
"""
output_pdf = (
pdf_form[:-4]
Expand All @@ -19,9 +61,9 @@ def fill_form(self, pdf_form: str, llm: LLM):
+ "_filled.pdf"
)

# Generate dictionary of answers from your original function
# Generate dictionary of answers
t2j = llm.main_loop()
textbox_answers = t2j.get_data() # This is a dictionary
textbox_answers = t2j.get_data()

answers_list = list(textbox_answers.values())

Expand All @@ -31,22 +73,33 @@ def fill_form(self, pdf_form: str, llm: LLM):
# Loop through pages
for page in pdf.pages:
if page.Annots:
sorted_annots = sorted(
page.Annots, key=lambda a: (-float(a.Rect[1]), float(a.Rect[0]))
)
# -- NEW CLUSTER SORTING LOGIC --
sorted_annots = self.sort_annots_by_cluster(page.Annots)

i = 0
for annot in sorted_annots:
if annot.Subtype == "/Widget" and annot.T:
if i < len(answers_list):
annot.V = f"{answers_list[i]}"
annot.AP = None
val = str(answers_list[i])

# CHECKBOX LOGIC (From previous PR)
if annot.FT == "/Btn":
if val.lower() in ["yes", "on", "true"]:
annot.V = PdfName("Yes")
annot.AS = PdfName("Yes")
else:
annot.V = PdfName("Off")
annot.AS = PdfName("Off")

# STANDARD TEXT LOGIC
else:
annot.V = f"{val}"
annot.AP = None

i += 1
else:
# Stop if we run out of answers
break

PdfWriter().write(output_pdf, pdf)

# Your main.py expects this function to return the path
return output_pdf
return output_pdf
2 changes: 1 addition & 1 deletion src/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def build_prompt(self, current_field):

def main_loop(self):
# self.type_check_all()
for field in self._target_fields.keys():
for field in self._target_fields:
prompt = self.build_prompt(field)
# print(prompt)
# ollama_url = "http://localhost:11434/api/generate"
Expand Down
5 changes: 3 additions & 2 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from commonforms import prepare_form
from pypdf import PdfReader
from controller import Controller
from typing import Union

def input_fields(num_fields: int):
fields = []
Expand Down Expand Up @@ -68,7 +69,7 @@ def run_pdf_fill_process(user_input: str, definitions: list, pdf_form_path: Unio
if __name__ == "__main__":
file = "./src/inputs/file.pdf"
user_input = "Hi. The employee's name is John Doe. His job title is managing director. His department supervisor is Jane Doe. His phone number is 123456. His email is jdoe@ucsc.edu. The signature is <Mamañema>, and the date is 01/02/2005"
fields = ["Employee's name", "Employee's job title", "Employee's department supervisor", "Employee's phone number", "Employee's email", "Signature", "Date"]
descriptive_fields = ["Employee's name", "Employee's job title", "Employee's department supervisor", "Employee's phone number", "Employee's email", "Signature", "Date"]
prepared_pdf = "temp_outfile.pdf"
prepare_form(file, prepared_pdf)

Expand All @@ -80,4 +81,4 @@ def run_pdf_fill_process(user_input: str, definitions: list, pdf_form_path: Unio
num_fields = 0

controller = Controller()
controller.fill_form(user_input, fields, file)
controller.fill_form(user_input, descriptive_fields, file)