I have code analyse that code. And modify the code to handle those entity which have exact same value but their bounding box on image can't be same because that entity address different purpose in invoice. And In case of amoun_and_tax entities code should read dataframe from button so that bounding should not come from table. Modify code with better Dynamic Programming approch but main logic should not change too much. 
Do return complete code with modifications

import cv2
import pandas as pd
import json
from thefuzz import fuzz

def preprocess_entity(entity):
    """Preprocess entities by removing commas in numerical values."""
    return entity.replace(",", "").strip()

def calculate_proximity_score(box_a, box_b):
    """Calculate a refined proximity score taking into account typical reading flows."""
    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(box_a, box_b, max_line_difference=1, max_distance=50):
    return calculate_proximity_score(box_a, box_b) <= max_distance + 2 * max_line_difference

def merge_boxes(boxes):
    """Merge a list of bounding boxes into a single bounding box."""
    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(dataframe, token, threshold=80):
    potential_matches = []
    for _, row in dataframe.iterrows():
        ocr_text = 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
            })
    return potential_matches

def find_best_sequence_heuristic(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: calculate_proximity_score(current_box, match["box"]))
        best_sequence.append(next_best_match)
    return best_sequence

def process_single_token_entity(dataframe, entity, threshold=75):
    best_match = None
    best_score = threshold
    entity = preprocess_entity(entity)
    for _, row in dataframe.iterrows():
        ocr_text = preprocess_entity(row["text"])
        score = fuzz.ratio(entity, ocr_text)
        if score > best_score:
            best_score = score
            best_match = {
                "left": row["left"], "right": row["right"],
                "top": row["top"], "bottom": row["bottom"]
            }
    return best_match

def process_multi_token_entity(dataframe, entity, threshold=85):
    tokens = entity.split()
    all_potential_matches = [find_potential_matches(dataframe, token, threshold) for token in tokens]
    if not all([matches for matches in all_potential_matches]):
        return None
    best_sequence = find_best_sequence_heuristic(all_potential_matches)
    if best_sequence:
        boxes_to_merge = [match["box"] for match in best_sequence]
        return merge_boxes(boxes_to_merge)
    return None

def draw_bounding_boxes(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_image.jpg_using_dp.jpg", image)

def main(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 = []

    for section in ["invoice_details", "Payment Details", "amounts_and_tax"]:
        entities = data.get(section, {})
        for entity_name, entity_value in entities.items():
            entity_value_no_comma = preprocess_entity(entity_value)
            if " " in entity_value_no_comma:
                box = process_multi_token_entity(dataframe, entity_value_no_comma)
            else:
                box = process_single_token_entity(dataframe, entity_value_no_comma)
            if box:
                bounding_boxes.append(box)
                entity_names.append(entity_name)

    draw_bounding_boxes(image_path, bounding_boxes, entity_names)

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")
# 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")