Skip to content

Commit

Permalink
Add Pccw parser (#301)
Browse files Browse the repository at this point in the history
* add pccw parser

* add pccw provider

* add pccw unit tests

* add pccw in readme.md

* use tuple type from typing module instead built-in tuple type for supporting python 3.8

* use pccw in all upper case to better match the company name

* add e2e test in ical format

* add comment for default circuit maintenance status
  • Loading branch information
snitass authored Jan 3, 2025
1 parent 6cbf7bc commit 60319aa
Show file tree
Hide file tree
Showing 26 changed files with 959 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ By default, there is a `GenericProvider` that supports a `SimpleProcessor` using
- EXA (formerly GTT) (\*)
- NTT
- PacketFabric
- PCCW
- Telstra (\*)

#### Supported providers based on other parsers
Expand All @@ -82,6 +83,7 @@ By default, there is a `GenericProvider` that supports a `SimpleProcessor` using
- Megaport
- Momentum
- Netflix (AS2906 only)
- PCCW
- Seaborn
- Sparkle
- Tata
Expand Down
2 changes: 2 additions & 0 deletions circuit_maintenance_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
Momentum,
Netflix,
PacketFabric,
PCCW,
Seaborn,
Sparkle,
Tata,
Expand Down Expand Up @@ -58,6 +59,7 @@
Netflix,
NTT,
PacketFabric,
PCCW,
Seaborn,
Sparkle,
Tata,
Expand Down
89 changes: 89 additions & 0 deletions circuit_maintenance_parser/parsers/pccw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Circuit maintenance parser for PCCW Email notifications."""
import re
from typing import List, Dict, Tuple, Any, ClassVar
from datetime import datetime

from bs4.element import ResultSet # type: ignore
from circuit_maintenance_parser.output import Status
from circuit_maintenance_parser.parser import Html, EmailSubjectParser


class HtmlParserPCCW(Html):
"""Custom Parser for HTML portion of PCCW circuit maintenance notifications."""

DATE_TIME_FORMAT: ClassVar[str] = "%d/%m/%Y %H:%M:%S"
PROVIDER: ClassVar[str] = "PCCW Global"

def parse_html(self, soup: ResultSet) -> List[Dict]:
"""Parse PCCW circuit maintenance email.
Args:
soup: BeautifulSoup ResultSet containing the email HTML content
Returns:
List containing a dictionary with parsed maintenance data
"""
data: Dict[str, Any] = {
"circuits": [],
"provider": self.PROVIDER,
"account": self._extract_account(soup),
}
start_time, end_time = self._extract_maintenance_window(soup)
data["start"] = self.dt2ts(start_time)
data["end"] = self.dt2ts(end_time)

return [data]

def _extract_account(self, soup: ResultSet) -> str:
"""Extract customer account from soup."""
customer_field = soup.find(string=re.compile("Customer Name :", re.IGNORECASE))
return customer_field.split(":")[1].strip()

def _extract_maintenance_window(self, soup: ResultSet) -> Tuple[datetime, datetime]:
"""Extract start and end times from maintenance window."""
datetime_field = soup.find(string=re.compile("Date Time :", re.IGNORECASE))
time_parts = (
datetime_field.lower().replace("date time :", "-").replace("to", "-").replace("gmt", "-").split("-")
)
start_time = datetime.strptime(time_parts[1].strip(), self.DATE_TIME_FORMAT)
end_time = datetime.strptime(time_parts[2].strip(), self.DATE_TIME_FORMAT)
return start_time, end_time


class SubjectParserPCCW(EmailSubjectParser):
"""Custom Parser for Email subject of PCCW circuit maintenance notifications.
This parser extracts maintenance ID, status and summary from the email subject line.
"""

# Only completion notification doesn't come with ICal. Other such as planned outage, urgent maintenance,
# amendment and cacellation notifications come with ICal. Hence, maintenance status is set to COMPLETED.
DEFAULT_STATUS: ClassVar[Status] = Status.COMPLETED

def parse_subject(self, subject: str) -> List[Dict]:
"""Parse PCCW circuit maintenance email subject.
Args:
subject: Email subject string to parse
Returns:
List containing a dictionary with parsed subject data including:
- maintenance_id: Extracted from end of subject
- status: Default COMPLETED status
- summary: Cleaned subject line
"""
data: Dict[str, Any] = {
"maintenance_id": self._extract_maintenance_id(subject),
"status": self.DEFAULT_STATUS,
"summary": self._clean_summary(subject),
}

return [data]

def _extract_maintenance_id(self, subject: str) -> str:
"""Extract maintenance ID from the end of subject line."""
return subject.split("-")[-1].strip()

def _clean_summary(self, subject: str) -> str:
"""Clean and format the summary text."""
return subject.strip().replace("\n", "")
24 changes: 24 additions & 0 deletions circuit_maintenance_parser/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from circuit_maintenance_parser.parsers.momentum import HtmlParserMomentum1, SubjectParserMomentum1
from circuit_maintenance_parser.parsers.netflix import TextParserNetflix1
from circuit_maintenance_parser.parsers.openai import OpenAIParser
from circuit_maintenance_parser.parsers.pccw import HtmlParserPCCW, SubjectParserPCCW
from circuit_maintenance_parser.parsers.seaborn import (
HtmlParserSeaborn1,
HtmlParserSeaborn2,
Expand Down Expand Up @@ -406,6 +407,29 @@ class PacketFabric(GenericProvider):
_default_organizer = PrivateAttr("[email protected]")


class PCCW(GenericProvider):
"""PCCW provider custom class."""

_include_filter = PrivateAttr(
{
"Icalendar": ["BEGIN"],
"ical": ["BEGIN"],
EMAIL_HEADER_SUBJECT: [
"Completion - Planned Outage Notification",
"Completion - Urgent Maintenance Notification",
],
}
)

_processors: List[GenericProcessor] = PrivateAttr(
[
SimpleProcessor(data_parsers=[ICal]),
CombinedProcessor(data_parsers=[HtmlParserPCCW, SubjectParserPCCW, EmailDateParser]),
]
)
_default_organizer = "mailto:[email protected]"


class Seaborn(GenericProvider):
"""Seaborn provider custom class."""

Expand Down
27 changes: 27 additions & 0 deletions tests/unit/data/pccw/pccw_amendment
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
BEGIN:VCALENDAR
PRODID:-//iCal4j v1.0//EN
VERSION:2.0
BEGIN:VEVENT
DTSTAMP:20241127T064203Z
DTSTART:20241215T040000Z
DTEND:20241215T120000Z
SUMMARY:Amendment - Planned Outage Notification of International Service -RPO Reference: R1234\\56B-A1 - INC0123456789AB
UID:20241119T061319Z-uidGen@fe80:0:0:0:42:acff:fe15:6%eth0
SEQUENCE:1
X-MAINTNOTE-PROVIDER:PCCW Global
X-MAINTNOTE-ACCOUNT:Acme Pte Ltd
X-MAINTNOTE-MAINTENANCE-ID:INC0123456789AB
X-MAINTNOTE-OBJECT-ID;SR=SR654321:ABC(XXX)-DEF(XXX) EP4321:AAAAAA-654321-
ELE
X-MAINTNOTE-OBJECT-ID;SR=SR123456:ABC(XXX)-DEF(XXX) EP1234:AAAAAA-123456-
ELE
X-MAINTNOTE-IMPACT:OUTAGE
X-MAINTNOTE-STATUS:CONFIRMED
ORGANIZER:mailto:[email protected]
BEGIN:VALARM
TRIGGER:-PT15M
ACTION:DISPLAY
DESCRIPTION:Reminder
END:VALARM
END:VEVENT
END:VCALENDAR
25 changes: 25 additions & 0 deletions tests/unit/data/pccw/pccw_amendment_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[
{
"account": "Acme Pte Ltd",
"circuits": [
{
"circuit_id": "ABC(XXX)-DEF(XXX) EP4321:AAAAAA-654321-",
"impact": "OUTAGE"
},
{
"circuit_id": "ABC(XXX)-DEF(XXX) EP1234:AAAAAA-123456-",
"impact": "OUTAGE"
}
],
"end": 1734264000,
"maintenance_id": "INC0123456789AB",
"organizer": "mailto:[email protected]",
"provider": "PCCW Global",
"sequence": 1,
"stamp": 1732689723,
"start": 1734235200,
"status": "CONFIRMED",
"summary": "Amendment - Planned Outage Notification of International Service -RPO Reference: R1234\\56B-A1 - INC0123456789AB",
"uid": "20241119T061319Z-uidGen@fe80:0:0:0:42:acff:fe15:6%eth0"
}
]
23 changes: 23 additions & 0 deletions tests/unit/data/pccw/pccw_cancellation
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
BEGIN:VCALENDAR
PRODID:-//iCal4j v1.0//EN
VERSION:2.0
BEGIN:VEVENT
DTSTAMP:20241204T013128Z
DTSTART:20241205T150000Z
DTEND:20241205T190000Z
SUMMARY:Cancellation - Urgent Maintenance Notification of International Service -UMW Reference number U1234\\56E-A1 - INC0123456789AB
STATUS:CANCELLED
UID:20241202T075849Z-uidGen@fe80:0:0:0:42:acff:fe15:3%eth0
SEQUENCE:1
X-MAINTNOTE-PROVIDER:PCCW Global
X-MAINTNOTE-ACCOUNT:Acme Pte Ltd
X-MAINTNOTE-MAINTENANCE-ID:INC0123456789AB
X-MAINTNOTE-OBJECT-ID;SR=SR654321:ABC(XXX)-DEF(XXX) EP4321:AAAAAA-654321-
ELE
X-MAINTNOTE-OBJECT-ID;SR=SR123456:ABC(XXX)-DEF(XXX) EP1234:AAAAAA-123456-
ELE
X-MAINTNOTE-IMPACT:OUTAGE
X-MAINTNOTE-STATUS:CANCELLED
ORGANIZER:mailto:[email protected]
END:VEVENT
END:VCALENDAR
25 changes: 25 additions & 0 deletions tests/unit/data/pccw/pccw_cancellation_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[
{
"account": "Acme Pte Ltd",
"circuits": [
{
"circuit_id": "ABC(XXX)-DEF(XXX) EP4321:AAAAAA-654321-",
"impact": "OUTAGE"
},
{
"circuit_id": "ABC(XXX)-DEF(XXX) EP1234:AAAAAA-123456-",
"impact": "OUTAGE"
}
],
"end": 1733425200,
"maintenance_id": "INC0123456789AB",
"organizer": "mailto:[email protected]",
"provider": "PCCW Global",
"sequence": 1,
"stamp": 1733275888,
"start": 1733410800,
"status": "CANCELLED",
"summary": "Cancellation - Urgent Maintenance Notification of International Service -UMW Reference number U1234\\56E-A1 - INC0123456789AB",
"uid": "20241202T075849Z-uidGen@fe80:0:0:0:42:acff:fe15:3%eth0"
}
]
90 changes: 90 additions & 0 deletions tests/unit/data/pccw/pccw_completion1_body.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>template</title>
<style type="text/css">
.ReadMsgBody{width: 100%;}
.ExternalClass{width: 100%;}
div, p, a, li, td { -webkit-text-size-adjust:none; }
a[x-apple-data-detectors]{
color: inherit !important;
text-decoration: inherit !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !i mportant;
line-height: inherit !important;
}
</style>
</head>
<body>
<div style="background-color:#FFEB9C; width:100%; padding:2pt; font-size:10pt; line-height:12pt; font-family:'Calibri'; color:Black; text-align: left;">
<span style="color:#9C6500; font-weight:bold;">CAUTION:</span> This email has been sent from an external source. Do not click links, open attachments, or provide sensitive business information unless you can verify the sender’s legitimacy.
</div>
<br>
<div>
<table style="width: 720px;" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr style="line-height: 0px;">
<td style="line-height: 0px;"><img src="https://www.pccwglobal.com/wp-content/uploads/notifications/request_planned_outage.jpg" alt="" width="700" height="250"></td>
</tr>
</tbody>
</table>
<table style="width: 720px;" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td align="left" valign="top" height="30">
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>
<p><span style="font-size: 11pt; font-family: verdana, geneva;">Customer Name : Acme Pte Ltd
<br>
From : Global Services &amp; Operations Tel : +852-28291567<br>
Date : 29/11/2024 23:38:36 GMT (dd/mm/yyyy)<br>
<br>
<br>
Dear Sir/Madam,<br>
<br>
<br>
PCCW Global wished to advise you that the following network activity that will affect your service.
<br>
<br>
<br>
RPO Reference Number : R1234\56B<br>
Date Time : 26/11/2024 01:00:00 to 26/11/2024 10:00:00 GMT (dd/mm/yyyy)<br>
Duration : 0 mins<br>
Service/Circuit Affected :<br>
SR654321 / ABC(XXX)-DEF(XXX) EP4321 / <br>
SR123456 / ABC(XXX)-DEF(XXX) EP1234 / <br>
<br>
Outage Description : AAE-1: PW for Fiber migration works between XXXX - XXXXX (XXX-XXX) span within segment L.1, Services
<br>
<br>
Impact : Switching hits<br>
<br>
PCCW Global apologies for all inconveniences caused.<br>
<br>
Should you have any question or any enquiries, please do not hesitate to contact us by calling our 24x7 customer service hotline at +852-28291567 / 80028291567 (Toll free number)<br>
<br>
<br>
Best Regards,<br>
<br>
Global Services &amp; Operations <br>
PCCW Global</span></p>
</td>
</tr>
</tbody>
</table>
<table style="width: 720px;" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr style="line-height: 0px;">
<td style="line-height: 0px;" width="479" height="91"></td>
</tr>
</tbody>
</table>
<div></div>
</div>
</body>
</html>
9 changes: 9 additions & 0 deletions tests/unit/data/pccw/pccw_completion1_body_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"account": "Acme Pte Ltd",
"circuits": [],
"end": 1732615200,
"provider": "PCCW Global",
"start": 1732582800
}
]
1 change: 1 addition & 0 deletions tests/unit/data/pccw/pccw_completion1_subject.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Completion - Planned Outage Notification of International Service - RPO Reference number R1234\\56B - INC0123456789AB
7 changes: 7 additions & 0 deletions tests/unit/data/pccw/pccw_completion1_subject_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"maintenance_id": "INC0123456789AB",
"status": "COMPLETED",
"summary": "Completion - Planned Outage Notification of International Service - RPO Reference number R1234\\\\56B - INC0123456789AB"
}
]
Loading

0 comments on commit 60319aa

Please sign in to comment.