Change the fuzz logic find any better solution than this and write a better code to map the bounding boxes for multi token entities. The code sould avoid any extra bounding box than the actual entity bounding box

import json
import pandas as pd
import numpy as np
import cv2
from sklearn.cluster import DBSCAN
from thefuzz import process
from scipy.spatial import distance
from thefuzz import fuzz

def read_textract_output(csv_path):
    return pd.read_csv(csv_path)

def read_json_entities(json_path):
    with open(json_path, "r") as file:
        return json.load(file)

def calculate_bounding_boxes(cluster):
    min_left = np.min(cluster[:, 0])
    min_top = np.min(cluster[:, 1])
    max_right = np.max(cluster[:, 0] + cluster[:, 2])
    max_bottom = np.max(cluster[:, 1] + cluster[:, 3])
    return (min_left, min_top, max_right - min_left, max_bottom - min_top)

def apply_dbscan_clustering(bounding_boxes, eps=25, min_samples=1):
    # Prepare data for clustering (using the center points of bounding boxes)
    X = np.array([(x + w / 2, y + h / 2) for x, y, w, h in bounding_boxes])
    clustering = DBSCAN(eps=eps, min_samples=min_samples, metric="euclidean").fit(X)
    labels = clustering.labels_
    
    # Group bounding boxes by cluster
    clustered_boxes = []
    for label in set(labels):
        if label == -1:  # -1 is noise in DBSCAN
            continue
        indices = np.where(labels == label)[0]
        cluster = np.array(bounding_boxes)[indices]
        clustered_boxes.append(calculate_bounding_boxes(cluster))
    
    return clustered_boxes

def find_entity_bounding_boxes(entity_text, textract_df, image_size):
    entity_tokens = entity_text.split()
    bounding_boxes = []

    for token in entity_tokens:
        choices = textract_df["text"].dropna().tolist()
        best_match = process.extractOne(token, choices, scorer=fuzz.token_sort_ratio)
        if best_match and best_match[1] >= 90:
            best_matches_df = textract_df[textract_df["text"] == best_match[0]]
            bounding_boxes.extend(best_matches_df.apply(lambda row: (row["left"], row["top"], row["width"], row["height"]), axis=1).tolist())

    if not bounding_boxes:
        return []

    # Apply DBSCAN clustering to group bounding boxes
    clustered_boxes = apply_dbscan_clustering(bounding_boxes)

    return clustered_boxes

def draw_bounding_boxes(image_path, entities, textract_df):
    image = cv2.imread(image_path)
    image_size = image.shape[1], image.shape[0]

    for category, details in entities.items():
        if category == "invoice_details" or category == "Payment Details" or category == "amounts_and_tax":
            for entity, value in details.items():
                if value:
                    bounding_boxes = find_entity_bounding_boxes(value, textract_df, image_size)
                    try:
                        for bbox in bounding_boxes:
                            x, y, w, h = map(int, bbox)
                            cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
                    except:
                        pass
        else:
            pass

    cv2.imwrite('/home/ritik1s/Desktop/bbox_issues/temp_GPT/annotated_invoice.jpg', image)

IMAGE_PATH = "/home/ritik1s/Desktop/bbox_issues/temp_GPT/check.jpeg"
CSV_PATH = "/home/ritik1s/Desktop/bbox_issues/temp_GPT/check.csv"
JSON_PATH = "/home/ritik1s/Desktop/bbox_issues/temp_GPT/row_skip.json"

textract_df = read_textract_output(CSV_PATH)
entities = read_json_entities(JSON_PATH)

draw_bounding_boxes(IMAGE_PATH, entities, textract_df)