From 75f9ed526de5399a8c666143afa66e824933a1e7 Mon Sep 17 00:00:00 2001 From: mkekez-sie Date: Thu, 6 Aug 2026 15:33:52 +0930 Subject: [PATCH 1/5] Add Hawaiki Parser and tests --- circuit_maintenance_parser/__init__.py | 2 + circuit_maintenance_parser/parsers/hawaiki.py | 73 +++++++++++++++++++ circuit_maintenance_parser/provider.py | 11 +++ tests/unit/data/hawaiki/hawaiki1.eml | 67 +++++++++++++++++ tests/unit/data/hawaiki/hawaiki1_result.json | 21 ++++++ .../unit/data/hawaiki/hawaiki_completion.eml | 38 ++++++++++ tests/unit/test_e2e.py | 19 +++++ 7 files changed, 231 insertions(+) create mode 100644 circuit_maintenance_parser/parsers/hawaiki.py create mode 100644 tests/unit/data/hawaiki/hawaiki1.eml create mode 100644 tests/unit/data/hawaiki/hawaiki1_result.json create mode 100644 tests/unit/data/hawaiki/hawaiki_completion.eml diff --git a/circuit_maintenance_parser/__init__.py b/circuit_maintenance_parser/__init__.py index 10f68024..806b4479 100644 --- a/circuit_maintenance_parser/__init__.py +++ b/circuit_maintenance_parser/__init__.py @@ -13,6 +13,7 @@ FLAG, GTT, HGC, + Hawaiki, NTT, PCCW, RETN, @@ -66,6 +67,7 @@ Google, GTT, HGC, + Hawaiki, Lumen, Megaport, Momentum, diff --git a/circuit_maintenance_parser/parsers/hawaiki.py b/circuit_maintenance_parser/parsers/hawaiki.py new file mode 100644 index 00000000..b65dff0e --- /dev/null +++ b/circuit_maintenance_parser/parsers/hawaiki.py @@ -0,0 +1,73 @@ +"""Hawaiki parser.""" + +import logging +import re +from typing import Dict, List + +from dateutil import parser + +from circuit_maintenance_parser.output import CircuitImpact, Impact, Status +from circuit_maintenance_parser.parser import EmailSubjectParser, Text + +logger = logging.getLogger(__name__) + + +class SubjectParserHawaiki1(EmailSubjectParser): + """Extract maintenance_id from Hawaiki email subject.""" + + def parse_subject(self, subject: str) -> List[Dict]: + """Extract ticket number from subject like '[Ticket#2026072290001004] ...'.""" + match = re.search(r"\[Ticket#(\S+)\]", subject) + if match: + return [{"maintenance_id": match.group(1)}] + return [{}] + + +class TextParserHawaiki1(Text): + """Parse plain-text body of Hawaiki initial maintenance notifications.""" + + def parse_text(self, text: str) -> List[Dict]: + """Extract attributes from Hawaiki notification. + + Example: + Service ID: HW-CID-12345 + + Maintenance Window + Start: 2026-August-05 20:00 UTC + End: 2026-August-05 21:00 UTC + """ + data: Dict = { + "circuits": [], + "status": Status.CONFIRMED, + "account": "Unknown", + "summary": "Hawaiki maintenance notification", + } + + lines = text.splitlines() + in_maintenance_window = False + + for line in lines: + line = line.strip() + + match = re.match(r"Service ID:\s*(.+)", line) + if match: + circuit_id = match.group(1).strip() + data["circuits"].append(CircuitImpact(circuit_id=circuit_id, impact=Impact.NO_IMPACT)) + continue + + if line == "Maintenance Window": + in_maintenance_window = True + continue + + if in_maintenance_window: + match = re.match(r"Start:\s*(.+)", line) + if match: + data["start"] = self.dt2ts(parser.parse(match.group(1).strip())) + continue + match = re.match(r"End:\s*(.+)", line) + if match: + data["end"] = self.dt2ts(parser.parse(match.group(1).strip())) + in_maintenance_window = False + continue + + return [data] diff --git a/circuit_maintenance_parser/provider.py b/circuit_maintenance_parser/provider.py index 09ce9bd3..20e6fcaa 100644 --- a/circuit_maintenance_parser/provider.py +++ b/circuit_maintenance_parser/provider.py @@ -28,6 +28,7 @@ from circuit_maintenance_parser.parsers.globalcloudxchange import HtmlParserGcx1, SubjectParserGcx1 from circuit_maintenance_parser.parsers.google import HtmlParserGoogle1, SubjectParserGoogle1 from circuit_maintenance_parser.parsers.gtt import HtmlParserGTT1 +from circuit_maintenance_parser.parsers.hawaiki import SubjectParserHawaiki1, TextParserHawaiki1 from circuit_maintenance_parser.parsers.hgc import HtmlParserHGC1, HtmlParserHGC2, SubjectParserHGC1 from circuit_maintenance_parser.parsers.lumen import HtmlParserLumen1 from circuit_maintenance_parser.parsers.megaport import HtmlParserMegaport1 @@ -648,6 +649,16 @@ class Windstream(GenericProvider): _default_organizer = PrivateAttr("wci.maintenance.notifications@windstream.com") +class Hawaiki(GenericProvider): + """Hawaiki provider custom class.""" + + _include_filter = PrivateAttr({"text/plain": ["Maintenance Window"], "text/html": ["Maintenance Window"]}) + _processors: List[GenericProcessor] = PrivateAttr( + [CombinedProcessor(data_parsers=[EmailDateParser, TextParserHawaiki1, SubjectParserHawaiki1])] + ) + _default_organizer = PrivateAttr("support@bw-digital.com") + + class Zayo(GenericProvider): """Zayo provider custom class.""" diff --git a/tests/unit/data/hawaiki/hawaiki1.eml b/tests/unit/data/hawaiki/hawaiki1.eml new file mode 100644 index 00000000..7bff4174 --- /dev/null +++ b/tests/unit/data/hawaiki/hawaiki1.eml @@ -0,0 +1,67 @@ +Date: Wed, 5 Aug 2026 20:00:00 +0000 +From: HAWAIKI NOC +To: noc@example.com +Subject: [Ticket#2026072290001004] Hawaiki Submarine Cable: Hazardous Condition Notification 2026-Aug-05 20:00 UTC +Content-Type: multipart/mixed; boundary="----------=_boundary-hawaiki1" +MIME-Version: 1.0 + +------------=_boundary-hawaiki1 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Dear valued Hawaiki customer, + +Please be advised of maintenance works to be carried out on Hawaiki network. +This work is NOT EXPECTED to impact your service identified below. + +Service ID: HW-CID-12345 + +Reason for Notification: Maintenance works to be carried out in Hawaiki network + +Maintenance Window +Start: 2026-August-05 20:00 UTC +End: 2026-August-05 21:00 UTC + +Contingency Window +Start: 2026-August-06 20:00 UTC +End: 2026-August-06 21:00 UTC + +Location of Maintenance: Pacific City + +Service impact: No service impact expected + +Hawaiki NOC will monitor the progress of works throughout the maintenance +window, keep you informed of developments as they occur and advise when the +activity is complete. + +Please contact the HAWAIKI SUBMARINE CABLE Network Operations Centre (NOC) +with any questions of concerns. + +----- +Best Regards, +HAWAIKI NOC + +------------=_boundary-hawaiki1 +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: 7bit + + +

Dear valued Hawaiki customer,

+

Please be advised of maintenance works to be carried out on Hawaiki network. This work is NOT EXPECTED to impact your service identified below.

+

Service ID: HW-CID-12345

+

Reason for Notification: Maintenance works to be carried out in Hawaiki network

+

Maintenance Window
+Start: 2026-August-05 20:00 UTC
+End: 2026-August-05 21:00 UTC
+
+Contingency Window
+Start: 2026-August-06 20:00 UTC
+End: 2026-August-06 21:00 UTC
+
+Location of Maintenance: Pacific City
+
+Service impact: No service impact expected

+

Hawaiki NOC will monitor the progress of works throughout the maintenance window.

+ + +------------=_boundary-hawaiki1-- diff --git a/tests/unit/data/hawaiki/hawaiki1_result.json b/tests/unit/data/hawaiki/hawaiki1_result.json new file mode 100644 index 00000000..8760533e --- /dev/null +++ b/tests/unit/data/hawaiki/hawaiki1_result.json @@ -0,0 +1,21 @@ +[ + { + "account": "Unknown", + "circuits": [ + { + "circuit_id": "HW-CID-12345", + "impact": "NO-IMPACT" + } + ], + "end": 1785963600, + "maintenance_id": "2026072290001004", + "organizer": "support@bw-digital.com", + "provider": "hawaiki", + "sequence": 1, + "stamp": 1785960000, + "start": 1785960000, + "status": "CONFIRMED", + "summary": "Hawaiki maintenance notification", + "uid": "0" + } +] diff --git a/tests/unit/data/hawaiki/hawaiki_completion.eml b/tests/unit/data/hawaiki/hawaiki_completion.eml new file mode 100644 index 00000000..4c49d4bb --- /dev/null +++ b/tests/unit/data/hawaiki/hawaiki_completion.eml @@ -0,0 +1,38 @@ +Date: Wed, 5 Aug 2026 21:34:23 +0000 +From: HAWAIKI NOC +To: noc@example.com +Subject: Re: [Ticket#2026072290001004] Hawaiki Submarine Cable: Hazardous Condition Notification 2026-Aug-05 20:00 UTC +Content-Type: multipart/alternative; boundary="----------=_boundary-hawaiki-completion" +MIME-Version: 1.0 + +------------=_boundary-hawaiki-completion +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Dear Valued Hawaiki Customer, + +Please be informed that the maintenance has been completed. Contingency window +will not be required. + +Ticket Reference: +2026072290001004 + +----- +Best Regards, +HAWAIKI NOC + +------------=_boundary-hawaiki-completion +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: 7bit + + + +

Dear Valued Hawaiki Customer,

+

Please be informed that the maintenance has been completed. Contingency window will not be required.

+

Ticket Reference:
+2026072290001004

+

Best Regards,
+HAWAIKI NOC

+ + +------------=_boundary-hawaiki-completion-- diff --git a/tests/unit/test_e2e.py b/tests/unit/test_e2e.py index d0dd32df..f3440612 100644 --- a/tests/unit/test_e2e.py +++ b/tests/unit/test_e2e.py @@ -18,6 +18,7 @@ FLAG, GTT, HGC, + Hawaiki, NTT, PCCW, RETN, @@ -599,6 +600,24 @@ Path(dir_path, "data", "google", "google9_completed_no_end_time_result.json"), ], ), + # Hawaiki - completion/update emails contain no "Maintenance Window" and are filtered to [] + ( + Hawaiki, + [ + ("email", Path(dir_path, "data", "hawaiki", "hawaiki_completion.eml")), + ], + [], + ), + # Hawaiki - initial maintenance notification with Maintenance Window block + ( + Hawaiki, + [ + ("email", Path(dir_path, "data", "hawaiki", "hawaiki1.eml")), + ], + [ + Path(dir_path, "data", "hawaiki", "hawaiki1_result.json"), + ], + ), # Lumen ( Lumen, From 7dd6243929b8d04f4068d1d59d1a2bd037169239 Mon Sep 17 00:00:00 2001 From: mkekez-sie Date: Thu, 6 Aug 2026 17:01:49 +0930 Subject: [PATCH 2/5] Add changelog for #429 feature --- changes/429.added | 1 + circuit_maintenance_parser/__init__.py | 2 +- tests/unit/test_e2e.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changes/429.added diff --git a/changes/429.added b/changes/429.added new file mode 100644 index 00000000..76bf4870 --- /dev/null +++ b/changes/429.added @@ -0,0 +1 @@ +Added Hawaiki Provider, Parser and Tests diff --git a/circuit_maintenance_parser/__init__.py b/circuit_maintenance_parser/__init__.py index 806b4479..fe95a004 100644 --- a/circuit_maintenance_parser/__init__.py +++ b/circuit_maintenance_parser/__init__.py @@ -13,7 +13,6 @@ FLAG, GTT, HGC, - Hawaiki, NTT, PCCW, RETN, @@ -29,6 +28,7 @@ GenericProvider, GlobalCloudXchange, Google, + Hawaiki, Lumen, Megaport, Momentum, diff --git a/tests/unit/test_e2e.py b/tests/unit/test_e2e.py index f3440612..661c1712 100644 --- a/tests/unit/test_e2e.py +++ b/tests/unit/test_e2e.py @@ -18,7 +18,6 @@ FLAG, GTT, HGC, - Hawaiki, NTT, PCCW, RETN, @@ -34,6 +33,7 @@ GenericProvider, GlobalCloudXchange, Google, + Hawaiki, Lumen, Megaport, Momentum, From efa13898e493b4feafc70e9a6cf19ff1e67fd372 Mon Sep 17 00:00:00 2001 From: mkekez-sie Date: Fri, 7 Aug 2026 10:22:31 +0930 Subject: [PATCH 3/5] Re-organise supported vendors --- circuit_maintenance_parser/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuit_maintenance_parser/__init__.py b/circuit_maintenance_parser/__init__.py index fe95a004..8b9b278d 100644 --- a/circuit_maintenance_parser/__init__.py +++ b/circuit_maintenance_parser/__init__.py @@ -66,8 +66,8 @@ GlobalCloudXchange, Google, GTT, - HGC, Hawaiki, + HGC, Lumen, Megaport, Momentum, From 924802502cecaf8c6c861223ac9674fe55d6a3fe Mon Sep 17 00:00:00 2001 From: mkekez-sie Date: Fri, 7 Aug 2026 11:02:24 +0930 Subject: [PATCH 4/5] Fix Hawaiki impact parsing --- circuit_maintenance_parser/parsers/hawaiki.py | 25 ++++++++++++++++--- tests/unit/data/hawaiki/hawaiki1_result.json | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/circuit_maintenance_parser/parsers/hawaiki.py b/circuit_maintenance_parser/parsers/hawaiki.py index b65dff0e..15575a1f 100644 --- a/circuit_maintenance_parser/parsers/hawaiki.py +++ b/circuit_maintenance_parser/parsers/hawaiki.py @@ -10,7 +10,7 @@ from circuit_maintenance_parser.parser import EmailSubjectParser, Text logger = logging.getLogger(__name__) - +IMPACT_LINE_RE = re.compile(r"^Service impact:\s*(.+)$", re.IGNORECASE | re.MULTILINE) class SubjectParserHawaiki1(EmailSubjectParser): """Extract maintenance_id from Hawaiki email subject.""" @@ -39,20 +39,24 @@ def parse_text(self, text: str) -> List[Dict]: data: Dict = { "circuits": [], "status": Status.CONFIRMED, - "account": "Unknown", + "account": "Customer info unavailable", "summary": "Hawaiki maintenance notification", } lines = text.splitlines() in_maintenance_window = False + # All circuits in a Hawaiki notification share one "Service impact" line, and it appears + # after the "Service ID" lines, so resolve it up front. + impact = self._parse_impact(text) + for line in lines: line = line.strip() match = re.match(r"Service ID:\s*(.+)", line) if match: circuit_id = match.group(1).strip() - data["circuits"].append(CircuitImpact(circuit_id=circuit_id, impact=Impact.NO_IMPACT)) + data["circuits"].append(CircuitImpact(circuit_id=circuit_id, impact=impact)) continue if line == "Maintenance Window": @@ -71,3 +75,18 @@ def parse_text(self, text: str) -> List[Dict]: continue return [data] + + @staticmethod + def _parse_impact(text: str) -> Impact: + """Map the notification's single 'Service impact' line to an Impact.""" + match = IMPACT_LINE_RE.search(text) + if not match: + logger.warning("No Hawaiki 'Service impact' line found, defaulting to OUTAGE.") + return Impact.OUTAGE + impact_text = match.group(1).strip().lower() + if "no service impact" in impact_text: + return Impact.NO_IMPACT + if "redundancy" in impact_text or "re-routed" in impact_text: + return Impact.REDUCED_REDUNDANCY + logger.warning("Unrecognized Hawaiki service impact %r, defaulting to OUTAGE.", impact_text) + return Impact.OUTAGE diff --git a/tests/unit/data/hawaiki/hawaiki1_result.json b/tests/unit/data/hawaiki/hawaiki1_result.json index 8760533e..e1119934 100644 --- a/tests/unit/data/hawaiki/hawaiki1_result.json +++ b/tests/unit/data/hawaiki/hawaiki1_result.json @@ -1,6 +1,6 @@ [ { - "account": "Unknown", + "account": "Customer info unavailable", "circuits": [ { "circuit_id": "HW-CID-12345", From 5dc0c0ab19078d367275d5dbced74ec0f6efe288 Mon Sep 17 00:00:00 2001 From: mkekez-sie Date: Fri, 7 Aug 2026 11:05:23 +0930 Subject: [PATCH 5/5] Fix Formatting of Hawaiki file --- circuit_maintenance_parser/parsers/hawaiki.py | 1 + 1 file changed, 1 insertion(+) diff --git a/circuit_maintenance_parser/parsers/hawaiki.py b/circuit_maintenance_parser/parsers/hawaiki.py index 15575a1f..372b6f4f 100644 --- a/circuit_maintenance_parser/parsers/hawaiki.py +++ b/circuit_maintenance_parser/parsers/hawaiki.py @@ -12,6 +12,7 @@ logger = logging.getLogger(__name__) IMPACT_LINE_RE = re.compile(r"^Service impact:\s*(.+)$", re.IGNORECASE | re.MULTILINE) + class SubjectParserHawaiki1(EmailSubjectParser): """Extract maintenance_id from Hawaiki email subject."""