diff --git a/src/pathcomp/frontend/service/algorithms/_Algorithm.py b/src/pathcomp/frontend/service/algorithms/_Algorithm.py
index 0286e1420a6c96a140d39380a2fa35d24f991abf..6cc4c5496597cce06be04587d69335d0a614cef5 100644
--- a/src/pathcomp/frontend/service/algorithms/_Algorithm.py
+++ b/src/pathcomp/frontend/service/algorithms/_Algorithm.py
@@ -25,6 +25,10 @@ from .tools.ComposeRequest import compose_device, compose_link, compose_service
 from .tools.ComputeSubServices import (
     convert_explicit_path_hops_to_connections, convert_explicit_path_hops_to_plain_connection)
 
+SRC_END = 'src'
+DST_END = 'dst'
+SENSE = [SRC_END, DST_END]
+
 class _Algorithm:
     def __init__(self, algorithm_id : str, sync_paths : bool, class_name=__name__) -> None:
         # algorithm_id: algorithm to be executed
@@ -44,7 +48,7 @@ class _Algorithm:
         self.endpoint_name_mapping : Dict[Tuple[str, str], str] = dict()
         self.link_list : List[Dict] = list()
         self.link_dict : Dict[str, Tuple[Dict, Link]] = dict()
-        self.endpoint_to_link_dict : Dict[Tuple[str, str], Tuple[Dict, Link]] = dict()
+        self.endpoint_to_link_dict : Dict[Tuple[str, str, str], Tuple[Dict, Link]] = dict()
         self.service_list : List[Dict] = list()
         self.service_dict : Dict[Tuple[str, str], Tuple[Dict, Service]] = dict()
 
@@ -86,11 +90,11 @@ class _Algorithm:
             link_uuid = json_link['link_Id']
             self.link_dict[link_uuid] = (json_link, grpc_link)
 
-            for link_endpoint_id in json_link['link_endpoint_ids']:
+            for i,link_endpoint_id in enumerate(json_link['link_endpoint_ids']):
                 link_endpoint_id = link_endpoint_id['endpoint_id']
                 device_uuid = link_endpoint_id['device_id']
                 endpoint_uuid = link_endpoint_id['endpoint_uuid']
-                endpoint_key = (device_uuid, endpoint_uuid)
+                endpoint_key = (device_uuid, endpoint_uuid, SENSE[i])
                 link_tuple = (json_link, grpc_link)
                 self.endpoint_to_link_dict[endpoint_key] = link_tuple
 
@@ -175,7 +179,9 @@ class _Algorithm:
             MSG = 'Unhandled generic Config Rules for service {:s} {:s}'
             self.logger.warning(MSG.format(str(service_uuid), str(ServiceTypeEnum.Name(service_type))))
 
-        compose_device_config_rules(config_rules, service.service_config.config_rules, path_hops)
+        compose_device_config_rules(
+            config_rules, service.service_config.config_rules, path_hops,
+            self.device_name_mapping, self.endpoint_name_mapping)
 
         if path_hops is not None and len(path_hops) > 0:
             ingress_endpoint_id = service.service_endpoint_ids.add()
@@ -214,7 +220,7 @@ class _Algorithm:
             if no_path_issue is not None:
                 # no path found: leave connection with no endpoints
                 # no_path_issue == 1 => no path due to a constraint
-                grpc_services[service_key] = grpc_orig_service
+                grpc_services[orig_service_key] = grpc_orig_service
                 continue
 
             orig_config_rules = grpc_orig_service.service_config.config_rules
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
index 30845bb11f2b027d24da3e42e4e4fee12b7da1ba..91367e23f29a02aa3e9605fcd0d2864b9191d800 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
@@ -12,8 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import json, re
-from typing import Dict, List, Optional
+import itertools, json, re
+from typing import Dict, List, Optional, Tuple
 from common.proto.context_pb2 import ConfigRule
 from common.tools.object_factory.ConfigRule import json_config_rule_set
 
@@ -71,7 +71,11 @@ def compose_l3nm_config_rules(main_service_config_rules : List, subservice_confi
 def compose_tapi_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None:
     compose_config_rules(main_service_config_rules, subservice_config_rules, TAPI_SETTINGS_FIELD_DEFAULTS)
 
-def compose_device_config_rules(config_rules : List, subservice_config_rules : List, path_hops : List) -> None:
+def compose_device_config_rules(
+    config_rules : List, subservice_config_rules : List, path_hops : List,
+    device_name_mapping : Dict[str, str], endpoint_name_mapping : Dict[Tuple[str, str], str]
+) -> None:
+
     endpoints_traversed = set()
     for path_hop in path_hops:
         device_uuid_or_name = path_hop['device']
@@ -82,8 +86,16 @@ def compose_device_config_rules(config_rules : List, subservice_config_rules : L
         if config_rule.WhichOneof('config_rule') != 'custom': continue
         match = DEV_EP_SETTINGS.match(config_rule.custom.resource_key)
         if match is None: continue
+
         device_uuid_or_name = match.group(1)
+        device_name_or_uuid = device_name_mapping[device_uuid_or_name]
+        device_keys = {device_uuid_or_name, device_name_or_uuid}
+
         endpoint_uuid_or_name = match.group(2)
-        dev_ep_kep = (device_uuid_or_name, endpoint_uuid_or_name)
-        if dev_ep_kep not in endpoints_traversed: continue
+        endpoint_name_or_uuid_1 = endpoint_name_mapping[(device_uuid_or_name, endpoint_uuid_or_name)]
+        endpoint_name_or_uuid_2 = endpoint_name_mapping[(device_name_or_uuid, endpoint_uuid_or_name)]
+        endpoint_keys = {endpoint_uuid_or_name, endpoint_name_or_uuid_1, endpoint_name_or_uuid_2}
+
+        device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys))
+        if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue
         subservice_config_rules.append(config_rule)
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py
index ee85f0bb083500c655e78798bbcd2bd00e8a4501..e2c6dc13804703d89242b27156763ce887aa4884 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py
@@ -118,11 +118,11 @@ def compose_link(grpc_link : Link) -> Dict:
         for link_endpoint_id in grpc_link.link_endpoint_ids
     ]
 
-    forwarding_direction = LinkForwardingDirection.BIDIRECTIONAL.value
+    forwarding_direction = LinkForwardingDirection.UNIDIRECTIONAL.value
     total_potential_capacity = compose_capacity(200, CapacityUnit.MBPS.value)
     available_capacity = compose_capacity(200, CapacityUnit.MBPS.value)
     cost_characteristics = compose_cost_characteristics('linkcost', '1', '0')
-    latency_characteristics = compose_latency_characteristics('2')
+    latency_characteristics = compose_latency_characteristics('1')
 
     return {
         'link_Id': link_uuid, 'link_endpoint_ids': endpoint_ids, 'forwarding_direction': forwarding_direction,
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py b/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py
index 75701b99e327792f0e97068c25b594976e1ebc9e..1f2b4df9ca91f8588d947608896316da753dd0be 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py
@@ -30,145 +30,41 @@
 # ]
 #
 # connections=[
-#     (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), ServiceTypeEnum.TAPI, [
+#     (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, [
 #             {'device': 'TN-OLS', 'ingress_ep': '833760219d0f', 'egress_ep': 'cf176771a4b9'}
 #         ], []),
-#     (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), ServiceTypeEnum.L2NM, [
+#     (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), ServiceTypeEnum.SERVICETYPE_L2NM, [
 #             {'device': 'CS1-GW1', 'ingress_ep': '10/1', 'egress_ep': '1/2'},
 #             {'device': 'TN-R2', 'ingress_ep': '1/2', 'egress_ep': '2/1'},
 #             {'device': 'TN-R3', 'ingress_ep': '2/1', 'egress_ep': '1/1'},
 #             {'device': 'CS2-GW1', 'ingress_ep': '1/1', 'egress_ep': '10/1'}
 #         ], [UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e')]),
-#     (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), ServiceTypeEnum.L2NM, [
+#     (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), ServiceTypeEnum.SERVICETYPE_L2NM, [
 #             {'device': 'DC1-GW', 'ingress_ep': 'int', 'egress_ep': 'eth1'},
 #             {'device': 'DC2-GW', 'ingress_ep': 'eth1', 'egress_ep': 'int'}
 #         ], [UUID('c2e57966-5d82-4705-a5fe-44cf6487219e')])
 # ]
 
-import enum, json, queue, uuid
+import logging, queue, uuid
 from typing import Dict, List, Optional, Tuple
 from common.DeviceTypes import DeviceTypeEnum
 from common.proto.context_pb2 import Device, ServiceTypeEnum
+from .ResourceGroups import IGNORED_DEVICE_TYPES, get_resource_classification
+from .ServiceTypes import get_service_type
 
-class StackActionEnum(enum.Enum):
-    PATH_INGRESS         = 'ingress'
-    CREATE_CONNECTION    = 'create'
-    APPEND_PATH_HOP      = 'append'
-    CHAIN_CONNECTION     = 'chain'
-    TERMINATE_CONNECTION = 'terminate'
-
-def is_datacenter(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.DATACENTER, DeviceTypeEnum.EMULATED_DATACENTER}
-
-def is_packet_router(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.PACKET_ROUTER, DeviceTypeEnum.EMULATED_PACKET_ROUTER}
-
-def is_packet_switch(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH}
-
-def is_packet_device(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return is_packet_router(dev_type) or is_packet_switch(dev_type)
-
-def is_tfs_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.TERAFLOWSDN_CONTROLLER}
-
-def is_mw_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM, DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM}
-
-def is_ipm_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.XR_CONSTELLATION, DeviceTypeEnum.EMULATED_XR_CONSTELLATION}
-
-def is_ols_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.OPEN_LINE_SYSTEM, DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM}
-
-def is_subdevice(dev_manager : Optional[str]) -> bool:
-    return dev_manager is not None
-
-def is_subdevice_equal(dev_manager_a : Optional[str], dev_manager_b : Optional[str]) -> bool:
-    if dev_manager_a is None and dev_manager_b is None: return True
-    if dev_manager_a is not None and dev_manager_b is not None: return dev_manager_a == dev_manager_b
-    return False
-
-def get_action(
-    prv_type : Optional[DeviceTypeEnum], prv_manager : Optional[str],
-    cur_type : DeviceTypeEnum, cur_manager : Optional[str]
-) -> StackActionEnum:
-    if prv_type is None:
-        return StackActionEnum.PATH_INGRESS
-
-    if is_datacenter(prv_type):
-        if is_packet_device(cur_type): return StackActionEnum.CREATE_CONNECTION
-        if is_tfs_controller(cur_type): return StackActionEnum.CREATE_CONNECTION
-
-    if is_packet_device(prv_type):
-        if is_datacenter(cur_type): return StackActionEnum.TERMINATE_CONNECTION
-        if is_packet_device(cur_type):
-            if is_subdevice_equal(cur_manager, prv_manager): return StackActionEnum.APPEND_PATH_HOP
-            if is_subdevice(prv_manager) and not is_subdevice(cur_manager): return StackActionEnum.TERMINATE_CONNECTION
-            if not is_subdevice(prv_manager) and is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION
-
-        if is_mw_controller(cur_type) and not is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION
-        if is_ols_controller(cur_type) and not is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION
-        if is_tfs_controller(cur_type) and is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION
-
-    if is_mw_controller(prv_type) or is_ols_controller(prv_type):
-        if is_packet_device(cur_type): return StackActionEnum.TERMINATE_CONNECTION
-
-    if is_tfs_controller(prv_type):
-        if is_tfs_controller(cur_type) and is_subdevice_equal(prv_manager, cur_manager): return StackActionEnum.APPEND_PATH_HOP
-        if is_datacenter(cur_type): return StackActionEnum.TERMINATE_CONNECTION
-        if is_packet_device(cur_type): return StackActionEnum.TERMINATE_CONNECTION
-        if is_mw_controller(cur_type) or is_ols_controller(cur_type): return StackActionEnum.CHAIN_CONNECTION
-
-    str_fields = ', '.join([
-        'prv_type={:s}'.format(str(prv_type)), 'prv_manager={:s}'.format(str(prv_manager)),
-        'cur_type={:s}'.format(str(cur_type)), 'cur_manager={:s}'.format(str(cur_manager)),
-    ])
-    raise Exception('Undefined Action for ({:s})'.format(str_fields))
-
-def get_device_manager_uuid(device : Device) -> Optional[str]:
-    for config_rule in device.device_config.config_rules:
-        if config_rule.WhichOneof('config_rule') != 'custom': continue
-        if config_rule.custom.resource_key != '_manager': continue
-        device_manager_id = json.loads(config_rule.custom.resource_value)
-        return device_manager_id['uuid']
-    return None
-
-def get_device_type(
-    grpc_device : Device, device_dict : Dict[str, Tuple[Dict, Device]], device_manager_uuid : Optional[str]
-) -> DeviceTypeEnum:
-    if device_manager_uuid is None:
-        return DeviceTypeEnum._value2member_map_[grpc_device.device_type] # pylint: disable=no-member
-    device_manager_tuple = device_dict.get(device_manager_uuid)
-    if device_manager_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_manager_uuid)))
-    _,grpc_device = device_manager_tuple
-    return DeviceTypeEnum._value2member_map_[grpc_device.device_type] # pylint: disable=no-member
-
-SERVICE_TYPE_LXNM = {ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM}
-
-def get_service_type(device_type : DeviceTypeEnum, prv_service_type : ServiceTypeEnum) -> ServiceTypeEnum:
-    if is_tfs_controller(device_type) or is_packet_router(device_type):
-        if prv_service_type in SERVICE_TYPE_LXNM: return prv_service_type
-    if is_packet_switch(device_type) or is_mw_controller(device_type):
-        if prv_service_type == ServiceTypeEnum.SERVICETYPE_L2NM: return prv_service_type
-    if is_ols_controller(device_type) or is_ipm_controller(device_type):
-        return ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE
-
-    str_fields = ', '.join([
-        'device_type={:s}'.format(str(device_type)),
-    ])
-    raise Exception('Undefined Service Type for ({:s})'.format(str_fields))
+LOGGER = logging.getLogger(__name__)
 
 def convert_explicit_path_hops_to_connections(
     path_hops : List[Dict], device_dict : Dict[str, Tuple[Dict, Device]],
     main_service_uuid : str, main_service_type : ServiceTypeEnum
 ) -> List[Tuple[str, int, List[str], List[str]]]:
 
+    LOGGER.debug('path_hops={:s}'.format(str(path_hops)))
+
     connection_stack = queue.LifoQueue()
     connections : List[Tuple[str, int, List[str], List[str]]] = list()
     prv_device_uuid = None
-    prv_device_type = None
-    prv_manager_uuid = None
+    prv_res_class : Tuple[Optional[int], Optional[DeviceTypeEnum], Optional[str]] = None, None, None
 
     for path_hop in path_hops:
         device_uuid = path_hop['device']
@@ -177,29 +73,35 @@ def convert_explicit_path_hops_to_connections(
         if device_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_uuid)))
         _,grpc_device = device_tuple
 
-        manager_uuid = get_device_manager_uuid(grpc_device)
-        device_type = get_device_type(grpc_device, device_dict, manager_uuid)
-        action = get_action(prv_device_type, prv_manager_uuid, device_type, manager_uuid)
+        res_class = get_resource_classification(grpc_device, device_dict)
+        if res_class[1] in IGNORED_DEVICE_TYPES: continue
 
-        if action == StackActionEnum.PATH_INGRESS:
+        if prv_res_class[0] is None:
+            # path ingress
             connection_stack.put((main_service_uuid, main_service_type, [path_hop], []))
-        elif action == StackActionEnum.CREATE_CONNECTION:
-            connection_uuid = str(uuid.uuid4())
-            prv_service_type = connection_stack.queue[-1][1]
-            service_type = get_service_type(device_type, prv_service_type)
-            connection_stack.put((connection_uuid, service_type, [path_hop], []))
-        elif action == StackActionEnum.APPEND_PATH_HOP:
-            connection_stack.queue[-1][2].append(path_hop)
-        elif action == StackActionEnum.CHAIN_CONNECTION:
-            connection = connection_stack.get()
-            connections.append(connection)
-            connection_stack.queue[-1][3].append(connection[0])
-
+        elif prv_res_class[0] > res_class[0]:
+            # create underlying connection
             connection_uuid = str(uuid.uuid4())
             prv_service_type = connection_stack.queue[-1][1]
-            service_type = get_service_type(device_type, prv_service_type)
+            service_type = get_service_type(res_class[1], prv_service_type)
             connection_stack.put((connection_uuid, service_type, [path_hop], []))
-        elif action == StackActionEnum.TERMINATE_CONNECTION:
+        elif prv_res_class[0] == res_class[0]:
+            # same resource group kind
+            if prv_res_class[1] == res_class[1] and prv_res_class[2] == res_class[2]:
+                # same device type and device manager: connection continues
+                connection_stack.queue[-1][2].append(path_hop)
+            else:
+                # different device type or device manager: chain connections
+                connection = connection_stack.get()
+                connections.append(connection)
+                connection_stack.queue[-1][3].append(connection[0])
+
+                connection_uuid = str(uuid.uuid4())
+                prv_service_type = connection_stack.queue[-1][1]
+                service_type = get_service_type(res_class[1], prv_service_type)
+                connection_stack.put((connection_uuid, service_type, [path_hop], []))
+        elif prv_res_class[0] < res_class[0]:
+            # underlying connection ended
             connection = connection_stack.get()
             connections.append(connection)
             connection_stack.queue[-1][3].append(connection[0])
@@ -208,11 +110,11 @@ def convert_explicit_path_hops_to_connections(
             raise Exception('Uncontrolled condition')
 
         prv_device_uuid = device_uuid
-        prv_device_type = device_type
-        prv_manager_uuid = manager_uuid
+        prv_res_class = res_class
 
     # path egress
     connections.append(connection_stack.get())
+    LOGGER.debug('connections={:s}'.format(str(connections)))
     assert connection_stack.empty()
     return connections
 
diff --git a/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py b/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py
index c8a902999ddfb5011fd7ec09fa99ff6fa697ea40..670757d76b7d21ecf28f6ead4e8bc4e21951d18e 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py
@@ -43,13 +43,17 @@
 #
 
 import logging
-from typing import Dict, List
+from typing import Dict, List, Tuple
+from common.proto.context_pb2 import Link
 
 LOGGER = logging.getLogger(__name__)
 
-def eropath_to_hops(ero_path : List[Dict], endpoint_to_link_dict : Dict) -> List[Dict]:
+def eropath_to_hops(
+    ero_path : List[Dict], endpoint_to_link_dict : Dict[Tuple[str, str, str], Tuple[Dict, Link]]
+) -> List[Dict]:
     try:
         path_hops = []
+        num_ero_hops = len(ero_path)
         for endpoint in ero_path:
             device_uuid = endpoint['device_id']
             endpoint_uuid = endpoint['endpoint_uuid']
@@ -59,23 +63,17 @@ def eropath_to_hops(ero_path : List[Dict], endpoint_to_link_dict : Dict) -> List
                 continue
 
             last_hop = path_hops[-1]
-            if (last_hop['device'] == device_uuid):
-                if ('ingress_ep' not in last_hop) or ('egress_ep' in last_hop): continue
-                last_hop['egress_ep'] = endpoint_uuid
-                continue
+            if last_hop['device'] != device_uuid: raise Exception('Malformed path')
+            last_hop['egress_ep'] = endpoint_uuid
+
+            if num_ero_hops - 1 == len(path_hops): break
 
-            endpoint_key = (last_hop['device'], last_hop['egress_ep'])
-            link_tuple = endpoint_to_link_dict.get(endpoint_key)
-            ingress = next(iter([
-                ep_id for ep_id in link_tuple[0]['link_endpoint_ids']
-                if (ep_id['endpoint_id']['device_id'] == device_uuid) and\
-                    (ep_id['endpoint_id']['endpoint_uuid'] != endpoint_uuid)
-            ]), None)
-            if ingress['endpoint_id']['device_id'] != device_uuid: raise Exception('Malformed path')
+            link_tuple = endpoint_to_link_dict[(device_uuid, endpoint_uuid, 'src')]
+            if link_tuple is None: raise Exception('Malformed path')
+            ingress = link_tuple[0]['link_endpoint_ids'][-1]
             path_hops.append({
                 'device': ingress['endpoint_id']['device_id'],
-                'ingress_ep': ingress['endpoint_id']['endpoint_uuid'],
-                'egress_ep': endpoint_uuid,
+                'ingress_ep': ingress['endpoint_id']['endpoint_uuid']
             })
         return path_hops
     except:
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py b/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f6ee63dc14352a5af36cbac84929c5b4381a074
--- /dev/null
+++ b/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py
@@ -0,0 +1,94 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import json
+from typing import Dict, Optional, Tuple
+from common.DeviceTypes import DeviceTypeEnum
+from common.proto.context_pb2 import Device
+from common.tools.grpc.Tools import grpc_message_to_json_string
+
+DEVICE_TYPE_TO_DEEPNESS = {
+    DeviceTypeEnum.EMULATED_DATACENTER.value             : 90,
+    DeviceTypeEnum.DATACENTER.value                      : 90,
+    DeviceTypeEnum.NETWORK.value                         : 90,
+
+    DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value          : 80,
+    DeviceTypeEnum.EMULATED_PACKET_ROUTER.value          : 70,
+    DeviceTypeEnum.PACKET_ROUTER.value                   : 70,
+
+    DeviceTypeEnum.EMULATED_PACKET_SWITCH.value          : 60,
+    DeviceTypeEnum.PACKET_SWITCH.value                   : 60,
+    DeviceTypeEnum.EMULATED_P4_SWITCH.value              : 60,
+    DeviceTypeEnum.P4_SWITCH.value                       : 60,
+
+    DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM.value : 40,
+    DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value          : 40,
+
+    DeviceTypeEnum.EMULATED_XR_CONSTELLATION.value       : 40,
+    DeviceTypeEnum.XR_CONSTELLATION.value                : 40,
+
+    DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM.value       : 30,
+    DeviceTypeEnum.OPEN_LINE_SYSTEM.value                : 30,
+
+    DeviceTypeEnum.EMULATED_PACKET_RADIO_ROUTER.value    : 10,
+    DeviceTypeEnum.PACKET_RADIO_ROUTER.value             : 10,
+    DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER.value    : 10,
+    DeviceTypeEnum.OPTICAL_TRANSPONDER.value             : 10,
+    DeviceTypeEnum.EMULATED_OPTICAL_ROADM.value          : 10,
+    DeviceTypeEnum.OPTICAL_ROADM.value                   : 10,
+
+    DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER.value       :  0,
+}
+
+IGNORED_DEVICE_TYPES = {DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER}
+
+def get_device_manager_uuid(
+    device : Device
+) -> Optional[str]:
+    for config_rule in device.device_config.config_rules:
+        if config_rule.WhichOneof('config_rule') != 'custom': continue
+        if config_rule.custom.resource_key != '_manager': continue
+        device_manager_id = json.loads(config_rule.custom.resource_value)
+        return device_manager_id['uuid']
+    return None
+
+def _map_device_type(device : Device) -> DeviceTypeEnum:
+    device_type = DeviceTypeEnum._value2member_map_.get(device.device_type) # pylint: disable=no-member
+    if device_type is None:
+        MSG = 'Unsupported DeviceType({:s}) for Device({:s})'
+        raise Exception(MSG.format(str(device.device_type), grpc_message_to_json_string(device)))
+    return device_type
+
+def _map_resource_to_deepness(device_type : DeviceTypeEnum) -> int:
+    deepness = DEVICE_TYPE_TO_DEEPNESS.get(device_type.value)
+    if deepness is None: raise Exception('Unsupported DeviceType({:s})'.format(str(device_type.value)))
+    return deepness
+
+def get_device_type(
+    device : Device, device_dict : Dict[str, Tuple[Dict, Device]], device_manager_uuid : Optional[str]
+) -> DeviceTypeEnum:
+    if device_manager_uuid is None: return _map_device_type(device)
+    device_manager_tuple = device_dict.get(device_manager_uuid)
+    if device_manager_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_manager_uuid)))
+    _,device = device_manager_tuple
+    return _map_device_type(device)
+
+def get_resource_classification(
+    device : Device, device_dict : Dict[str, Tuple[Dict, Device]]
+) -> Tuple[int, DeviceTypeEnum, Optional[str]]:
+    device_manager_uuid = get_device_manager_uuid(device)
+    device_type = get_device_type(device, device_dict, device_manager_uuid)
+    resource_deepness = _map_resource_to_deepness(device_type)
+    return resource_deepness, device_type, device_manager_uuid
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py b/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py
new file mode 100644
index 0000000000000000000000000000000000000000..463b8039b6c8c611b579bdb74933c06fb0f99507
--- /dev/null
+++ b/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py
@@ -0,0 +1,53 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from common.DeviceTypes import DeviceTypeEnum
+from common.proto.context_pb2 import ServiceTypeEnum
+
+PACKET_DEVICE_TYPES = {
+    DeviceTypeEnum.TERAFLOWSDN_CONTROLLER,
+    DeviceTypeEnum.PACKET_ROUTER, DeviceTypeEnum.EMULATED_PACKET_ROUTER,
+    DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH,
+}
+
+L2_DEVICE_TYPES = {
+    DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH,
+    DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM, DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM,
+    DeviceTypeEnum.PACKET_RADIO_ROUTER, DeviceTypeEnum.EMULATED_PACKET_RADIO_ROUTER,
+    DeviceTypeEnum.P4_SWITCH, DeviceTypeEnum.EMULATED_P4_SWITCH,
+}
+
+OPTICAL_DEVICE_TYPES = {
+    DeviceTypeEnum.OPEN_LINE_SYSTEM, DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM,
+    DeviceTypeEnum.XR_CONSTELLATION, DeviceTypeEnum.EMULATED_XR_CONSTELLATION,
+    DeviceTypeEnum.OPTICAL_ROADM, DeviceTypeEnum.EMULATED_OPTICAL_ROADM,
+    DeviceTypeEnum.OPTICAL_TRANSPONDER, DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER,
+}
+
+SERVICE_TYPE_L2NM = {ServiceTypeEnum.SERVICETYPE_L2NM}
+SERVICE_TYPE_L3NM = {ServiceTypeEnum.SERVICETYPE_L3NM}
+SERVICE_TYPE_LXNM = {ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM}
+SERVICE_TYPE_TAPI = {ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE}
+
+def get_service_type(device_type : DeviceTypeEnum, prv_service_type : ServiceTypeEnum) -> ServiceTypeEnum:
+    if device_type in PACKET_DEVICE_TYPES and prv_service_type in SERVICE_TYPE_LXNM: return prv_service_type
+    if device_type in L2_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_L2NM
+    if device_type in OPTICAL_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE
+
+    str_fields = ', '.join([
+        'device_type={:s}'.format(str(device_type)),
+        'prv_service_type={:s}'.format(str(prv_service_type)),
+    ])
+    raise Exception('Undefined Service Type for ({:s})'.format(str_fields))
diff --git a/test_pathcomp/ComputeSubServices.py b/test_pathcomp/ComputeSubServices.py
index e0b229625838dd3f9300f4fde774b310c7511f53..1f2b4df9ca91f8588d947608896316da753dd0be 100644
--- a/test_pathcomp/ComputeSubServices.py
+++ b/test_pathcomp/ComputeSubServices.py
@@ -30,167 +30,41 @@
 # ]
 #
 # connections=[
-#     (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), <DeviceLayerEnum.OPTICAL_CONTROLLER: 1>, [
+#     (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, [
 #             {'device': 'TN-OLS', 'ingress_ep': '833760219d0f', 'egress_ep': 'cf176771a4b9'}
 #         ], []),
-#     (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), <DeviceLayerEnum.PACKET_DEVICE: 30>, [
+#     (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), ServiceTypeEnum.SERVICETYPE_L2NM, [
 #             {'device': 'CS1-GW1', 'ingress_ep': '10/1', 'egress_ep': '1/2'},
 #             {'device': 'TN-R2', 'ingress_ep': '1/2', 'egress_ep': '2/1'},
 #             {'device': 'TN-R3', 'ingress_ep': '2/1', 'egress_ep': '1/1'},
 #             {'device': 'CS2-GW1', 'ingress_ep': '1/1', 'egress_ep': '10/1'}
 #         ], [UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e')]),
-#     (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), <DeviceLayerEnum.APPLICATION_DEVICE: 40>, [
+#     (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), ServiceTypeEnum.SERVICETYPE_L2NM, [
 #             {'device': 'DC1-GW', 'ingress_ep': 'int', 'egress_ep': 'eth1'},
 #             {'device': 'DC2-GW', 'ingress_ep': 'eth1', 'egress_ep': 'int'}
 #         ], [UUID('c2e57966-5d82-4705-a5fe-44cf6487219e')])
 # ]
 
-import enum, json, queue, uuid
+import logging, queue, uuid
 from typing import Dict, List, Optional, Tuple
 from common.DeviceTypes import DeviceTypeEnum
-from common.proto.context_pb2 import Device, ServiceTypeEnum #, DeviceDriverEnum as grpc_DeviceDriverEnum
-#from .ConstantsMappings import DEVICE_TYPE_TO_LAYER, DeviceLayerEnum
-
-class StackActionEnum(enum.Enum):
-    PATH_INGRESS         = 'ingress'
-    CREATE_CONNECTION    = 'create'
-    APPEND_PATH_HOP      = 'append'
-    CHAIN_CONNECTION     = 'chain'
-    TERMINATE_CONNECTION = 'terminate'
-
-#class DeviceDriverEnum(enum.IntEnum):
-#    EMULATED              = grpc_DeviceDriverEnum.DEVICEDRIVER_UNDEFINED
-#    OPENCONFIG            = grpc_DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG
-#    TRANSPORT_API         = grpc_DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API
-#    P4                    = grpc_DeviceDriverEnum.DEVICEDRIVER_P4
-#    IETF_NETWORK_TOPOLOGY = grpc_DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY
-#    ONF_TR_352            = grpc_DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352
-#    XR                    = grpc_DeviceDriverEnum.DEVICEDRIVER_XR
-#    IETF_L2VPN            = grpc_DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN
-
-def is_datacenter(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.DATACENTER, DeviceTypeEnum.EMULATED_DATACENTER}
-
-def is_packet_router(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.PACKET_ROUTER, DeviceTypeEnum.EMULATED_PACKET_ROUTER}
-
-def is_packet_switch(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH}
-
-def is_packet_device(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return is_packet_router(dev_type) or is_packet_switch(dev_type)
-
-def is_tfs_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.TERAFLOWSDN_CONTROLLER}
-
-def is_mw_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM, DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM}
-
-def is_ipm_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.XR_CONSTELLATION, DeviceTypeEnum.EMULATED_XR_CONSTELLATION}
-
-def is_ols_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
-    return dev_type in {DeviceTypeEnum.OPEN_LINE_SYSTEM, DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM}
-
-def is_subdevice(dev_manager : Optional[str]) -> bool:
-    return dev_manager is not None
-
-def is_subdevice_equal(dev_manager_a : Optional[str], dev_manager_b : Optional[str]) -> bool:
-    if dev_manager_a is None and dev_manager_b is None: return True
-    if dev_manager_a is not None and dev_manager_b is not None: return dev_manager_a == dev_manager_b
-    return False
-
-#def has_driver(dev_drivers : List[DeviceDriverEnum], dev_driver : DeviceDriverEnum) -> bool:
-#    return dev_driver in dev_drivers
-
-def get_action(
-    prv_type : Optional[DeviceTypeEnum], prv_manager : Optional[str],
-    cur_type : DeviceTypeEnum, cur_manager : Optional[str]
-) -> StackActionEnum:
-    if prv_type is None:
-        return StackActionEnum.PATH_INGRESS
-
-    if is_datacenter(prv_type):
-        if is_packet_device(cur_type): return StackActionEnum.CREATE_CONNECTION
-        if is_tfs_controller(cur_type): return StackActionEnum.CREATE_CONNECTION
-
-    if is_packet_device(prv_type):
-        if is_datacenter(cur_type): return StackActionEnum.TERMINATE_CONNECTION
-        if is_packet_device(cur_type):
-            if is_subdevice_equal(cur_manager, prv_manager): return StackActionEnum.APPEND_PATH_HOP
-            if is_subdevice(prv_manager) and not is_subdevice(cur_manager): return StackActionEnum.TERMINATE_CONNECTION
-            if not is_subdevice(prv_manager) and is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION
-
-        if is_mw_controller(cur_type) and not is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION
-        if is_ols_controller(cur_type) and not is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION
-        if is_tfs_controller(cur_type) and is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION
-
-    if is_mw_controller(prv_type) or is_ols_controller(prv_type):
-        if is_packet_device(cur_type): return StackActionEnum.TERMINATE_CONNECTION
-
-    if is_tfs_controller(prv_type):
-        if is_tfs_controller(cur_type) and is_subdevice_equal(prv_manager, cur_manager): return StackActionEnum.APPEND_PATH_HOP
-        if is_datacenter(cur_type): return StackActionEnum.TERMINATE_CONNECTION
-        if is_packet_device(cur_type): return StackActionEnum.TERMINATE_CONNECTION
-        if is_mw_controller(cur_type) or is_ols_controller(cur_type): return StackActionEnum.CHAIN_CONNECTION
-
-    str_fields = ', '.join([
-        'prv_type={:s}'.format(str(prv_type)), 'prv_manager={:s}'.format(str(prv_manager)),
-        'cur_type={:s}'.format(str(cur_type)), 'cur_manager={:s}'.format(str(cur_manager)),
-    ])
-    raise Exception('Undefined Action for ({:s})'.format(str_fields))
-
-def get_device_manager_uuid(device : Device) -> Optional[str]:
-    for config_rule in device.device_config.config_rules:
-        if config_rule.WhichOneof('config_rule') != 'custom': continue
-        if config_rule.custom.resource_key != '_manager': continue
-        device_manager_id = json.loads(config_rule.custom.resource_value)
-        return device_manager_id['uuid']
-    return None
-
-def get_device_type(
-    grpc_device : Device, device_dict : Dict[str, Tuple[Dict, Device]], device_manager_uuid : Optional[str]
-) -> DeviceTypeEnum:
-    if device_manager_uuid is None:
-        return DeviceTypeEnum._value2member_map_[grpc_device.device_type] # pylint: disable=no-member
-    device_manager_tuple = device_dict.get(device_manager_uuid)
-    if device_manager_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_manager_uuid)))
-    _,grpc_device = device_manager_tuple
-    return DeviceTypeEnum._value2member_map_[grpc_device.device_type] # pylint: disable=no-member
-
-    #manager_drivers = set(grpc_device.device_drivers)
-    #if DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN in manager_drivers:
-    #    device_layer = DeviceLayerEnum.MAC_LAYER_CONTROLLER
-    #else:
-    #    device_type = json_device['device_type']
-    #    device_layer = DEVICE_TYPE_TO_LAYER.get(device_type)
-    #    if device_layer is None: raise Exception('Undefined Layer for DeviceType({:s})'.format(str(device_type)))
-
-SERVICE_TYPE_LXNM = {ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM}
-
-def get_service_type(device_type : DeviceTypeEnum, prv_service_type : ServiceTypeEnum) -> ServiceTypeEnum:
-    if is_tfs_controller(device_type) or is_packet_router(device_type):
-        if prv_service_type in SERVICE_TYPE_LXNM: return prv_service_type
-    if is_packet_switch(device_type) or is_mw_controller(device_type):
-        if prv_service_type == ServiceTypeEnum.SERVICETYPE_L2NM: return prv_service_type
-    if is_ols_controller(device_type) or is_ipm_controller(device_type):
-        return ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE
-
-    str_fields = ', '.join([
-        'device_type={:s}'.format(str(device_type)),
-    ])
-    raise Exception('Undefined Service Type for ({:s})'.format(str_fields))
+from common.proto.context_pb2 import Device, ServiceTypeEnum
+from .ResourceGroups import IGNORED_DEVICE_TYPES, get_resource_classification
+from .ServiceTypes import get_service_type
+
+LOGGER = logging.getLogger(__name__)
 
 def convert_explicit_path_hops_to_connections(
     path_hops : List[Dict], device_dict : Dict[str, Tuple[Dict, Device]],
     main_service_uuid : str, main_service_type : ServiceTypeEnum
 ) -> List[Tuple[str, int, List[str], List[str]]]:
 
+    LOGGER.debug('path_hops={:s}'.format(str(path_hops)))
+
     connection_stack = queue.LifoQueue()
     connections : List[Tuple[str, int, List[str], List[str]]] = list()
     prv_device_uuid = None
-    prv_device_type = None
-    prv_manager_uuid = None
+    prv_res_class : Tuple[Optional[int], Optional[DeviceTypeEnum], Optional[str]] = None, None, None
 
     for path_hop in path_hops:
         device_uuid = path_hop['device']
@@ -199,29 +73,35 @@ def convert_explicit_path_hops_to_connections(
         if device_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_uuid)))
         _,grpc_device = device_tuple
 
-        manager_uuid = get_device_manager_uuid(grpc_device)
-        device_type = get_device_type(grpc_device, device_dict, manager_uuid)
-        action = get_action(prv_device_type, prv_manager_uuid, device_type, manager_uuid)
+        res_class = get_resource_classification(grpc_device, device_dict)
+        if res_class[1] in IGNORED_DEVICE_TYPES: continue
 
-        if action == StackActionEnum.PATH_INGRESS:
+        if prv_res_class[0] is None:
+            # path ingress
             connection_stack.put((main_service_uuid, main_service_type, [path_hop], []))
-        elif action == StackActionEnum.CREATE_CONNECTION:
-            connection_uuid = str(uuid.uuid4())
-            prv_service_type = connection_stack.queue[-1][1]
-            service_type = get_service_type(device_type, prv_service_type)
-            connection_stack.put((connection_uuid, service_type, [path_hop], []))
-        elif action == StackActionEnum.APPEND_PATH_HOP:
-            connection_stack.queue[-1][2].append(path_hop)
-        elif action == StackActionEnum.CHAIN_CONNECTION:
-            connection = connection_stack.get()
-            connections.append(connection)
-            connection_stack.queue[-1][3].append(connection[0])
-
+        elif prv_res_class[0] > res_class[0]:
+            # create underlying connection
             connection_uuid = str(uuid.uuid4())
             prv_service_type = connection_stack.queue[-1][1]
-            service_type = get_service_type(device_type, prv_service_type)
+            service_type = get_service_type(res_class[1], prv_service_type)
             connection_stack.put((connection_uuid, service_type, [path_hop], []))
-        elif action == StackActionEnum.TERMINATE_CONNECTION:
+        elif prv_res_class[0] == res_class[0]:
+            # same resource group kind
+            if prv_res_class[1] == res_class[1] and prv_res_class[2] == res_class[2]:
+                # same device type and device manager: connection continues
+                connection_stack.queue[-1][2].append(path_hop)
+            else:
+                # different device type or device manager: chain connections
+                connection = connection_stack.get()
+                connections.append(connection)
+                connection_stack.queue[-1][3].append(connection[0])
+
+                connection_uuid = str(uuid.uuid4())
+                prv_service_type = connection_stack.queue[-1][1]
+                service_type = get_service_type(res_class[1], prv_service_type)
+                connection_stack.put((connection_uuid, service_type, [path_hop], []))
+        elif prv_res_class[0] < res_class[0]:
+            # underlying connection ended
             connection = connection_stack.get()
             connections.append(connection)
             connection_stack.queue[-1][3].append(connection[0])
@@ -230,11 +110,11 @@ def convert_explicit_path_hops_to_connections(
             raise Exception('Uncontrolled condition')
 
         prv_device_uuid = device_uuid
-        prv_device_type = device_type
-        prv_manager_uuid = manager_uuid
+        prv_res_class = res_class
 
     # path egress
     connections.append(connection_stack.get())
+    LOGGER.debug('connections={:s}'.format(str(connections)))
     assert connection_stack.empty()
     return connections
 
diff --git a/test_pathcomp/ResourceGroups.py b/test_pathcomp/ResourceGroups.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b38881443859417df8882c9c727114ce646239c
--- /dev/null
+++ b/test_pathcomp/ResourceGroups.py
@@ -0,0 +1,93 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+from typing import Dict, Optional, Tuple
+from common.DeviceTypes import DeviceTypeEnum
+from common.proto.context_pb2 import Device
+from common.tools.grpc.Tools import grpc_message_to_json_string
+
+DEVICE_TYPE_TO_DEEPNESS = {
+    DeviceTypeEnum.EMULATED_DATACENTER.value             : 90,
+    DeviceTypeEnum.DATACENTER.value                      : 90,
+    DeviceTypeEnum.NETWORK.value                         : 90,
+
+    DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value          : 80,
+    DeviceTypeEnum.EMULATED_PACKET_ROUTER.value          : 70,
+    DeviceTypeEnum.PACKET_ROUTER.value                   : 70,
+
+    DeviceTypeEnum.EMULATED_PACKET_SWITCH.value          : 60,
+    DeviceTypeEnum.PACKET_SWITCH.value                   : 60,
+    DeviceTypeEnum.EMULATED_P4_SWITCH.value              : 60,
+    DeviceTypeEnum.P4_SWITCH.value                       : 60,
+
+    DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM.value : 40,
+    DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value          : 40,
+
+    DeviceTypeEnum.EMULATED_XR_CONSTELLATION.value       : 40,
+    DeviceTypeEnum.XR_CONSTELLATION.value                : 40,
+
+    DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM.value       : 30,
+    DeviceTypeEnum.OPEN_LINE_SYSTEM.value                : 30,
+
+    DeviceTypeEnum.EMULATED_PACKET_RADIO_ROUTER.value    : 10,
+    DeviceTypeEnum.PACKET_RADIO_ROUTER.value             : 10,
+    DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER.value    : 10,
+    DeviceTypeEnum.OPTICAL_TRANSPONDER.value             : 10,
+    DeviceTypeEnum.EMULATED_OPTICAL_ROADM.value          : 10,
+    DeviceTypeEnum.OPTICAL_ROADM.value                   : 10,
+
+    DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER.value       :  0,
+}
+
+IGNORED_DEVICE_TYPES = {DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER}
+
+def get_device_manager_uuid(
+    device : Device
+) -> Optional[str]:
+    for config_rule in device.device_config.config_rules:
+        if config_rule.WhichOneof('config_rule') != 'custom': continue
+        if config_rule.custom.resource_key != '_manager': continue
+        device_manager_id = json.loads(config_rule.custom.resource_value)
+        return device_manager_id['uuid']
+    return None
+
+def _map_device_type(device : Device) -> DeviceTypeEnum:
+    device_type = DeviceTypeEnum._value2member_map_.get(device.device_type) # pylint: disable=no-member
+    if device_type is None:
+        MSG = 'Unsupported DeviceType({:s}) for Device({:s})'
+        raise Exception(MSG.format(str(device.device_type), grpc_message_to_json_string(device)))
+    return device_type
+
+def _map_resource_to_deepness(device_type : DeviceTypeEnum) -> int:
+    deepness = DEVICE_TYPE_TO_DEEPNESS.get(device_type.value)
+    if deepness is None: raise Exception('Unsupported DeviceType({:s})'.format(str(device_type.value)))
+    return deepness
+
+def get_device_type(
+    device : Device, device_dict : Dict[str, Tuple[Dict, Device]], device_manager_uuid : Optional[str]
+) -> DeviceTypeEnum:
+    if device_manager_uuid is None: return _map_device_type(device)
+    device_manager_tuple = device_dict.get(device_manager_uuid)
+    if device_manager_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_manager_uuid)))
+    _,device = device_manager_tuple
+    return _map_device_type(device)
+
+def get_resource_classification(
+    device : Device, device_dict : Dict[str, Tuple[Dict, Device]]
+) -> Tuple[int, DeviceTypeEnum, Optional[str]]:
+    device_manager_uuid = get_device_manager_uuid(device)
+    device_type = get_device_type(device, device_dict, device_manager_uuid)
+    resource_deepness = _map_resource_to_deepness(device_type)
+    return resource_deepness, device_type, device_manager_uuid
diff --git a/test_pathcomp/ServiceTypes.py b/test_pathcomp/ServiceTypes.py
new file mode 100644
index 0000000000000000000000000000000000000000..463b8039b6c8c611b579bdb74933c06fb0f99507
--- /dev/null
+++ b/test_pathcomp/ServiceTypes.py
@@ -0,0 +1,53 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from common.DeviceTypes import DeviceTypeEnum
+from common.proto.context_pb2 import ServiceTypeEnum
+
+PACKET_DEVICE_TYPES = {
+    DeviceTypeEnum.TERAFLOWSDN_CONTROLLER,
+    DeviceTypeEnum.PACKET_ROUTER, DeviceTypeEnum.EMULATED_PACKET_ROUTER,
+    DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH,
+}
+
+L2_DEVICE_TYPES = {
+    DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH,
+    DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM, DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM,
+    DeviceTypeEnum.PACKET_RADIO_ROUTER, DeviceTypeEnum.EMULATED_PACKET_RADIO_ROUTER,
+    DeviceTypeEnum.P4_SWITCH, DeviceTypeEnum.EMULATED_P4_SWITCH,
+}
+
+OPTICAL_DEVICE_TYPES = {
+    DeviceTypeEnum.OPEN_LINE_SYSTEM, DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM,
+    DeviceTypeEnum.XR_CONSTELLATION, DeviceTypeEnum.EMULATED_XR_CONSTELLATION,
+    DeviceTypeEnum.OPTICAL_ROADM, DeviceTypeEnum.EMULATED_OPTICAL_ROADM,
+    DeviceTypeEnum.OPTICAL_TRANSPONDER, DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER,
+}
+
+SERVICE_TYPE_L2NM = {ServiceTypeEnum.SERVICETYPE_L2NM}
+SERVICE_TYPE_L3NM = {ServiceTypeEnum.SERVICETYPE_L3NM}
+SERVICE_TYPE_LXNM = {ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM}
+SERVICE_TYPE_TAPI = {ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE}
+
+def get_service_type(device_type : DeviceTypeEnum, prv_service_type : ServiceTypeEnum) -> ServiceTypeEnum:
+    if device_type in PACKET_DEVICE_TYPES and prv_service_type in SERVICE_TYPE_LXNM: return prv_service_type
+    if device_type in L2_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_L2NM
+    if device_type in OPTICAL_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE
+
+    str_fields = ', '.join([
+        'device_type={:s}'.format(str(device_type)),
+        'prv_service_type={:s}'.format(str(prv_service_type)),
+    ])
+    raise Exception('Undefined Service Type for ({:s})'.format(str_fields))
diff --git a/test_pathcomp/data.py b/test_pathcomp/data.py
index 5e94d969e31e86a572300483ca9b603cbf1d1417..399c5ff601e0ba183a12f8e3773a542ab5a5cac5 100644
--- a/test_pathcomp/data.py
+++ b/test_pathcomp/data.py
@@ -15,101 +15,56 @@
 
 import json
 from typing import Dict, Tuple
+from common.DeviceTypes import DeviceTypeEnum
 from common.proto.context_pb2 import ConfigActionEnum, Device
 
 path_hops = [
-    {'device': 'DC1', 'ingress_ep': 'int', 'egress_ep': 'eth1'},
-    {'device': 'PE1', 'ingress_ep': '1/1', 'egress_ep': '1/2'},
-    {'device': 'MW1-2', 'ingress_ep': '172.18.0.1:1', 'egress_ep': '172.18.0.2:1'},
-    {'device': 'R1', 'ingress_ep': '1/1', 'egress_ep': '1/3'},
-    {'device': 'OLS', 'ingress_ep': 'aade6001-f00b-5e2f-a357-6a0a9d3de870', 'egress_ep': '0ef74f99-1acc-57bd-ab9d-4b958b06c513'},
-    {'device': 'R2', 'ingress_ep': '1/1', 'egress_ep': '1/2'},
-    {'device': 'PE3', 'ingress_ep': '1/1', 'egress_ep': '1/2'},
-    {'device': 'DC2', 'ingress_ep': 'eth1', 'egress_ep': 'int'}
+    {'device': 'DC1',      'ingress_ep': 'int',                  'egress_ep': 'eth1'                 },
+    {'device': 'PE1',      'ingress_ep': '1/1',                  'egress_ep': '1/2'                  },
+    {'device': 'MW1-2',    'ingress_ep': '172.18.0.1:1',         'egress_ep': '172.18.0.2:1'         },
+    {'device': 'HUB1',     'ingress_ep': '1/1',                  'egress_ep': 'XR-T1'                },
+    {'device': 'splitter', 'ingress_ep': 'common',               'egress_ep': 'leaf1'                },
+    {'device': 'OLS',      'ingress_ep': 'node_1_port_13-input', 'egress_ep': 'node_4_port_13-output'},
+    {'device': 'LEAF2',    'ingress_ep': 'XR-T1',                'egress_ep': '1/1'                  },
+    {'device': 'PE4',      'ingress_ep': '1/1',                  'egress_ep': '1/2'                  },
+    {'device': 'DC2',      'ingress_ep': 'eth2',                 'egress_ep': 'int'                  }
 ]
 
-device_dict = {
-    'R3': {'device_Id': 'R3', 'device_type': 'emu-packet-router', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'R3', 'endpoint_uuid': '1/1'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'R3', 'endpoint_uuid': '1/2'}, 'endpoint_type': 'copper/internal'}
-    ]},
-    'PE4': {'device_Id': 'PE4', 'device_type': 'emu-packet-router', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'PE4', 'endpoint_uuid': 'mgmt'}, 'endpoint_type': 'mgmt'},
-        {'endpoint_id': {'device_id': 'PE4', 'endpoint_uuid': '1/1'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'PE4', 'endpoint_uuid': '1/2'}, 'endpoint_type': 'copper/internal'}
-    ]},
-    'PE2': {'device_Id': 'PE2', 'device_type': 'emu-packet-router', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'PE2', 'endpoint_uuid': '1/1'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'PE2', 'endpoint_uuid': 'mgmt'}, 'endpoint_type': 'mgmt'},
-        {'endpoint_id': {'device_id': 'PE2', 'endpoint_uuid': '1/2'}, 'endpoint_type': 'copper/internal'}
-    ]},
-    'R1': {'device_Id': 'R1', 'device_type': 'emu-packet-router', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'R1', 'endpoint_uuid': '1/1'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'R1', 'endpoint_uuid': '1/3'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'R1', 'endpoint_uuid': '1/2'}, 'endpoint_type': 'copper/internal'}
-    ]},
-    'PE3': {'device_Id': 'PE3', 'device_type': 'emu-packet-router', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'PE3', 'endpoint_uuid': '1/1'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'PE3', 'endpoint_uuid': '1/2'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'PE3', 'endpoint_uuid': 'mgmt'}, 'endpoint_type': 'mgmt'}
-    ]},
-    'OLS': {'device_Id': 'OLS', 'device_type': 'emu-open-line-system', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'OLS', 'endpoint_uuid': '0ef74f99-1acc-57bd-ab9d-4b958b06c513'}, 'endpoint_type': 'optical'},
-        {'endpoint_id': {'device_id': 'OLS', 'endpoint_uuid': '50296d99-58cc-5ce7-82f5-fc8ee4eec2ec'}, 'endpoint_type': 'optical'},
-        {'endpoint_id': {'device_id': 'OLS', 'endpoint_uuid': 'aade6001-f00b-5e2f-a357-6a0a9d3de870'}, 'endpoint_type': 'optical'},
-        {'endpoint_id': {'device_id': 'OLS', 'endpoint_uuid': 'eb287d83-f05e-53ec-ab5a-adf6bd2b5418'}, 'endpoint_type': 'optical'}
-    ]},
-    'PE1': {'device_Id': 'PE1', 'device_type': 'emu-packet-router', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'PE1', 'endpoint_uuid': '1/2'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'PE1', 'endpoint_uuid': 'mgmt'}, 'endpoint_type': 'mgmt'},
-        {'endpoint_id': {'device_id': 'PE1', 'endpoint_uuid': '1/1'}, 'endpoint_type': 'copper/internal'}
-    ]},
-    'DC2': {'device_Id': 'DC2', 'device_type': 'emu-datacenter', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'DC2', 'endpoint_uuid': 'eth1'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'DC2', 'endpoint_uuid': 'eth2'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'DC2', 'endpoint_uuid': 'int'}, 'endpoint_type': 'copper/internal'}
-    ]},
-    'MW1-2': {'device_Id': 'MW1-2', 'device_type': 'emu-microwave-radio-system', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'MW1-2', 'endpoint_uuid': '172.18.0.1:1'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'MW1-2', 'endpoint_uuid': '172.18.0.2:1'}, 'endpoint_type': 'copper/internal'}
-    ]},
-    'MW3-4': {'device_Id': 'MW3-4', 'device_type': 'emu-microwave-radio-system', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'MW3-4', 'endpoint_uuid': '172.18.0.3:1'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'MW3-4', 'endpoint_uuid': '172.18.0.4:1'}, 'endpoint_type': 'copper/internal'}
-    ]},
-    'TFS': {'device_Id': 'TFS', 'device_type': 'teraflowsdn', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'TFS', 'endpoint_uuid': 'mgmt'}, 'endpoint_type': 'mgmt'}
-    ]},
-    'R2': {'device_Id': 'R2', 'device_type': 'emu-packet-router', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'R2', 'endpoint_uuid': '1/2'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'R2', 'endpoint_uuid': '1/1'}, 'endpoint_type': 'copper/internal'}
-    ]},
-    'DC1': {'device_Id': 'DC1', 'device_type': 'emu-datacenter', 'device_endpoints': [
-        {'endpoint_id': {'device_id': 'DC1', 'endpoint_uuid': 'int'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'DC1', 'endpoint_uuid': 'eth1'}, 'endpoint_type': 'copper/internal'},
-        {'endpoint_id': {'device_id': 'DC1', 'endpoint_uuid': 'eth2'}, 'endpoint_type': 'copper/internal'}
-    ]},
-}
+device_data = {
+    'TFS'     : {'manager_uuid': None,  'device_type': DeviceTypeEnum.TERAFLOWSDN_CONTROLLER   },
+    'IPM'     : {'manager_uuid': None,  'device_type': DeviceTypeEnum.XR_CONSTELLATION         },
+    'OLS'     : {'manager_uuid': None,  'device_type': DeviceTypeEnum.OPEN_LINE_SYSTEM         },
+    'MW1-2'   : {'manager_uuid': None,  'device_type': DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM   },
+    'MW3-4'   : {'manager_uuid': None,  'device_type': DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM   },
 
-MANAGED_DEVICES = {'PE1', 'PE2', 'PE3', 'PE4'}
-MANAGER = 'TFS'
+    'DC1'     : {'manager_uuid': None,  'device_type': DeviceTypeEnum.EMULATED_DATACENTER      },
+    'DC2'     : {'manager_uuid': None,  'device_type': DeviceTypeEnum.EMULATED_DATACENTER      },
 
-def process_device(json_device) -> Tuple[Dict, Device]:
-    device_uuid = json_device['device_Id']
+    'PE1'     : {'manager_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+    'PE2'     : {'manager_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+    'PE3'     : {'manager_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+    'PE4'     : {'manager_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
 
-    grpc_device = Device()
-    grpc_device.device_id.device_uuid.uuid = device_uuid
-    grpc_device.device_type = json_device['device_type']
+    'HUB1'    : {'manager_uuid': 'IPM', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+    'LEAF1'   : {'manager_uuid': 'IPM', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+    'LEAF2'   : {'manager_uuid': 'IPM', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
 
-    if device_uuid in MANAGED_DEVICES:
-        config_rule = grpc_device.device_config.config_rules.add()
+    'splitter': {'manager_uuid': None,  'device_type': DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER},
+}
+
+def process_device(device_uuid, json_device) -> Tuple[Dict, Device]:
+    grpc_device = Device()
+    grpc_device.device_id.device_uuid.uuid = device_uuid            # pylint: disable=no-member
+    grpc_device.device_type = json_device['device_type'].value
+    manager_uuid = json_device.get('manager_uuid')
+    if manager_uuid is not None:
+        config_rule = grpc_device.device_config.config_rules.add()  # pylint: disable=no-member
         config_rule.action = ConfigActionEnum.CONFIGACTION_SET
         config_rule.custom.resource_key = '_manager'
-        config_rule.custom.resource_value = json.dumps({'uuid': MANAGER})
-
+        config_rule.custom.resource_value = json.dumps({'uuid': manager_uuid})
     return json_device, grpc_device
 
 device_dict = {
-    device_uuid:process_device(json_device)
-    for device_uuid,json_device in device_dict.items()
+    device_uuid:process_device(device_uuid, json_device)
+    for device_uuid,json_device in device_data.items()
 }
diff --git a/test_pathcomp/old_ComputeSubServices.py b/test_pathcomp/old_ComputeSubServices.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1d3115d4c72e807e0d68c9caeacd153331e8c4f
--- /dev/null
+++ b/test_pathcomp/old_ComputeSubServices.py
@@ -0,0 +1,119 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Convert the path defined as explicit hops with ingress and egress endpoints per device into a set of connections and
+# compute the dependencies among them.
+#
+# Example:
+# o-- int DC1 eth1 -- 10/1 CS1 1/2 -- 1/2 R2 2/1 -- a7.. OLS 60.. -- 2/1 R3 1/1 -- 1/1 CS2 10/1 -- eth1 DC2 int --o
+#         APP              PKT            PKT            CTRL            PKT           PKT              APP
+#
+# path_hops = [
+#     {'device': 'DC1-GW', 'ingress_ep': 'int', 'egress_ep': 'eth1'},
+#     {'device': 'CS1-GW1', 'ingress_ep': '10/1', 'egress_ep': '1/2'},
+#     {'device': 'TN-R2', 'ingress_ep': '1/2', 'egress_ep': '2/1'},
+#     {'device': 'TN-OLS', 'ingress_ep': 'a7a80b23a703', 'egress_ep': '60519106029e'},
+#     {'device': 'TN-R3', 'ingress_ep': '2/1', 'egress_ep': '1/1'},
+#     {'device': 'CS2-GW1', 'ingress_ep': '1/1', 'egress_ep': '10/1'},
+#     {'device': 'DC2-GW', 'ingress_ep': 'eth1', 'egress_ep': 'int'}
+# ]
+#
+# connections=[
+#     (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, [
+#             {'device': 'TN-OLS', 'ingress_ep': '833760219d0f', 'egress_ep': 'cf176771a4b9'}
+#         ], []),
+#     (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), ServiceTypeEnum.SERVICETYPE_L2NM, [
+#             {'device': 'CS1-GW1', 'ingress_ep': '10/1', 'egress_ep': '1/2'},
+#             {'device': 'TN-R2', 'ingress_ep': '1/2', 'egress_ep': '2/1'},
+#             {'device': 'TN-R3', 'ingress_ep': '2/1', 'egress_ep': '1/1'},
+#             {'device': 'CS2-GW1', 'ingress_ep': '1/1', 'egress_ep': '10/1'}
+#         ], [UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e')]),
+#     (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), ServiceTypeEnum.SERVICETYPE_L2NM, [
+#             {'device': 'DC1-GW', 'ingress_ep': 'int', 'egress_ep': 'eth1'},
+#             {'device': 'DC2-GW', 'ingress_ep': 'eth1', 'egress_ep': 'int'}
+#         ], [UUID('c2e57966-5d82-4705-a5fe-44cf6487219e')])
+# ]
+
+import enum, json, logging, queue, uuid
+from typing import Dict, List, Optional, Tuple
+from common.DeviceTypes import DeviceTypeEnum
+from common.proto.context_pb2 import Device, ServiceTypeEnum
+from test_pathcomp.ResourceGroups import ResourceGroupKindEnum
+
+
+
+from .ConstantsMappings import DEVICE_TYPE_TO_LAYER, DeviceLayerEnum
+
+def convert_explicit_path_hops_to_connections(
+    path_hops : List[Dict], device_dict : Dict[str, Tuple[Dict, Device]],
+    main_service_uuid : str, main_service_type : ServiceTypeEnum
+) -> List[Tuple[str, int, List[str], List[str]]]:
+
+    connection_stack = queue.LifoQueue()
+    connections : List[Tuple[str, int, List[str], List[str]]] = list()
+    prv_device_uuid = None
+    prv_resource_group : Optional[Tuple[ResourceGroupKindEnum, DeviceTypeEnum, str]] = None
+
+    for path_hop in path_hops:
+        device_uuid = path_hop['device']
+        if prv_device_uuid == device_uuid: continue
+        device_tuple = device_dict.get(device_uuid)
+        if device_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_uuid)))
+        json_device,_ = device_tuple
+        device_type = json_device['device_type']
+        resource_group = DEVICE_TYPE_TO_LAYER.get(device_type)
+        if resource_group is None: raise Exception('Undefined Layer for DeviceType({:s})'.format(str(device_type)))
+
+        if prv_resource_group is None:
+            # path ingress
+            connection_stack.put((main_service_uuid, main_service_type, [path_hop], []))
+        elif prv_resource_group > resource_group:
+            # underlying connection begins
+            connection_uuid = str(uuid.uuid4())
+            connection_stack.put((connection_uuid, resource_group, [path_hop], []))
+        elif prv_resource_group == resource_group:
+            # same connection continues
+            connection_stack.queue[-1][2].append(path_hop)
+        elif prv_resource_group < resource_group:
+            # underlying connection ended
+            connection = connection_stack.get()
+            connections.append(connection)
+            connection_stack.queue[-1][3].append(connection[0])
+            connection_stack.queue[-1][2].append(path_hop)
+        else:
+            raise Exception('Uncontrolled condition')
+
+        prv_resource_group = resource_group
+        prv_device_uuid = device_uuid
+
+    # path egress
+    connections.append(connection_stack.get())
+    assert connection_stack.empty()
+    return connections
+
+def convert_explicit_path_hops_to_plain_connection(
+    path_hops : List[Dict], main_service_uuid : str, main_service_type : ServiceTypeEnum
+) -> List[Tuple[str, int, List[str], List[str]]]:
+
+    connection : Tuple[str, int, List[str], List[str]] = \
+        (main_service_uuid, main_service_type, [], [])
+
+    prv_device_uuid = None
+    for path_hop in path_hops:
+        device_uuid = path_hop['device']
+        if prv_device_uuid == device_uuid: continue
+        connection[2].append(path_hop)
+        prv_device_uuid = device_uuid
+
+    return [connection]