Добавь в код учет ограничения по времени, где если заданно 00,24 означает что нет ограничения

import numpy as np
import networkx as nx
import matplotlib.pyplot as plt

import pulp as pl
from typing import Dict, Tuple

def tsp_ip(init: nx.DiGraph | np.ndarray, msg: bool = False) -> (int | float | None, nx.DiGraph):
    """
    Solves the asymmetric Traveling Salesman Problem using integer linear programming.

    Parameters:
        init_graph (nx.DiGraph): The directed graph representing the distances
        or adjacency matrix numpy.

    Returns:
        Length of the shortest path,
        nx.DiGraph: The optimized route as a directed graph.
    """
    
    # Init griph
    if isinstance(init, nx.DiGraph):        
        init_graph = init.copy()
    else:
        init_graph = nx.from_numpy_array(init, create_using=nx.DiGraph)

    # Number of graph vertices
    n = len(init_graph)
    if n <= 1:
        return None, nx.DiGraph()
        
    # Removing self-loops
    init_graph.remove_edges_from(nx.selfloop_edges(init_graph))    
    
    # Fill index dictionaries
    array_index: Dict[int, Tuple[int, int]] = {}
    array_key: Dict[Tuple[int, int], int] = {}
    for i, key in enumerate(init_graph.in_edges()):
        array_key[key] = i
        array_index[i] = key

    # Declare the model
    model = pl.LpProblem(name="tsp", sense=pl.LpMinimize)

    # Connect the solver
    solver = pl.PULP_CBC_CMD(msg=msg)

    # Declare model variables
    x = [pl.LpVariable(name=f'x_{array_index[i][0]}_{array_index[i][1]}', cat='Integer', lowBound=0) for i in array_index]

    # Input the objective function
    model += pl.lpSum([init_graph[array_index[i][0]][array_index[i][1]]['weight'] * x[i] for i in array_index])

    # Set initial constraints
    for i in range(n):
        # Each node can have only one outgoing edge
        model += pl.lpSum([x[array_key[j]] for j in init_graph.out_edges(i)]) == 1
        # Each node can have only one incoming edge
        model += pl.lpSum([x[array_key[j]] for j in init_graph.in_edges(i)]) == 1

    print(model)

    half_n = n // 2
    while True:
        # Find a solution
        status = model.solve(solver)        

        if status != 1:
            return None, nx.DiGraph()

        # Save solver result as a graph
        graph_result = nx.DiGraph()
        for val in model.variables():
            if round(val.value()) == 1:
                key = val.name.split('_')
                graph_result.add_edge(int(key[1]), int(key[2]))

        # Split the result graph into subsets
        result_sets = list(nx.connected_components(graph_result.to_undirected()))

        # Solution found if there is only one subset in the graph
        if len(result_sets) == 1:
            return sum([init_graph[i[0]][i[1]]['weight'] for i in graph_result.edges()]), graph_result

        # For each subset, add a constraint connecting it to other subsets
        for val in result_sets:
            if len(val) <= half_n:
                model += pl.lpSum([
                    x[array_key[key]] for key in init_graph.out_edges(val) if key[0] in val and key[1] in val
                ]) <= len(val) - 1


def create_distance_matrix(num_nodes, edges):
    # Инициализация матрицы
    distance_matrix = np.zeros((num_nodes, num_nodes))

    for edge in edges:
        i, j, distance = edge
        distance_matrix[i][j] = distance
        distance_matrix[j][i] = distance  # так как граф неориентированный
    
    return distance_matrix

def create_graph_from_distance_matrix(distance_matrix, node_names_with_idx):
    # Создание графа
    G = nx.Graph()

    # Добавление узлов (точек)
    num_points = distance_matrix.shape[0]
    G.add_nodes_from(node_names)

    # Добавление ребер с весами (расстояниями)
    for i in range(num_points):
        for j in range(i + 1, num_points):
            if distance_matrix[i, j] > 0:  # учтем только ненулевые расстояния
                G.add_edge(node_names_with_idx[i], node_names_with_idx[j], weight=distance_matrix[i, j])
    
    return G

def draw_graph(G):
    pos = nx.spring_layout(G)  # для визуализации
    weights = nx.get_edge_attributes(G, 'weight')
    
    plt.figure(figsize=(10, 8))
    nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=700, font_size=10)
    nx.draw_networkx_edge_labels(G, pos, edge_labels=weights)
    plt.title('Graph from Distance Matrix')
    plt.show()

text = '''
{ИдТочки},{Sl_ТД000093},{Sl_ТД000058},{Sl_ТД000021},{Sl_ТД000016},{Sl_ТД000062},{Sl_ТД000002},{bn_ТМ000006}
{Sl_ТД000093},{0},{8430},{703,5},{664,4},{497,1},{2211,7},{59342,9}
{Sl_ТД000058},{8430},{0},{9133,4},{9094,4},{8927,1},{10311},{56409,6}
{Sl_ТД000021},{703,5},{9133,4},{0},{39,1},{380,3},{1174,7},{59226,1}
{Sl_ТД000016},{664,4},{9094,4},{39,1},{0},{341,3},{1213,8},{59187,1}
{Sl_ТД000062},{497,1},{8927,1},{380,3},{341,3},{0},{1714,6},{58845,8}
{Sl_ТД000002},{1949,8},{10052,8},{1237,2},{1249,5},{1452,7},{0},{57771,7}
{bn_ТМ000006},{0},{0},{0},{0},{0},{0},{0}
'''

# Преобразуем текст в список строк
lines = text.replace('', '') 
lines = lines.replace('},{', '};{')
lines = lines.strip().split('\n')

# Заголовки для сопоставления индексируем и извлекаем без ИдТочки
headers = [header.strip('{}') for header in lines[0].split(';')][1:]

# Перемещаем последнее значение в начало списка заголовков
last_value = headers.pop()
headers.insert(0, last_value)

# Создадим отдельное соответствие для индексов
index_mapping = {i: headers[i-1] for i in range(1, len(headers) + 1)}

# Создадим структуру данных
data = []
for i, line in enumerate(lines[1:], start=0):
    values = [v.strip('{}') for v in line.split(';')[1:]]
    for j, value in enumerate(values, start=0):
        #if i < j:
        #    number_value = float(value.replace(',', '.'))
        #    data.append([i, j, number_value])
        #if i < j:
            number_value = float(value.replace(',', '.'))
            if not number_value==0:
                data.append([i, j, number_value])


edges = data
print(edges)

node_names = list(index_mapping.values())
print(node_names)

num_nodes = len(node_names)
print(num_nodes)

distance_matrix = create_distance_matrix(num_nodes, edges)

# Решение задачи коммивояжера (Traveling Salesman Problem)
path_len, graph_result = tsp_ip(distance_matrix, msg=False)

if path_len:
    # Get the list of edges of the optimal path
    res = nx.find_cycle(graph_result)
    print('Min path =', res)
    print('Length =', path_len)
    for re in res:
        b, e = re
        #print(''+node_names[b]+' : '+node_names[e])
        print(node_names[e])

else:
    print('Solution impossible!')

# Входной текст
input_text = '''
{Ид},{Наименование},{ОкнаДоставки},{ИдТранспортныхСредств},{ИдЗоны},{ИдМаршрута}
{Sl_ТД000093},{658920, Алтайский край, Кулундинский р-н, Кулунда с, Советская ул, дом № 32},{00.00.00-24.00.00,},{bn000000043,},{},{}
{Sl_ТД000058},{658922, Алтайский край, Кулундинский р-н, Кулунда с, Заводская ул, дом № 1},{00.00.00-24.00.00,},{bn000000043,},{},{}
{Sl_ТД000021},{658920, Алтайский край, Кулундинский р-н, Кулунда с, Лермонтова ул, дом № 2/9},{00.00.00-24.00.00,},{bn000000043,},{},{}
{Sl_ТД000016},{658920, Алтайский край, Кулундинский р-н, Кулунда с, Лермонтова ул, дом 1А},{00.00.00-24.00.00,},{bn000000043,},{},{}
{Sl_ТД000062},{658920, Алтайский край, Кулундинский р-н, Кулунда с, Советская ул, дом № 12},{00.00.00-24.00.00,},{bn000000043,},{},{}
{Sl_ТД000002},{658923, Алтайский край, Кулундинский р-н, Кулунда с, Калинина пер, дом № 3А},{00.00.00-24.00.00,},{bn000000043,},{},{}
'''  # Тут будет продолжение вашего текста

# Заменяем ‘},{’ на ‘;’ и убираем фигурные скобки
input_text = input_text.replace('},{', ';').replace('{', '').replace('}', '')

# Удаляем ненужные символы и разбиваем по строкам
lines = input_text.strip().split('\n')

# Извлекаем и печатаем первые две колонки
data = []
for line in lines:
    columns = line.split(';')  # Используем ‘;’ как разделитель
    data.append(columns[:2])  # Добавляем только первую и вторую колонку до data

# Вывод результата
#for item in data:
#    print(item)

def search_by_id(search_id, data):
    for row in data[1:]:  # Пропускаем заголовок
        if row[0] == search_id:
            return row[1]  # Возвращаем наименование (адрес) при совпадении Ид
    return None

for item in res:
    b, e = item
    result = search_by_id(node_names[e], data)
    print(result)


# Создание графа из матрицы расстояний и его отображение
#G = create_graph_from_distance_matrix(distance_matrix, node_names)
#draw_graph(G)