# Problem Description
This problem involves finding the minimum cost path between two nodes in an undirected weighted graph. Each node has an associated value, and each edge has a cost. The goal is to determine the minimum cost required to travel from a given starting node to an ending node, following a specific pattern for calculating the path cost based on the nodes' values and edge costs.

# Visual Facts
1. **Graph Structure**:
   - The graph consists of 6 nodes labeled A through F.
   - There are 5 edges connecting these nodes.
   - The graph is undirected (no arrows on edges).

2. **Node Values**:
   - Node A has a value of 12.
   - Node B has a value of 3.
   - Node C has a value of -2.
   - Node D has a value of -8.
   - Node E has a value of -6.
   - Node F has a value of 4.

3. **Edge Costs**:
   - The edge between A and B has a cost of 36.
   - The edge between B and C has a cost of 6.
   - The edge between B and D has a cost of 24.
   - The edge between D and E has a cost of 48.
   - The edge between D and F has a cost of 32.

# Visual Patterns
1. **Cost Calculation Pattern**:
   - The cost of each edge appears to be the product of the absolute values of the two nodes it connects.
   - For example:
     - The cost of edge A-B is |12| * |3| = 36.
     - The cost of edge B-C is |3| * |-2| = 6.
     - The cost of edge B-D is |3| * |-8| = 24.
     - The cost of edge D-E is |-8| * |-6| = 48.
     - The cost of edge D-F is |-8| * |4| = 32.

2. **Graph Connectivity**:
   - Node A is connected to Node B.
   - Node B is connected to Nodes A, C, and D.
   - Node C is connected to Node B.
   - Node D is connected to Nodes B, E, and F.
   - Node E is connected to Node D.
   - Node F is connected to Node D.

# Solution
import heapq

def solution(nodes: dict, edges: list, start: str, end: str) -> int:
    """
    Given the nodes and edges of a graph, determine the minimum path cost from a given starting node to an ending node.
    Please observe the example graph in the image to deduce the pattern calculating the path cost between two nodes.

    Input:
    - nodes: A dictionary where each key represents a node, and its associated value is the node's value.
            Example: {'A': 10, 'B': 20} indicates that node A has a value of 10, and node B has a value of 20.
    - edges: A list of tuples, each containing two nodes that are directly connected.
            Example: [('A', 'B'), ('B', 'C')] means node A is connected to node B, and node B is connected to node C.
    - start: The starting node where the path begins.
    - end: The ending node where the path terminates.

    Output:
    - Return the minimum cost required to travel from the start node to the end node. Return -1 if no path exists.
    """
    graph = {node: {} for node in nodes}
    for node1, node2 in edges:
        if node1 in graph and node2 in graph:
            graph[node1][node2] = abs(nodes[node1]) * abs(nodes[node2])
            graph[node2][node1] = abs(nodes[node1]) * abs(nodes[node2])
    pq = [(0, start)]
    visited = set()
    min_cost = {node: float('inf') for node in nodes}
    min_cost[start] = 0
    while pq:
        current_cost, current_node = heapq.heappop(pq)
        if current_node in visited:
            continue
        visited.add(current_node)
        if current_node == end:
            return current_cost
        for neighbor, weight in graph[current_node].items():
            if neighbor not in visited:
                new_cost = current_cost + weight
                if new_cost < min_cost[neighbor]:
                    min_cost[neighbor] = new_cost
                    heapq.heappush(pq, (new_cost, neighbor))
    return -1


# Test Script
def test():
    nodes = {'A': 10, 'B': 3, 'C': -2, 'D': -8, 'E': -6, 'F': 4}
    connections = [('A', 'B'), ('B', 'C'), ('B', 'D'), ('D', 'E'), ('D', 'F')]
    pairs = [('A', 'C'), ('B', 'E'), ('A', 'F'), ('A', 'D'), ('A', 'E'), ('B', 'F')]
    expected_results = [36, 72, 86, 54, 102, 56]
    
    for index, (start, end) in enumerate(pairs):
        try:
            output = solution(nodes, connections, start, end)
        except Exception as e:
            error_message = (
                f"An exception occurred while running the test case:\n"
                f"`nodes`: {nodes}\n"
                f"`edges`: {connections}\n"
                f"`start`: {start}\n"
                f"`end`: {end}\n\n"
                f"Exception: '{str(e)}'"
            )
            assert False, error_message

        if output != expected_results[index]:
            error_message = (
                f"The following Test Case failed:\n"
                f"`nodes`: {nodes}\n"
                f"`edges`: {connections}\n"
                f"`start`: {start}\n"
                f"`end`: {end}\n\n"
                f"Actual Output: {output}\n"
                f"Expected Output: {expected_results[index]}"
            )
            assert False, error_message


    nodes = {'A': 1, 'B': -2, 'C': 3, 'D': -4, 'E': 5, 'F': -6}
    connections = [('A', 'D'), ('D', 'E'), ('E', 'F'), ('E', 'B'), ('F', 'C'), ('D', 'B')]
    pairs = [('A', 'C'), ('B', 'E'), ('A', 'F'), ('C', 'D'), ('A', 'B')]
    expected_results = [70, 10, 52, 66, 12]
    for index, (start, end) in enumerate(pairs):
        try:
            output = solution(nodes, connections, start, end)
        except Exception as e:
            error_message = (
                f"An exception occurred while running the test case:\n"
                f"`nodes`: {nodes}\n"
                f"`edges`: {connections}\n"
                f"`start`: {start}\n"
                f"`end`: {end}\n\n"
                f"Exception: '{str(e)}'"
            )
            assert False, error_message

        if output != expected_results[index]:
            error_message = (
                f"The following Test Case failed:\n"
                f"`nodes`: {nodes}\n"
                f"`edges`: {connections}\n"
                f"`start`: {start}\n"
                f"`end`: {end}\n\n"
                f"Actual Output: {output}\n"
                f"Expected Output: {expected_results[index]}"
            )
            assert False, error_message

test()