```python:inference/core/workflows/execution_engine/v1/compiler/utils.py
from __future__ import annotations

from typing import List, Type, TypeVar

from inference.core.workflows.errors import AssumptionError
from inference.core.workflows.execution_engine.constants import \
    NODE_COMPILATION_OUTPUT_PROPERTY
from inference.core.workflows.execution_engine.v1.compiler.entities import \
    ExecutionGraphNode
from networkx import DiGraph

NodeTypeVar = TypeVar("NodeTypeVar", bound=ExecutionGraphNode)


def identify_lineage(lineage: List[str]) -> int:
    # Avoid generator overhead by using a simple local loop and local binding of hash
    h = hash
    total = 0
    for e in lineage:
        total += h(e)
    return total




def node_as(
    execution_graph: DiGraph, node: str, expected_type: Type[NodeTypeVar]
) -> NodeTypeVar:
    if node not in execution_graph.nodes:
        raise AssumptionError(
            public_message=f"Workflow Compiler expected node: {node} to be present in execution graph, "
            f"but condition failed to be met. This is most likely the bug. Contact Roboflow team "
            f"through github issues (https://github.com/roboflow/inference/issues) providing full "
            f"context of the problem - including workflow definition you use.",
            context="workflow_compilation | execution_graph_construction | retrieving_compiled_node",
        )
    if NODE_COMPILATION_OUTPUT_PROPERTY not in execution_graph.nodes[node]:
        raise AssumptionError(
            public_message=f"Workflow Compiler expected key: {NODE_COMPILATION_OUTPUT_PROPERTY} to be present "
            f"for node {node} in execution graph, "
            f"but condition failed to be met. This is most likely the bug. Contact Roboflow team "
            f"through github issues (https://github.com/roboflow/inference/issues) providing full "
            f"context of the problem - including workflow definition you use.",
            context="workflow_compilation | execution_graph_construction | retrieving_compiled_node",
        )
    node_data: ExecutionGraphNode = execution_graph.nodes[node][
        NODE_COMPILATION_OUTPUT_PROPERTY
    ]
    if not isinstance(node_data, expected_type):
        node_data_type = type(node_data)
        raise AssumptionError(
            public_message=f"Workflow Compiler expected compilation output for node {node}"
            f"to be {expected_type}, but found: {node_data_type}. "
            f"This is most likely the bug. Contact Roboflow team "
            f"through github issues (https://github.com/roboflow/inference/issues) providing full "
            f"context of the problem - including workflow definition you use.",
            context="workflow_compilation | execution_graph_construction | retrieving_compiled_node",
        )
    return node_data
```
```python:inference/core/workflows/execution_engine/v1/compiler/graph_constructor.py
from collections import defaultdict
from typing import Dict, List, Set, Tuple

import networkx as nx
from inference.core.workflows.errors import AssumptionError
from inference.core.workflows.execution_engine.constants import \
    NODE_COMPILATION_OUTPUT_PROPERTY
from inference.core.workflows.execution_engine.v1.compiler.entities import \
    StepNode
from inference.core.workflows.execution_engine.v1.compiler.utils import \
    identify_lineage


def _collect_unique_control_flow_lineages_with_step_mapping(
    control_flow_steps_selectors: List[str],
    execution_graph: nx.DiGraph,
) -> Tuple[List[List[str]], Dict[int, List[str]]]:
    """
    Collect data lineages from control flow steps and deduplicate by lineage id.

    For each given control flow step selector, reads its data_lineage from the
    execution graph. Returns unique non-empty lineages (by identify_lineage id)
    and a mapping from lineage id to every step selector that produced that
    lineage (used e.g. for error reporting when lineage is incompatible).

    Args:
        control_flow_steps_selectors: Step selectors (node ids) of control
            flow steps whose data_lineage is to be collected.
        execution_graph: The workflow execution graph containing step nodes
            and their data_lineage.

    Returns:
        A tuple of (unique_lineages, lineage_id_to_step_selectors):
        - unique_lineages: list of distinct non-empty data lineages.
        - lineage_id_to_step_selectors: map from lineage id to list of step
          selectors whose data_lineage has that id (includes empty lineages).
    """
    unique_lineages: List[List[str]] = []
    seen_lineage_ids: Set[int] = set()
    lineage_id_to_step_selectors: Dict[int, List[str]] = defaultdict(list)

    for step_selector in control_flow_steps_selectors:
        # Inline node retrieval to avoid function-call overhead for each step
        try:
            node_attrs = execution_graph.nodes[step_selector]
        except Exception:
            # networkx may raise KeyError or other exceptions on missing node; preserve message
            raise AssumptionError(
                public_message=f"Workflow Compiler expected node: {step_selector} to be present in execution graph, "
                f"but condition failed to be met. This is most likely the bug. Contact Roboflow team "
                f"through github issues (https://github.com/roboflow/inference/issues) providing full "
                f"context of the problem - including workflow definition you use.",
                context="workflow_compilation | execution_graph_construction | retrieving_compiled_node",
            )

        if NODE_COMPILATION_OUTPUT_PROPERTY not in node_attrs:
            raise AssumptionError(
                public_message=f"Workflow Compiler expected key: {NODE_COMPILATION_OUTPUT_PROPERTY} to be present "
                f"for node {step_selector} in execution graph, "
                f"but condition failed to be met. This is most likely the bug. Contact Roboflow team "
                f"through github issues (https://github.com/roboflow/inference/issues) providing full "
                f"context of the problem - including workflow definition you use.",
                context="workflow_compilation | execution_graph_construction | retrieving_compiled_node",
            )

        step_data = node_attrs[NODE_COMPILATION_OUTPUT_PROPERTY]
        if not isinstance(step_data, StepNode):
            node_data_type = type(step_data)
            raise AssumptionError(
                public_message=f"Workflow Compiler expected compilation output for node {step_selector}"
                f"to be {StepNode}, but found: {node_data_type}. "
                f"This is most likely the bug. Contact Roboflow team "
                f"through github issues (https://github.com/roboflow/inference/issues) providing full "
                f"context of the problem - including workflow definition you use.",
                context="workflow_compilation | execution_graph_construction | retrieving_compiled_node",
            )

        lineage = step_data.data_lineage
        # Avoid hashing when lineage is empty (identify_lineage would sum over empty iterable -> 0)
        if not lineage:
            lineage_id = 0
        else:
            lineage_id = identify_lineage(lineage)

        lineage_id_to_step_selectors[lineage_id].append(step_selector)
        if lineage and lineage_id not in seen_lineage_ids:
            seen_lineage_ids.add(lineage_id)
            unique_lineages.append(lineage)

    return unique_lineages, dict(lineage_id_to_step_selectors)
```