diff --git a/inventory_provider/routes/classifier.py b/inventory_provider/routes/classifier.py
index b2e96b6522f9ca56bbb2015633178f087863fd0f..2c9f7f415224944f5df175290a8d941275de0540 100644
--- a/inventory_provider/routes/classifier.py
+++ b/inventory_provider/routes/classifier.py
@@ -1,5 +1,6 @@
 import ipaddress
 import json
+import re
 
 from flask import Blueprint, Response
 
@@ -33,6 +34,22 @@ def handle_request_error(error):
         status=error.status_code)
 
 
+def base_interface_name(interface):
+    m = re.match(r'(.*?)(\.\d+)?$', interface)
+    assert m  # sanity: anything should match
+    return m.group(1)
+
+
+def related_interfaces(hostname, interface):
+    r = common.get_redis()
+    prefix = 'netconf-interfaces:%s:' % hostname
+    for k in r.keys(prefix + base_interface_name(interface) + '*'):
+        k = k.decode('utf-8')
+        assert k.startswith(prefix)  # sanity
+        assert len(k) > len(prefix)  # sanity (contains at least an interface)
+        yield k[len(prefix):]
+
+
 @routes.route("/trap-metadata/<source_equipment>/<path:interface>",
               methods=['GET', 'POST'])
 @common.require_accepts_json
@@ -57,6 +74,18 @@ def get_trap_metadata(source_equipment, interface):
         if ifc_info:
             result['interface'] = json.loads(ifc_info.decode('utf-8'))
 
+        def _related_services():
+            for related in related_interfaces(source_equipment, interface):
+                rs = r.get('opsdb:interface_services:%s:%s'
+                           % (source_equipment, related))
+                if rs:
+                    for s in json.loads(rs.decode('utf-8')):
+                        yield s
+
+        related_services = list(_related_services())
+        if related_services:
+            result['related-services'] = related_services
+
         if not result:
             return Response(
                 response="no available info for {} {}".format(
diff --git a/test/test_classifier_routes.py b/test/test_classifier_routes.py
index 308a725762c796fb715ddaadb7644f736dfb69b7..e799c11c9b68cdda37ffb44a57bba899ece2b473 100644
--- a/test/test_classifier_routes.py
+++ b/test/test_classifier_routes.py
@@ -7,19 +7,123 @@ DEFAULT_REQUEST_HEADERS = {
     "Accept": ["application/json"]
 }
 
+JUNIPER_LINK_METADATA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+
+    "definitions": {
+        "ip-address": {
+            "type": "string",
+            "oneOf": [
+                {"pattern": r'^(\d+\.){3}\d+$'},
+                {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'}
+            ]
+        },
+        "ipv4-interface-address": {
+            "type": "string",
+            "pattern": r'^(\d+\.){3}\d+/\d+$'
+        },
+        "ipv6-interface-address": {
+            "type": "string",
+            "pattern": r'^[a-f\d:]+/\d+$'
+        },
+        "interface-info": {
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+                "description": {"type": "string"},
+                "ipv4": {
+                    "type": "array",
+                    "items": {"$ref": "#/definitions/ipv4-interface-address"}
+                },
+                "ipv6": {
+                    "type": "array",
+                    "items": {"$ref": "#/definitions/ipv6-interface-address"}
+                }
+            },
+            "required": ["name", "description", "ipv4", "ipv6"],
+            "additionalProperties": False
+        },
+        "service-info": {
+            "type": "object",
+            "properties": {
+                "id": {"type": "integer"},
+                "name": {"type": "string"},
+                "status": {
+                    "type": "string",
+                    "enum": ["operational", "installed", "planned", "ordered"]
+                },
+                "circuit_type": {
+                    "type": "string",
+                    "enum": ["path", "service", "l2circuit"]
+                },
+                "service_type": {"type": "string"},
+                "project": {"type": "string"},
+                "equipment": {"type": "string"},
+                "other_end_equipment": {"type": "string"},
+                "port": {"type": "string"},
+                "logical_unit": {
+                    "oneOf": [
+                        {"type": "integer"},
+                        {"type": "string", "maxLength": 0}
+                    ]
+                },
+                "other_end_logical_unit": {
+                    "oneOf": [
+                        {"type": "integer"},
+                        {"type": "string", "maxLength": 0}
+                    ]
+                },
+                "manufacturer": {
+                    "type": "string",
+                    "enum": ["juniper", "coriant", "infinera",
+                             "cisco", "hewlett packard",
+                             "corsa", "graham smith uk ltd",
+                             "unknown", ""]
+                },
+                "card_id": {"type": "string"},
+                "other_end_card_id": {"type": "string"},
+                "interface_name": {"type": "string"},
+                "other_end_interface_name": {"type": "string"}
+            },
+            # TODO: modify service-info so that "" entries are just omitted
+            #       (... rather than requiring 'oneOf')
+            # TODO: put 'other_end_*' params in a sub dictionary
+            "required": [
+                "id", "name", "status",
+                "circuit_type", "service_type",
+                "project", "port", "manufacturer",
+                "equipment", "logical_unit", "card_id", "interface_name"
+            ],
+            "additionalProperties": False
+        }
+    },
+
+    "type": "object",
+    "properties": {
+        "services": {
+            "type": "array",
+            "items": {"$ref": "#/definitions/service-info"}
+        },
+        "interface": {"$ref": "#/definitions/interface-info"},
+        "related-services": {
+            "type": "array",
+            "items": {"$ref": "#/definitions/service-info"}
+        }
+    },
+    "required": ["interface"],
+    "additionalProperties": False
+}
+
 
 def test_trap_metadata(client_with_mocked_data):
-    response_schema = {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "object"
-    }
     rv = client_with_mocked_data.get(
         '/classifier/trap-metadata/mx1.ams.nl.geant.net/ae15.1500',
         headers=DEFAULT_REQUEST_HEADERS)
     assert rv.status_code == 200
     assert rv.is_json
     response_data = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(response_data, response_schema)
+    jsonschema.validate(response_data, JUNIPER_LINK_METADATA)
 
 
 VPN_RR_PEER_INFO_KEYS = {'vpn-rr-peer-info'}
diff --git a/test/test_classifier_utilities.py b/test/test_classifier_utilities.py
new file mode 100644
index 0000000000000000000000000000000000000000..fae79a43f88c6385ac9fd0c4f0aa5cc594383c9f
--- /dev/null
+++ b/test/test_classifier_utilities.py
@@ -0,0 +1,21 @@
+import pytest
+from inventory_provider.routes import classifier
+
+@pytest.mark.parametrize('interface_name,base_name', [
+    ('ae0', 'ae0'),
+    ('ae0.0', 'ae0'),
+    ('ae1.0', 'ae1'),
+    ('ae10.2603', 'ae10'),
+    ('et-3/1/2', 'et-3/1/2'),
+    ('et-3/1/2.100', 'et-3/1/2'),
+    ('xe-2/1/0', 'xe-2/1/0'),
+    ('xe-2/1/0.933', 'xe-2/1/0'),
+
+    # degenerate cases ... check expected regex behavior
+    ('xe-2/1/0.933.933', 'xe-2/1/0.933'),
+    (' sss.333.aaa ', ' sss.333.aaa '),
+    (' sss.333.aaa .999', ' sss.333.aaa ')
+]
+)
+def test_base_interface_name(interface_name, base_name):
+    assert classifier.base_interface_name(interface_name) == base_name