I have a code understand the code. Basically the input of the code is a json where I have list of extracted entities and a ocr textract output in csv format containing these columns (page_num,block_num,line_num,word_num,left,right,top,bottom,width,height,conf,text,image_height,image_width,skewness,orientation). Now your job is to add new function in the code to calculate the confidence score for multi token entities bounding boxes and call that function in process_multi_token_entites and return the average confidence score from next_best_sequence. 
                                                                          
SyntaxError: parameter without a default follows parameter with a default
import cv2
import pandas as pd
import json
from thefuzz import fuzz
from itertools import product

class BoundingBoxFinder:
    def __init__(self):
        self.used_bounding_boxes = {}


    def preprocess_entity(self, entity):
        try:
            token = entity.replace(",", "").strip()
            return token
        except:
            pass

    def calculate_proximity_score(self, box_a, box_b):
        vertical_overlap = max(0, min(box_a["bottom"], box_b["bottom"]) - max(box_a["top"], box_b["top"]))
        vertical_distance = 0 if vertical_overlap > 0 else min(abs(box_a["top"] - box_b["bottom"]), abs(box_a["bottom"] - box_b["top"]))
        horizontal_overlap = max(0, min(box_a["right"], box_b["right"]) - max(box_a["left"], box_b["left"]))
        horizontal_distance = 0 if horizontal_overlap > 0 else abs(box_a["right"] - box_b["left"])
        return horizontal_distance + 2 * vertical_distance

    def is_nearby(self, box_a, box_b, max_line_difference=5, max_distance=100):
        return self.calculate_proximity_score(box_a, box_b) <= max_distance + 2 * max_line_difference

    def merge_boxes(self, boxes):
        min_left = min(box["left"] for box in boxes)
        max_right = max(box["right"] for box in boxes)
        min_top = min(box["top"] for box in boxes)
        max_bottom = max(box["bottom"] for box in boxes)
        return {"left": min_left, "right": max_right, "top": min_top, "bottom": max_bottom}

    def find_potential_matches(self, dataframe, token, threshold=75):
        potential_matches = []
        conf = []
        for _, row in dataframe.iterrows():
            ocr_text = self.preprocess_entity(row["text"])
            score = fuzz.ratio(token, ocr_text)
            if score > threshold:
                potential_matches.append({
                    "box": {"left": row["left"], "right": row["right"], "top": row["top"], "bottom": row["bottom"]},
                    "score": score
                })
                conf.append(row["conf"])
        return potential_matches, conf

    def find_best_sequence_heuristic(self, matches_list):
        if not matches_list or len(matches_list[0]) == 0:
            return []
        best_sequence = [min(matches_list[0], key=lambda match: match["score"])]
        for next_matches in matches_list[1:]:
            current_box = best_sequence[-1]["box"]
            next_best_match = min(next_matches, key=lambda match: self.calculate_proximity_score(current_box, match["box"]))
            best_sequence.append(next_best_match)
        return best_sequence

    def process_single_token_entity(self, dataframe, entity, threshold=75,entity_name,section):
        best_match = None
        best_score = threshold
        best_confidence = 0
        entity = {}
        entity = self.preprocess_entity(entity)

        if entity not in self.used_bounding_boxes:
            self.used_bounding_boxes[entity] = []

        for _, row in dataframe.iterrows():
            ocr_text = self.preprocess_entity(row["text"])
            score = fuzz.ratio(entity, ocr_text)
            current_box = {"left": row["left"], "right": row["right"], "top": row["top"], "bottom": row["bottom"]}
            if score > best_score and current_box not in self.used_bounding_boxes[entity]:
                best_score = score
                best_match = current_box
                best_confidence = row["conf"] 


        if best_match:
            self.used_bounding_boxes[entity].append(best_match)

        
        entity['label'] = entity_name
        # entity['value'] = entity_text
        # entity['bounding_box'] = {'x': row['left'], 'y': row['top'], 'width': row['width'], 'height': row['height']}
        # entity['confidence_score'] = np.round(row['conf'], 2)
        # entity['extract_pagecell_uid'] = gen_random_uuid()

        return best_match, best_confidence

    def box_overlap(self, box1, box2):
        """Check if there"s any overlap in any coordinate between two boxes."""
        return box1["left"] == box2["left"] or box1["right"] == box2["right"]

    def all_boxes_unique(self, sequence_boxes, used_boxes):
        """Ensure no part of the boxes in sequence_boxes overlaps with any box in used_boxes."""
        for seq_box in sequence_boxes:
            for used_box in used_boxes:
                if self.box_overlap(seq_box, used_box):
                    return False
        return True

    def get_next_best_sequence(self, all_potential_matches, previous_matches, entity):
        """
        Try to find the next best sequence of matches that hasn"t used any part of the bounding boxes.
        """
        # Flatten the list of used boxes for easier comparison.
        used_boxes = [box for sequence in previous_matches.get(entity, []) for box in sequence]

        for sequence in product(*all_potential_matches):
            sequence_boxes = [match["box"] for match in sequence]

            if self.all_boxes_unique(sequence_boxes, used_boxes):
                return sequence  # Found a sequence where no box part has been used before

        return None  # No unique sequence found

    def process_multi_token_entity(self, dataframe, entity, threshold=75, max_distance=100, max_line_difference=3):
        tokens = entity.split()
        all_potential_matches = [self.find_potential_matches(dataframe, token, threshold) for token in tokens]

        if not all(matches for matches in all_potential_matches):
            return None

        if entity not in self.used_bounding_boxes:
            self.used_bounding_boxes[entity] = []

        previous_matches = self.used_bounding_boxes.get(entity, [])

        all_sequences = list(product(*all_potential_matches))  # Create all possible sequences
        valid_sequences = []  # List to hold sequences that meet the is_nearby condition

        for sequence in all_sequences:
            sequence_boxes = [match["box"] for match in sequence]
            sequence_is_valid = True
            for i in range(len(sequence_boxes) - 1):
                if not self.is_nearby(sequence_boxes[i], sequence_boxes[i + 1], max_line_difference, max_distance):
                    sequence_is_valid = False
                    break
            if sequence_is_valid:
                valid_sequences.append(sequence)

        # Sort valid sequences by their cumulative proximity score, to prioritize those with boxes closer together
        valid_sequences = sorted(valid_sequences, key=lambda seq: sum(self.calculate_proximity_score(seq[i]["box"], seq[i+1]["box"]) for i in range(len(seq) - 1)))

        next_best_sequence = None
        for sequence in valid_sequences:
            sequence_boxes = [match["box"] for match in sequence]
            if self.all_boxes_unique(sequence_boxes, [box for sublist in previous_matches for box in sublist]):
                next_best_sequence = sequence
                break

        if next_best_sequence:
            new_boxes_sequence = [match["box"] for match in next_best_sequence]

            merged_box = self.merge_boxes(new_boxes_sequence)
            self.used_bounding_boxes[entity].append(new_boxes_sequence)
            return merged_box

        return None

    def draw_bounding_boxes(self, image_path, bounding_boxes, entity_names):
        image = cv2.imread(image_path)
        font = cv2.FONT_HERSHEY_SIMPLEX
        for box, name in zip(bounding_boxes, entity_names):
            if box:
                cv2.rectangle(image, (box["left"], box["top"]), (box["right"], box["bottom"]), (0, 255, 0), 2)
                cv2.putText(image, name, (box["left"], max(box["top"] - 10, 0)), font, 0.5, (0, 0, 255), 2)
        cv2.imwrite("annotated_imagecls.jpg", image)

    def process_data(self, json_path, csv_path, image_path):
        with open(json_path, "r") as f:
            data = json.load(f)
        dataframe = pd.read_csv(csv_path)
        bounding_boxes = []
        entity_names = []
        page_entities = {'invoice_details': [], 'amounts_and_tax': [], 'Payment Details': []}
        # Existing processing for non-special sections
        special_sections = ["amounts_and_tax", "Payment Details"]  # Define special handling cases here

        for section in ["invoice_details", "Payment Details", "amounts_and_tax"]:
            entities = data.get(section, {})

            # Check if the current section needs special handling
            if section not in special_sections:
                for entity_name, entity_value in entities.items():
                    entity_text_no_comma = self.preprocess_entity(entity_value)
                    if " " in entity_text_no_comma:
                        box = self.process_multi_token_entity(dataframe, entity_text_no_comma,entity_name,section)
                    else:
                        box = self.process_single_token_entity(dataframe, entity_text_no_comma,entity_name,section)
                    if box:
                        bounding_boxes.append(box)
                        entity_names.append(entity_name)
            else:
                # Special handling for "amounts_and_tax" section
                reversed_dataframe = dataframe.iloc[::-1].reset_index(drop=True)  # Reverse the dataframe
                for entity_name, entity_value in entities.items():
                    entity_text_no_comma = self.preprocess_entity(entity_value)
                    if " " in entity_text_no_comma:
                        # Use the reversed_dataframe for multi-token entities
                        box = self.process_multi_token_entity(reversed_dataframe, entity_text_no_comma,entity_name,special_sections)
                    else:
                        # Use the reversed_dataframe for single-token entities
                        box = self.process_single_token_entity(reversed_dataframe, entity_text_no_comma,entity_name,section)
                    if box:
                        bounding_boxes.append(box)
                        entity_names.append(entity_name)

        self.draw_bounding_boxes(image_path, bounding_boxes, entity_names)

# Example usage
if __name__ == "__main__":
    bbox_finder = BoundingBoxFinder()
    bbox_finder.process_data("/home/ritik1s/Desktop/bbox_issues/temp_GPT/row_skip.json", "/home/ritik1s/Desktop/bbox_issues/temp_GPT/check.csv", "/home/ritik1s/Desktop/bbox_issues/temp_GPT/check.jpeg")

# bbox_finder.main("/home/ritik1s/Desktop/bbox_issues/temp_GPT/row_skip.json", "/home/ritik1s/Desktop/bbox_issues/temp_GPT/check.csv", "/home/ritik1s/Desktop/bbox_issues/temp_GPT/check.jpeg")