Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/429.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added Hawaiki Provider, Parser and Tests
2 changes: 2 additions & 0 deletions circuit_maintenance_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
GenericProvider,
GlobalCloudXchange,
Google,
Hawaiki,
Lumen,
Megaport,
Momentum,
Expand Down Expand Up @@ -66,6 +67,7 @@
Google,
GTT,
HGC,
Hawaiki,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this before HGC to keep alphabetical.

Lumen,
Megaport,
Momentum,
Expand Down
73 changes: 73 additions & 0 deletions circuit_maintenance_parser/parsers/hawaiki.py
Original file line number Diff line number Diff line change
@@ -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",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"account": "Unknown",
"account": "Customer info unavailable",

Matching what is in apple.py:52 and bso.py:52

"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))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All circuits get Impact.NO_IMPACT unconditionally here, regardless of what the notification says. The email format already carries a Service impact: line that the parser walks past — so a genuinely service-affecting Hawaiki maintenance gets recorded as harmless. Downstream consumers (e.g. Nautobot circuit-maintenance) key off impact to decide whether to act, so this is worse than the current OpenAI-parser fallback for those emails.

The wrinkle is ordering: Service ID: appears above Service impact: in the body, so the single-pass loop can't know the impact at the moment it appends the circuit. Simplest fix is to resolve impact up front, since all circuits in a Hawaiki notification share one impact line; same approach equinix.py:82 takes ("all circuits in the notification share the same impact").

I'd also default to OUTAGE rather than NO_IMPACT for unrecognized phrasing. OUTAGE is the library's own default in CircuitImpact, and it's the safe direction to be wrong in an over-cautious maintenance window costs someone an unnecessary look, an under-cautious one costs them an unexpected outage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh thats my bad! Will get that resolved.

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]
11 changes: 11 additions & 0 deletions circuit_maintenance_parser/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""

Expand Down
67 changes: 67 additions & 0 deletions tests/unit/data/hawaiki/hawaiki1.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
Date: Wed, 5 Aug 2026 20:00:00 +0000
From: HAWAIKI NOC <support@bw-digital.com>
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

<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/></head><body>
<p>Dear valued Hawaiki customer,</p>
<p>Please be advised of maintenance works to be carried out on Hawaiki network. This work is NOT EXPECTED to impact your service identified below.</p>
<p><strong>Service ID:</strong> HW-CID-12345</p>
<p><strong>Reason for Notification:</strong> Maintenance works to be carried out in Hawaiki network</p>
<p><strong>Maintenance Window</strong><br />
Start: 2026-August-05 20:00 UTC<br />
End: 2026-August-05 21:00 UTC<br />
<br />
<strong>Contingency Window</strong><br />
Start: 2026-August-06 20:00 UTC<br />
End: 2026-August-06 21:00 UTC<br />
<br />
<strong>Location of Maintenance</strong>: Pacific City<br />
<br />
<strong>Service impact</strong>: No service impact expected</p>
<p>Hawaiki NOC will monitor the progress of works throughout the maintenance window.</p>
</body></html>

------------=_boundary-hawaiki1--
21 changes: 21 additions & 0 deletions tests/unit/data/hawaiki/hawaiki1_result.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
38 changes: 38 additions & 0 deletions tests/unit/data/hawaiki/hawaiki_completion.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
Date: Wed, 5 Aug 2026 21:34:23 +0000
From: HAWAIKI NOC <support@bw-digital.com>
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

<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>
<body>
<p>Dear Valued Hawaiki Customer,</p>
<p>Please be informed that the maintenance has been completed. Contingency window will not be required.</p>
<p><strong>Ticket Reference:</strong><br />
2026072290001004</p>
<p>Best Regards,<br />
HAWAIKI NOC</p>
</body></html>

------------=_boundary-hawaiki-completion--
19 changes: 19 additions & 0 deletions tests/unit/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
GenericProvider,
GlobalCloudXchange,
Google,
Hawaiki,
Lumen,
Megaport,
Momentum,
Expand Down Expand Up @@ -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,
Expand Down
Loading