From 16607db3cd18a35a48cd0266be14827436484b32 Mon Sep 17 00:00:00 2001 From: oliver Date: Wed, 13 May 2026 17:11:58 +0800 Subject: [PATCH] fix(netx): prefer UUID net_id from ME{} in objectName Only treat UUID-like values as net_id. Prefer extracting ME{uuid} from objectName and ignore ME{numeric} wrappers found in alarmkey so alarms join inventory correctly. Also constrain legacy alarmkey split formats (#, csv, space) to UUID-like prefixes. Co-authored-by: Cursor --- netx_api/ume_sync_service.py | 42 ++++++++++++++++++++++++++---------- tests/test_ume_sync.py | 8 +++++++ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/netx_api/ume_sync_service.py b/netx_api/ume_sync_service.py index 347d493..8e4c16c 100644 --- a/netx_api/ume_sync_service.py +++ b/netx_api/ume_sync_service.py @@ -67,28 +67,48 @@ def _derive_ne_id_from_alarm(alarm: dict[str, Any]) -> str: if ne_id: return ne_id + def _uuid_like(s: str) -> bool: + return bool( + re.fullmatch( + r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", + str(s or "").strip(), + ) + ) + + # Prefer UUID net_id in objectName, e.g. "... ME{33a3e8f4-a76e-40fd-a0ba-045371a5f234} ..." + object_name = _s(_pick(alarm, "objectName", "object-name")) + if object_name: + m_obj = re.search(r"ME\{([^}]+)\}", object_name, flags=re.IGNORECASE) + if m_obj: + candidate = _s(m_obj.group(1)) + if _uuid_like(candidate): + return candidate + alarm_key = _s(_pick(alarm, "alarmKey", "alarm-key", "alarmkey")) if not alarm_key: return "" + # If alarmkey contains ME{...}, only accept it when it looks like a UUID. + m0 = re.search(r"ME\{([^}]+)\}", alarm_key, flags=re.IGNORECASE) + if m0: + candidate = _s(m0.group(1)) + if _uuid_like(candidate): + return candidate + # Common UME formats observed: # 1) "#" # 2) ", , " # 3) " " if "#" in alarm_key: - return _s(alarm_key.split("#", 1)[0]) + candidate = _s(alarm_key.split("#", 1)[0]) + return candidate if _uuid_like(candidate) else "" if "," in alarm_key: - return _s(alarm_key.split(",", 1)[0]) + candidate = _s(alarm_key.split(",", 1)[0]) + return candidate if _uuid_like(candidate) else "" parts = [p for p in alarm_key.split() if p] - if len(parts) >= 2: - return _s(parts[0]) - - # Fallback: some alarms encode net_id as ME{} in objectName. - object_name = _s(_pick(alarm, "objectName", "object-name")) - if object_name: - m = re.search(r"ME\{([^}]+)\}", object_name, flags=re.IGNORECASE) - if m: - return _s(m.group(1)) + if len(parts) >= 2 and ":" not in parts[0]: + candidate = _s(parts[0]) + return candidate if _uuid_like(candidate) else "" return "" diff --git a/tests/test_ume_sync.py b/tests/test_ume_sync.py index ddcf87b..2fe532d 100644 --- a/tests/test_ume_sync.py +++ b/tests/test_ume_sync.py @@ -488,6 +488,10 @@ class UmeSyncServiceTests(unittest.TestCase): alarm_csv = {"alarmkey": "00ceb960-1b62-478e-8303-0935ffea1d28, 4237, 79"} alarm_space = {"alarmkey": "00ceb960-1b62-478e-8303-0935ffea1d28 4205 3588"} alarm_object_name_me = {"alarmkey": "no-net-id-here", "objectName": "OLT/ME{00ceb960-1b62-478e-8303-0935ffea1d28}/PON-1"} + alarm_key_me_wrapped = { + "alarmkey": "BN:ME{353319917}:35 1778634688", + "objectName": "ME{33a3e8f4-a76e-40fd-a0ba-045371a5f234},PWR={/module=3}", + } self.assertEqual( _derive_ne_id_from_alarm(alarm_hash), @@ -505,6 +509,10 @@ class UmeSyncServiceTests(unittest.TestCase): _derive_ne_id_from_alarm(alarm_object_name_me), "00ceb960-1b62-478e-8303-0935ffea1d28", ) + self.assertEqual( + _derive_ne_id_from_alarm(alarm_key_me_wrapped), + "33a3e8f4-a76e-40fd-a0ba-045371a5f234", + ) def test_serialize_ume_alarm_raw_row_select_fields(self): alarm = UmeAlarmCurrent(