From b548599a0d4539ee3ee23c10952367bcb72a90be Mon Sep 17 00:00:00 2001 From: Cubix33 Date: Tue, 3 Mar 2026 15:54:37 +0000 Subject: [PATCH 1/2] #168 - spatial sorting for fields input using clustering --- src/filler.py | 79 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/src/filler.py b/src/filler.py index e31e535..c9fb023 100644 --- a/src/filler.py +++ b/src/filler.py @@ -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] @@ -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()) @@ -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 \ No newline at end of file From d8f6af976da2cde55df2be74f369348c235e7b84 Mon Sep 17 00:00:00 2001 From: Cubix33 Date: Tue, 3 Mar 2026 19:09:08 +0000 Subject: [PATCH 2/2] fixx --- src/llm.py | 2 +- src/main.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/llm.py b/src/llm.py index 70937f9..3ed6761 100644 --- a/src/llm.py +++ b/src/llm.py @@ -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" diff --git a/src/main.py b/src/main.py index 5bb632b..54f6b0a 100644 --- a/src/main.py +++ b/src/main.py @@ -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 = [] @@ -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 , 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) @@ -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)