# Buggy code
async def visualize_neighborhood(
    current: Optional[List[Event]],
    event_sequences: List[List[Event]],
    output_file: Optional[Text] = None,
    max_history: int = 2,
    interpreter: NaturalLanguageInterpreter = RegexInterpreter(),
    nlu_training_data: Optional["TrainingData"] = None,
    should_merge_nodes: bool = True,
    max_distance: int = 1,
    fontsize: int = 12,
):
    """Given a set of event lists, visualizing the flows."""

    graph = _create_graph(fontsize)
    _add_default_nodes(graph)

    next_node_idx = START_NODE_ID
    special_node_idx = -3
    path_ellipsis_ends = set()

    for events in event_sequences:
        if current and max_distance:
            prefix = _length_of_common_action_prefix(current, events)
        else:
            prefix = len(events)

        message = None
        current_node = START_NODE_ID
        idx = 0
        is_current = events == current

        for idx, el in enumerate(events):
            if not prefix:
                idx -= 1
                break
            if isinstance(el, UserUttered):
                if not el.intent:
                    message = await interpreter.parse(el.text)
                else:
                    message = el.parse_data
            elif (
                isinstance(el, ActionExecuted) and el.action_name != ACTION_LISTEN_NAME
            ):
                next_node_idx += 1
                graph.add_node(
                    next_node_idx,
                    label=el.action_name,
                    fontsize=fontsize,
                    **{"class": "active" if is_current else ""},
                )

                _add_message_edge(
                    graph, message, current_node, next_node_idx, is_current
                )
                current_node = next_node_idx

                message = None
                prefix -= 1

        # determine what the end node of the conversation is going to be
        # this can either be an ellipsis "...", the conversation end node
        # "END" or a "TMP" node if this is the active conversation
        if is_current:
            if (
                isinstance(events[idx], ActionExecuted)
                and events[idx].action_name == ACTION_LISTEN_NAME
            ):
                next_node_idx += 1
                graph.add_node(
                    next_node_idx,
                    label="  ?  "
                    if not message
                    else sanitize(message.get("intent", {})).get("name", "  ?  "),
                    shape="rect",
                    **{"class": "intent dashed active"},
                )
                target = next_node_idx
            elif current_node:
                d = graph.nodes(data=True)[current_node]
                d["class"] = "dashed active"
                target = TMP_NODE_ID
            else:
                target = TMP_NODE_ID
        elif idx == len(events) - 1:
            target = END_NODE_ID
        elif current_node and current_node not in path_ellipsis_ends:
            graph.add_node(special_node_idx, label="...", **{"class": "ellipsis"})
            target = special_node_idx
            path_ellipsis_ends.add(current_node)
            special_node_idx -= 1
        else:
            target = END_NODE_ID

        _add_message_edge(graph, message, current_node, target, is_current)

    if should_merge_nodes:
        _merge_equivalent_nodes(graph, max_history)
    await _replace_edge_labels_with_nodes(
        graph, next_node_idx, interpreter, nlu_training_data
    )

    _remove_auxiliary_nodes(graph, special_node_idx)

    if output_file:
        persist_graph(graph, output_file)
    return graph


# Traceback (TypeError)
2019-05-29 12:17:24 INFO     root  - Generating grammar tables from /usr/lib/python3.6/lib2to3/Grammar.txt
2019-05-29 12:17:24 INFO     root  - Generating grammar tables from /usr/lib/python3.6/lib2to3/PatternGrammar.txt

WARNING: The TensorFlow contrib module will not be included in TensorFlow 2.0.
For more information, please see:
* https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md
* https://github.com/tensorflow/addons
If you depend on functionality not listed there, please file an issue.

2019-05-29 12:17:24 WARNING  rasa.core.interpreter  - No NLU model found. Using RegexInterpreter instead.
2019-05-29 12:17:24.816998: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2019-05-29 12:17:24.821672: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 3591990000 Hz
2019-05-29 12:17:24.822324: I tensorflow/compiler/xla/service/service.cc:150] XLA service 0x66de000 executing computations on platform Host. Devices:
2019-05-29 12:17:24.822342: I tensorflow/compiler/xla/service/service.cc:158]   StreamExecutor device (0): <undefined>, <undefined>
Bot loaded. Visualisation at http://localhost:5006/visualization.html.
Type a message and press enter (press 'Ctr-c' to exit).
Processed Story Blocks: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1804.78it/s, # trackers=1]
? Your input -> HI
? Your NLU model classified '/HI' with intent 'HI' and there are no entities, is this correct?  Yes
------
Chat History

#    Bot                     You
─────────────────────────────────────────
1    action_listen
─────────────────────────────────────────
2                                   /HI
intent: HI 1.00


Current slots:
date_slot: None, dept: None, doc_slot: None, name: None, requested_slot: None, time: None, time_slot: None

------
2019-05-29 12:17:29 ERROR    rasa.core.training.interactive  - An exception occurred while recording messages.
Traceback (most recent call last):
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/interactive.py", line 1349, in record_messages
endpoint, sender_id, finetune, sender_ids, plot_file
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/interactive.py", line 883, in _predict_till_next_listen
sender_ids, plot_file, endpoint, unconfirmed=[ActionExecuted(action_name)]
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/interactive.py", line 1274, in _plot_trackers
event_sequences[-1], event_sequences, output_file=None, max_history=2
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/visualization.py", line 467, in visualize_neighborhood
else sanitize(message.get("intent", {})).get("name", "  ?  "),
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/visualization.py", line 364, in sanitize
return re.sub(r"""[&amp;\\\<\>"'%();+]""", "", s)
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/re.py", line 191, in sub
return _compile(pattern, flags).sub(repl, string, count)
TypeError: expected string or bytes-like object
2019-05-29 12:17:29 ERROR    asyncio  - Task exception was never retrieved
future: <Task finished coro=<_serve_application.<locals>.run_interactive_io() done, defined at /home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/interactive.py:1391> exception=TypeError('expected string or bytes-like object',)>
Traceback (most recent call last):
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/interactive.py", line 1399, in run_interactive_io
sender_id=uuid.uuid4().hex,
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/interactive.py", line 1349, in record_messages
endpoint, sender_id, finetune, sender_ids, plot_file
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/interactive.py", line 883, in _predict_till_next_listen
sender_ids, plot_file, endpoint, unconfirmed=[ActionExecuted(action_name)]
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/interactive.py", line 1274, in _plot_trackers
event_sequences[-1], event_sequences, output_file=None, max_history=2
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/visualization.py", line 467, in visualize_neighborhood
else sanitize(message.get("intent", {})).get("name", "  ?  "),
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/site-packages/rasa/core/training/visualization.py", line 364, in sanitize
return re.sub(r"""[&amp;\\\<\>"'%();+]""", "", s)
File "/home/sfrplindia1/Work/SantaFe/Raghuram/chatbots/rasa_15/lib/python3.6/re.py", line 191, in sub
return _compile(pattern, flags).sub(repl, string, count)
TypeError: expected string or bytes-like object

# Fixed code
async def visualize_neighborhood(
    current: Optional[List[Event]],
    event_sequences: List[List[Event]],
    output_file: Optional[Text] = None,
    max_history: int = 2,
    interpreter: NaturalLanguageInterpreter = RegexInterpreter(),
    nlu_training_data: Optional["TrainingData"] = None,
    should_merge_nodes: bool = True,
    max_distance: int = 1,
    fontsize: int = 12,
):
    """Given a set of event lists, visualizing the flows."""

    graph = _create_graph(fontsize)
    _add_default_nodes(graph)

    next_node_idx = START_NODE_ID
    special_node_idx = -3
    path_ellipsis_ends = set()

    for events in event_sequences:
        if current and max_distance:
            prefix = _length_of_common_action_prefix(current, events)
        else:
            prefix = len(events)

        message = None
        current_node = START_NODE_ID
        idx = 0
        is_current = events == current

        for idx, el in enumerate(events):
            if not prefix:
                idx -= 1
                break
            if isinstance(el, UserUttered):
                if not el.intent:
                    message = await interpreter.parse(el.text)
                else:
                    message = el.parse_data
            elif (
                isinstance(el, ActionExecuted) and el.action_name != ACTION_LISTEN_NAME
            ):
                next_node_idx += 1
                graph.add_node(
                    next_node_idx,
                    label=el.action_name,
                    fontsize=fontsize,
                    **{"class": "active" if is_current else ""},
                )

                _add_message_edge(
                    graph, message, current_node, next_node_idx, is_current
                )
                current_node = next_node_idx

                message = None
                prefix -= 1

        # determine what the end node of the conversation is going to be
        # this can either be an ellipsis "...", the conversation end node
        # "END" or a "TMP" node if this is the active conversation
        if is_current:
            if (
                isinstance(events[idx], ActionExecuted)
                and events[idx].action_name == ACTION_LISTEN_NAME
            ):
                next_node_idx += 1
                graph.add_node(
                    next_node_idx,
                    label="  ?  "
                    if not message
                    else sanitize(message.get("intent", {}).get("name", "  ?  ")),
                    shape="rect",
                    **{"class": "intent dashed active"},
                )
                target = next_node_idx
            elif current_node:
                d = graph.nodes(data=True)[current_node]
                d["class"] = "dashed active"
                target = TMP_NODE_ID
            else:
                target = TMP_NODE_ID
        elif idx == len(events) - 1:
            target = END_NODE_ID
        elif current_node and current_node not in path_ellipsis_ends:
            graph.add_node(special_node_idx, label="...", **{"class": "ellipsis"})
            target = special_node_idx
            path_ellipsis_ends.add(current_node)
            special_node_idx -= 1
        else:
            target = END_NODE_ID

        _add_message_edge(graph, message, current_node, target, is_current)

    if should_merge_nodes:
        _merge_equivalent_nodes(graph, max_history)
    await _replace_edge_labels_with_nodes(
        graph, next_node_idx, interpreter, nlu_training_data
    )

    _remove_auxiliary_nodes(graph, special_node_idx)

    if output_file:
        persist_graph(graph, output_file)
    return graph