From a8b9044c17323b103ab999a03555628ced6cb6b9 Mon Sep 17 00:00:00 2001 From: Arjan Koopen Date: Fri, 15 May 2026 16:32:17 +0200 Subject: [PATCH 1/3] Add parser for FLAG (fka Globalcloudexchange) --- circuit_maintenance_parser/__init__.py | 2 + circuit_maintenance_parser/parsers/flag.py | 112 +++++++++++ circuit_maintenance_parser/provider.py | 12 ++ tests/unit/data/flag/flag1.eml | 176 +++++++++++++++++ tests/unit/data/flag/flag1_result.json | 14 ++ .../unit/data/flag/flag1_result_combined.json | 21 ++ tests/unit/data/flag/flag2.eml | 177 +++++++++++++++++ tests/unit/data/flag/flag2_result.json | 14 ++ .../unit/data/flag/flag2_result_combined.json | 21 ++ tests/unit/data/flag/flag3.eml | 183 ++++++++++++++++++ tests/unit/data/flag/flag3_result.json | 14 ++ .../unit/data/flag/flag3_result_combined.json | 21 ++ tests/unit/data/flag/flag4_subject.eml | 1 + .../unit/data/flag/flag4_subject_result.json | 6 + tests/unit/test_e2e.py | 29 +++ tests/unit/test_parsers.py | 22 +++ 16 files changed, 825 insertions(+) create mode 100644 circuit_maintenance_parser/parsers/flag.py create mode 100644 tests/unit/data/flag/flag1.eml create mode 100644 tests/unit/data/flag/flag1_result.json create mode 100644 tests/unit/data/flag/flag1_result_combined.json create mode 100644 tests/unit/data/flag/flag2.eml create mode 100644 tests/unit/data/flag/flag2_result.json create mode 100644 tests/unit/data/flag/flag2_result_combined.json create mode 100644 tests/unit/data/flag/flag3.eml create mode 100644 tests/unit/data/flag/flag3_result.json create mode 100644 tests/unit/data/flag/flag3_result_combined.json create mode 100644 tests/unit/data/flag/flag4_subject.eml create mode 100644 tests/unit/data/flag/flag4_subject_result.json diff --git a/circuit_maintenance_parser/__init__.py b/circuit_maintenance_parser/__init__.py index bc994864..12dcb696 100644 --- a/circuit_maintenance_parser/__init__.py +++ b/circuit_maintenance_parser/__init__.py @@ -10,6 +10,7 @@ ATT, AWS, BSO, + FLAG, GTT, HGC, NTT, @@ -54,6 +55,7 @@ CrownCastle, Equinix, EUNetworks, + FLAG, GlobalCloudXchange, Google, GTT, diff --git a/circuit_maintenance_parser/parsers/flag.py b/circuit_maintenance_parser/parsers/flag.py new file mode 100644 index 00000000..7df6fb74 --- /dev/null +++ b/circuit_maintenance_parser/parsers/flag.py @@ -0,0 +1,112 @@ +"""Circuit Maintenance Parser for FLAG Notifications. + +Note: this is a fork of Globalcloudexchange parser. +""" + +import re +from datetime import datetime +from typing import Any, Dict, List + +from bs4 import BeautifulSoup +from bs4.element import ResultSet # type: ignore + +from circuit_maintenance_parser.output import Impact +from circuit_maintenance_parser.parser import EmailSubjectParser, Html, Status + + +class HtmlParserFlag1(Html): + """Custom Parser for HTML portion of FLAG circuit maintenance notifications.""" + + def parse_html(self, soup: BeautifulSoup) -> List[Dict]: + """Parse an FLAG circuit maintenance email. + + Args: + soup (BeautifulSoup): beautiful soup object containing the html portion of an email. + + Returns: + Dict: The data dict containing circuit maintenance data. + """ + data: Dict[str, Any] = {"circuits": []} + self.parse_tables(soup.find_all("table", attrs={"border-collapse": "collapse"}), data) + self.parse_paragraphs(soup.find_all("p"), data) + + return [data] + + def parse_tables(self, tables: ResultSet, data: Dict): + """Parse table elements to find maintenance windows (start/end) and circuit ID's.""" + date_format = date_format = "%d-%b-%Y %H:%M" + for table in tables: + table_type = "" + for row in table.find_all("tr"): + cols = row.find_all("td") + if cols[0].text.strip() == "Service ID": + table_type = "circuits" + continue + if cols[0].text.strip() == "Window": + table_type = "windows" + continue + + # this table is listing all circuits + if table_type == "circuits": + impact = Impact.OUTAGE + if "at risk" in cols[1].text.lower(): + impact = Impact.REDUCED_REDUNDANCY + + data["circuits"].append({"circuit_id": cols[0].text.strip(), "impact": impact}) + # this table is listing windows (note: for now, we will only use the last listed window) + elif table_type == "windows": + data["start"] = self.dt2ts(datetime.strptime(cols[1].text.strip(), date_format)) + data["end"] = self.dt2ts(datetime.strptime(cols[2].text.strip(), date_format)) + + def parse_paragraphs(self, paragraphs: ResultSet, data: Dict): + """Parse paragraph elements to find account and summary.""" + for p in paragraphs: + for pstring in p.strings: + # print(f"hoi: {pstring}") + search = re.search("Dear (.*),", pstring) + if search: + data["account"] = search.group(1).strip() + continue + + # after account has been set, next paragraph is the summary + if "account" in data and "summary" not in data: + data["summary"] = pstring.strip() + continue + + +class SubjectParserFlag1(EmailSubjectParser): + """Parse the subject of a FLAG circuit maintenance email. The subject contains the maintenance ID and status.""" + + def parse_subject(self, subject: str) -> List[Dict]: + """Parse the FLAG Email subject for maintenance ID and status. + + Args: + subject (str): subject of email + e.g. 'FLAG | PE2025102750538 | Planned Event | Rescheduled'. + + + Returns: + List[Dict]: Returns the data object with maintenance_id and status fields. + """ + data = {} + search = re.search( + r"^FLAG \| ([A-Z0-9]+) \| ([\w\s]+) \| ([\w\s]+)$", + subject, + ) + if search: + data["maintenance_id"] = search.group(1) + + if "completed" in subject.lower(): + data["status"] = Status.COMPLETED + elif "rescheduled" in subject.lower(): + data["status"] = Status.RE_SCHEDULED + elif "scheduled" in subject.lower() or "reminder" in subject.lower() or "notice" in subject.lower(): + data["status"] = Status.CONFIRMED + elif "cancelled" in subject.lower(): + data["status"] = Status.CANCELLED + else: + # Some FLAG notifications don't clearly state a status in their subject. + # From inspection of examples, it looks like "Confirmed" would be the most appropriate in this case. + data["status"] = Status.CONFIRMED + + return [data] diff --git a/circuit_maintenance_parser/provider.py b/circuit_maintenance_parser/provider.py index 5b02c2b0..794458a9 100644 --- a/circuit_maintenance_parser/provider.py +++ b/circuit_maintenance_parser/provider.py @@ -23,6 +23,7 @@ from circuit_maintenance_parser.parsers.colt import CsvParserColt1, SubjectParserColt1, SubjectParserColt2 from circuit_maintenance_parser.parsers.crowncastle import HtmlParserCrownCastle1 from circuit_maintenance_parser.parsers.equinix import HtmlParserEquinix, SubjectParserEquinix +from circuit_maintenance_parser.parsers.flag import HtmlParserFlag1, SubjectParserFlag1 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 @@ -358,6 +359,17 @@ class EUNetworks(GenericProvider): _default_organizer = "noc@eunetworks.com" +class FLAG(GenericProvider): + """FLAG provider custom class.""" + + _processors: List[GenericProcessor] = PrivateAttr( + [ + CombinedProcessor(data_parsers=[EmailDateParser, SubjectParserFlag1, HtmlParserFlag1]), + ] + ) + _default_organizer = PrivateAttr("change@flagtel.com") + + class GlobalCloudXchange(GenericProvider): """Global Cloud Xchange provider custom class.""" diff --git a/tests/unit/data/flag/flag1.eml b/tests/unit/data/flag/flag1.eml new file mode 100644 index 00000000..8dd42aa5 --- /dev/null +++ b/tests/unit/data/flag/flag1.eml @@ -0,0 +1,176 @@ +Received: from SA1PR17MB5585.namprd17.prod.outlook.com (2603:10b6:806:1c8::17) + by CH3PR17MB6987.namprd17.prod.outlook.com with HTTPS; Sat, 9 May 2026 + 13:59:10 +0000 +Received: from YQBPR0101CA0199.CANPRD01.PROD.OUTLOOK.COM + (2603:10b6:c01:67::19) by SA1PR17MB5585.namprd17.prod.outlook.com + (2603:10b6:806:1c8::17) with Microsoft SMTP Server (version=TLS1_2, + cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9891.21; Sat, 9 May + 2026 13:59:06 +0000 +Received: from QB1PEPF00004E07.CANPRD01.PROD.OUTLOOK.COM + (2603:10b6:c01:67:cafe::18) by YQBPR0101CA0199.outlook.office365.com + (2603:10b6:c01:67::19) with Microsoft SMTP Server (version=TLS1_3, + cipher=TLS_AES_256_GCM_SHA384) id 15.20.9891.21 via Frontend Transport; Sat, + 9 May 2026 13:59:05 +0000 +Authentication-Results: spf=pass (sender IP is 62.216.151.5) + smtp.mailfrom=flagtel.com; dkim=none (message not signed) + header.d=none;dmarc=pass action=none header.from=flagtel.com;compauth=pass + reason=100 +Received-SPF: Pass (protection.outlook.com: domain of flagtel.com designates + 62.216.151.5 as permitted sender) receiver=protection.outlook.com; + client-ip=62.216.151.5; helo=MTA2.GLOBALCLOUDXCHANGE.COM; pr=C +Received: from MTA2.GLOBALCLOUDXCHANGE.COM (62.216.151.5) by + QB1PEPF00004E07.mail.protection.outlook.com (10.167.240.21) with Microsoft + SMTP Server id 15.21.25.13 via Frontend Transport; Sat, 9 May 2026 13:59:04 + +0000 +Message-ID: +IronPort-SDR: f9p+GPgP6h+MEky5ppxeCQ1fsRMMty4FPrAJsVa0TmNO/ISSvk3rMbPaiLXAzpDYja6jcgapz4 + y9bwWvH7hUDw== +Received: from portals-02.rgcom.com (HELO PORTALS-02) ([10.10.212.99]) + by MTA2.GLOBALCLOUDXCHANGE.COM with ESMTP; 09 May 2026 14:59:02 +0100 +Sensitivity: Normal +From: "FLAG Change Management" +To: +Reply-To: change@flagtel.com +Date: Sat, 9 May 2026 14:59:03 +0100 +Subject: FLAG | PWMR2026050900470 | Planned Work | Scheduled +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: quoted-printable +Return-Path: change@flagtel.com +X-MS-Exchange-Organization-ExpirationStartTime: 09 May 2026 13:59:05.2188 + (UTC) +X-MS-Exchange-Organization-ExpirationStartTimeReason: OriginalSubmit +X-MS-Exchange-Organization-ExpirationInterval: 1:00:00:00.0000000 +X-MS-Exchange-Organization-ExpirationIntervalReason: OriginalSubmit +X-MS-Exchange-Organization-Network-Message-Id: 8c34c4b1-f8e4-467e-c102-08deadd321ee +X-EOPAttributedMessage: 0 +X-EOPTenantAttributedMessage: e01bd386-fa51-4210-a2a4-29e5ab6f7ab1:0 +X-MS-Exchange-Organization-MessageDirectionality: Incoming +X-MS-PublicTrafficType: Email +X-MS-TrafficTypeDiagnostic: QB1PEPF00004E07:EE_|SA1PR17MB5585:EE_|CH3PR17MB6987:EE_ +X-MS-Exchange-Organization-AuthSource: QB1PEPF00004E07.CANPRD01.PROD.OUTLOOK.COM +X-MS-Exchange-Organization-AuthAs: Anonymous +X-MS-Office365-Filtering-Correlation-Id: 8c34c4b1-f8e4-467e-c102-08deadd321ee +X-MS-Exchange-AtpMessageProperties: SA|SL +X-MS-Exchange-Organization-SCL: 1 +X-Microsoft-Antispam: BCL:3;ARA:13230040|704161411799003|2092899012|3072899012|7049299003|3109299003|12012899012|7149299003|69100299015|4076899003|8096899003|19002099003|16102099003|13003099007|55112099003|57112099003; +X-Forefront-Antispam-Report: CIP:62.216.151.5;CTRY:GB;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:MTA2.GLOBALCLOUDXCHANGE.COM;PTR:gcxsmtp01.gcxworld.com;CAT:NONE;SFS:(13230040)(704161411799003)(2092899012)(3072899012)(7049299003)(3109299003)(12012899012)(7149299003)(69100299015)(4076899003)(8096899003)(19002099003)(16102099003)(13003099007)(55112099003)(57112099003);DIR:INB; +X-MS-Exchange-CrossTenant-OriginalArrivalTime: 09 May 2026 13:59:04.8120 + (UTC) +X-MS-Exchange-CrossTenant-Network-Message-Id: 8c34c4b1-f8e4-467e-c102-08deadd321ee +X-MS-Exchange-CrossTenant-Id: e01bd386-fa51-4210-a2a4-29e5ab6f7ab1 +X-MS-Exchange-CrossTenant-AuthSource: QB1PEPF00004E07.CANPRD01.PROD.OUTLOOK.COM +X-MS-Exchange-CrossTenant-AuthAs: Anonymous +X-MS-Exchange-CrossTenant-FromEntityHeader: Internet +X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA1PR17MB5585 +X-MS-Exchange-Transport-EndToEndLatency: 00:00:05.8767700 +X-MS-Exchange-Processed-By-BccFoldering: 15.20.9891.006 +X-Microsoft-Antispam-Mailbox-Delivery: + ucf:0;jmr:0;auth:0;dest:I;ENG:(910005)(944490095)(944506478)(944626604)(920097)(930201)(20251009189)(140003); +X-Microsoft-Antispam-Message-Info: + =?utf-8?B?ZUFZalY5MDRvci8wemFiZjdsclEzMVZJY0xDTEpxVjE1U1lFQWZMQlA5L2Ji?= + =?utf-8?B?OXllTktzNE5yNm1YVHA3ZnRNVFNSQjdFSzF1YkhzakJUUzZYRFUvZURsUHdE?= + =?utf-8?B?UkgxUWlIMzdzeDRZTEVGSGE0Z3JJR1dxOHRRVWl5RVFLeW9MaWliYUlrRzNy?= + =?utf-8?B?Znl5cmZXcVloYUZUUWRRbWxoR0dtVU93MFB4SDBJWVNYQ3UwaDhSbUlYWjFZ?= + =?utf-8?B?VHczRDA3TjM5NXZNc3JUKyt3N1N0ZjRjSEN3RllBUlRsQXJtOHBrSCtVd2sx?= + =?utf-8?B?b0liV2ZpMFhkcDhrL1lPWmk5cDRoeFhkM3pJdDV2Rk03emIvOEJubVVRNmx1?= + =?utf-8?B?YVFEczdFNzY5QU9WUFJsRkh0RkRqZlo5M2gxOVcxQVkzdmdVeC95QUpaUXJM?= + =?utf-8?B?VHV1VmRIL2NZK0piTytreDZ6QTIrWXE2eUQ0Z3dZVEwwVHVoYzJpeW5DdDlw?= + =?utf-8?B?L3IyRmtXeFRyeVdzeTliMDRSdFg5KzdwNlNrdUVZdmpQVTRBckxlSkdpTG9y?= + =?utf-8?B?R1p6UUR0TjZTNm5XMnR4M2QvR1Y5Z0NFSDhtL3oxc2hTck1VWHQ4T1NiT2Jr?= + =?utf-8?B?NSsyZ1ZNTUhGRTd3VXZmSUFoTWh6YjcydzRHS1V4VElYaXNocWcra003ck9i?= + =?utf-8?B?aEU3OTJvR3lTd0tWSHRDVDZBUU5RT244Y09yZmI3eFlRbEZSRm8rSVhSbXlH?= + =?utf-8?B?VmNobGMzSUFTZTIrUHpJNFhEcWRBZk1YSG8xQVljVVJ6NWdidW9sRzZPemlh?= + =?utf-8?B?THFLZFJPRnNFUzZqVHJqc3FhUEltTmY2MnNCQ2ZSYmhFdThOUjl6ZnVtZDdD?= + =?utf-8?B?NFhadGR0RmZhbnI5SVhPTzdGQUVzTHc1V3NwR1gxYUVRbDZDS2E5bXNlMnZM?= + =?utf-8?B?ZzNyVW5NN3pMY3ZHVDArN2Zpb2pueHpQYzdUbS9xUjJ0eFJOUXlIZmJXU1FP?= + =?utf-8?B?RDA1MWZwRSsxTHNMWXc3V2NZdTVRcXlra0JyNHdHUXpLOUF3VG1LcUVxWUxz?= + =?utf-8?B?b1k2S01WclJlazhuQVpKQ0czMU8yZENZcVIvQ0E4cG1JeTZFSWRSODkyYlJz?= + =?utf-8?B?SnJ5Rkl6WXBJdzN1V3p0SVdFaFlJejVZNnB6M3hTM2JtY0VIV2pidWxOclZs?= + =?utf-8?B?dmpWd3FqZ0tGQXJHZG9YMmJIZFlsUEpPdHR0K2FXZ21OMEp3TTdpcWZxeHIw?= + =?utf-8?B?WDh5MXFxZm5hR05kUlRQSksrcU1lRGlsUThzOUhJZlVPMzVMU2dJc0R5R2kx?= + =?utf-8?B?bUJ4RTZSR2VOaytXQlpJajJkc3NycENjcmdjSGk2NUN6RzVXOWNKSjhkbC9E?= + =?utf-8?B?RjBocDNRSXd1MVNaOW1JZGFPbThkUTE0b2RvZHhvdzRKNEdhMXBGdEFRVGxN?= + =?utf-8?B?OHlHSlM3M3g0MDZvbGt6MXhiWWZ3ZFF0Z1Z5SnZGNzlEMkRHdDRJUFUvYXJx?= + =?utf-8?B?b29JaUdncWYxZXZkeGEzNlVvQW16Mk9EWE5EMWd2eEhMNUFzeTZKTGc1RkR6?= + =?utf-8?B?V05Ea3V3Tnk3T01MTVovSkFYWkZES2dETU1CRjl4WWVITlJLSmt4c21lQWtY?= + =?utf-8?B?cGd1Ny94OE41R1JqZDh1eHFWL01pSmo0T1o5WnJaVDBzZVdWT0k1Q1grSm9O?= + =?utf-8?B?T3I0TVc5aEhGR1gzWXFCUkpkRTJta1lmOVJ2dXgxTFN1eERnRXJpN2VLZ21T?= + =?utf-8?B?YzIxL1paT3Z6QTJzM2VLVGJTT05CcmxaTGJ1SEpZUE5YSDArRmFINlRMMng3?= + =?utf-8?B?c29SOVY0MjIrQk9XZlNkNSsrWVI3UGt6dzU5Q1ZqdUcwWG1GNkZmUGpzdXhn?= + =?utf-8?B?V3gvR2hvOXlRbkZMUWY0bDkrdHBFS3RTWGZGcXVKMDBVMTRWV1Q0dXNJTERh?= + =?utf-8?B?MEJJb2lFZ2xXSTNFeXZFcHV6eFMyMkVhSUxpS0kxRFVPdVRmVDdCV3JjT1pE?= + =?utf-8?B?V002OTh6Z0E4TmtCZittVS83amFaUHkwMHpKcEpFYlY5M2FtNEo3M1lIcTFt?= + =?utf-8?B?eTErRUV4VVpuSE1CVDYxZks3YTExRFJMeHNHSDJuWFNyMnltSkdEbkJRajhQ?= + =?utf-8?B?R1U0ZTRiLzZYS1UxYVBWOEY0VTliM1I5enp4eU9icTFZYWFUaFhBVzFQZzcz?= + =?utf-8?B?LzlJdStFRHIvZ3VUWEt3aVhCRCtDa1R1dWQ5YXc5R2ExYjJ6L3o1Z3IzTTNS?= + =?utf-8?B?K1c4bjRPWEhjSlBKTkU2MVkxUE1hSWJlNmIzMTlMbERuMzF6ZUN6TTI5SGhj?= + =?utf-8?B?cllkR3gvdnd4Rkt3QVhTSTk5VzBJcGRINzJqMjFEYjUvZU0wNEJLdFY3MXVP?= + =?utf-8?B?bmwwVG9QMWg5T0l6aVBXSFp0b01hK2h1d09HWU03SitLOXQwanE0dThaMlhs?= + =?utf-8?B?WXkyM1RwNGJyMFB6N0hrQjh5dVJjb29HbkZvdytvTDNlRHhzWjhBZVMybkdr?= + =?utf-8?B?YnRTQTFBeVBlV2pFdzAzOFl1RHlVVVY2TkVGei9iODcvcEFjRGsxNzluSExa?= + =?utf-8?B?NWhGeVZHNSsrcGpYSlFzaUdTMTVaMklmTWJVNDJINXlIVnI4RVJiSlVrYnkx?= + =?utf-8?B?eHJRZzZGMUg4WjZWZ2tvMjhFN3FHTzdXYTNOSERFTHJDazRRNUcwSmUzVUZD?= + =?utf-8?B?OStBL0ovOURJaElTQmE2YmU2WkxJbkVOVFRPQTRBaEErZEtYUEU3RDcwZmJZ?= + =?utf-8?B?OS9wNkFiWThuQ1FsVDJRQkFDanc0TE5vZFFYam5yVHpMNFg5UFRuM2p3RmRE?= + =?utf-8?B?RVNpRktJdTY2UFpndm14Wk5sbDNkVDZvbmFSSzVUU2dXYWtVYUpVajZiUFpw?= + =?utf-8?B?enczUy94VnpnSHFSb3ZsazZtRXp4aWtTc2RZR2RQTndzRW93NnF6REFkRUFa?= + =?utf-8?B?NnFaYzI4MmNZSEJ4c3lpUU5MWmQ5VlJ0N1hTSnZ5WjlKemJMR2FiNzdLWEk3?= + =?utf-8?B?a1gvTTNsWUdmTWFOSlFXa3lZNzdzSFBHNkZTQWgzS3MwM2FvODlaUTRHM2da?= + =?utf-8?B?Y1RYSXJocFp1UUN0Q01ZMS9TSHBIV0dCa3dSTzBtWVptSVRvUHF5amdYZG9H?= + =?utf-8?B?V21GdUdEMTE1aEtPVE13eG1aTTVKbllta1FCOTlaclBud3R0NTVtUkRXRTFW?= + =?utf-8?B?WERwTmZJcXptQVFqeER6eTJzQWRCVXlPTzRJY0hCQmlsTFVHZlQ3bk9zRFkx?= + =?utf-8?B?OTNmNDRRaHU1MVJJMUZhTzNBelJWMWJFT0ZLRlhtOXgxU2RLbmd4TCttdGFQ?= + =?utf-8?B?R1ROUHFLbWFZTGlNWitVWXp6blFBT0oyMUd5QVVIaWJYbjdPSUtNY0FCZDZJ?= + =?utf-8?B?V2VDSFlvNHVqOTJYUXFETWNVVk1ScEJMME5oTDRaSm5pbEtYYk1WVkNidzRM?= + =?utf-8?B?ejg0MVJrWkNoWXQxUTk5WHRFdGEwRHFkcEpVSXY2cHVqRWdvQzN2OTZZam9x?= + =?utf-8?B?VEE2REF2aktSTkk0eFR6VWVYczNUTk5jMG5nU1FIWU5CM2FFSE5kdHgzMDhK?= + =?utf-8?Q?V+Z5hkEnHCvn3T2InGV+3QqV918gJpdPo7APqc=3D?= +MIME-Version: 1.0 + + + + + + + + + + +
+ + + + +
+

Dear ACME CORP,

Our provider will be carrying out a planned activity of fiber repair at Milford, Connecticut +
+
The following schedule is provided as a guide, which is subject to change depending on conditions encountered during the activity:

+ +

+
Window Start Date (GMT) End Date (GMT) Backup Start Date (GMT) Backup End Date (GMT)
129-May-2026 04:0129-May-2026 10:00NANA
+

+ + + +
+
Service ID Impact Impact Duration
RGWLS13337Service Interruption03 hours 00 minutes
+ +
+ + Key Terms + + + + + + +
Service Interruption Services will experience Interruption /Outage.
Loss of Resiliency Services will experience loss of resiliency for the "Impact Duration" and may switch to alternate path.
No Impact Service will continue to work as normal.
+ + +

FLAG apologizes for any inconvenience this work may cause you and your customers.

Should you require any additional information, please contact us at change@flagtel.com or by phone at +44(0) 208 282 1599.


+Regards,
Vishal Parab
Change Management, Global Network Operations Center
D +91-22 303 86074
M:+91 9702427917
vvparab@flagtel.com +



LinkedIn | YouTube

+
+
+The information contained in this message is likely to be confidential and legally privileged. It is intended only for the person named above. If you have received this message in error, please notify us and delete it. Any copying or use of this message is strictly prohibited. Business e-mails are sent to you subject to our usual terms of business and Acceptable Use Policy. When the content of an e-mail is a personal message, the sender is not acting in his/her capacity as a Director, officer or employee of FLAG. We cannot guarantee the integrity or suitability of this message or that it is virus/worms free, therefore, we accept no liability for any damage caused to your systems. FLAG is a trading name of a number of organisations and legal entities around the world. For full details of the registered offices for each company together with their legal name, country of registration, VAT or equivalent number please contact the sender of this e-mail or visit the corporate website of https://www.flagtel.com. + diff --git a/tests/unit/data/flag/flag1_result.json b/tests/unit/data/flag/flag1_result.json new file mode 100644 index 00000000..22c19ac2 --- /dev/null +++ b/tests/unit/data/flag/flag1_result.json @@ -0,0 +1,14 @@ +[ + { + "account": "ACME CORP", + "circuits": [ + { + "circuit_id": "RGWLS13337", + "impact": "OUTAGE" + } + ], + "end": 1780048800, + "start": 1780027260, + "summary": "Our provider will be carrying out a planned activity of fiber repair at Milford, Connecticut" + } +] \ No newline at end of file diff --git a/tests/unit/data/flag/flag1_result_combined.json b/tests/unit/data/flag/flag1_result_combined.json new file mode 100644 index 00000000..b20a7049 --- /dev/null +++ b/tests/unit/data/flag/flag1_result_combined.json @@ -0,0 +1,21 @@ +[ + { + "account": "ACME CORP", + "circuits": [ + { + "circuit_id": "RGWLS13337", + "impact": "OUTAGE" + } + ], + "end": 1780048800, + "maintenance_id": "PWMR2026050900470", + "organizer": "change@flagtel.com", + "provider": "flag", + "sequence": 1, + "stamp": 1778335143, + "start": 1780027260, + "status": "CONFIRMED", + "summary": "Our provider will be carrying out a planned activity of fiber repair at Milford, Connecticut", + "uid": "0" + } +] \ No newline at end of file diff --git a/tests/unit/data/flag/flag2.eml b/tests/unit/data/flag/flag2.eml new file mode 100644 index 00000000..a9ec3cc6 --- /dev/null +++ b/tests/unit/data/flag/flag2.eml @@ -0,0 +1,177 @@ +Received: from IA3PR17MB7840.namprd17.prod.outlook.com (2603:10b6:208:542::15) + by CH3PR17MB6987.namprd17.prod.outlook.com with HTTPS; Fri, 17 Apr 2026 + 08:45:19 +0000 +Received: from YT4PR01CA0138.CANPRD01.PROD.OUTLOOK.COM (2603:10b6:b01:d5::8) + by IA3PR17MB7840.namprd17.prod.outlook.com (2603:10b6:208:542::15) with + Microsoft SMTP Server (version=TLS1_2, + cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9818.25; Fri, 17 Apr + 2026 08:45:17 +0000 +Received: from TO1PEPF00005345.CANPRD01.PROD.OUTLOOK.COM + (2603:10b6:b01:d5:cafe::ed) by YT4PR01CA0138.outlook.office365.com + (2603:10b6:b01:d5::8) with Microsoft SMTP Server (version=TLS1_3, + cipher=TLS_AES_256_GCM_SHA384) id 15.20.9769.52 via Frontend Transport; Fri, + 17 Apr 2026 08:45:16 +0000 +Authentication-Results: spf=pass (sender IP is 62.216.151.5) + smtp.mailfrom=flagtel.com; dkim=none (message not signed) + header.d=none;dmarc=pass action=none header.from=flagtel.com;compauth=pass + reason=100 +Received-SPF: Pass (protection.outlook.com: domain of flagtel.com designates + 62.216.151.5 as permitted sender) receiver=protection.outlook.com; + client-ip=62.216.151.5; helo=MTA2.GLOBALCLOUDXCHANGE.COM; pr=C +Received: from MTA2.GLOBALCLOUDXCHANGE.COM (62.216.151.5) by + TO1PEPF00005345.mail.protection.outlook.com (10.167.241.5) with Microsoft + SMTP Server id 15.20.9769.17 via Frontend Transport; Fri, 17 Apr 2026 + 08:45:15 +0000 +Message-ID: +IronPort-SDR: WqkSmdmrUEBTr33YvFWsk7f8zQzi8FRv3INnYd7PWi77EdJpuWkNDVAUJfZEAHdYcEyRA4vdHq + 09u2V/0UAbTQ== +Received: from unknown (HELO PORTALS-02) ([10.10.212.99]) + by MTA2.GLOBALCLOUDXCHANGE.COM with ESMTP; 17 Apr 2026 09:45:14 +0100 +Sensitivity: Normal +From: "FLAG Change Management" +To: +Reply-To: change@flagtel.com +Date: Fri, 17 Apr 2026 09:45:14 +0100 +Subject: FLAG | PWMR2026040300190 | Planned Work | Completed +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: quoted-printable +Return-Path: change@flagtel.com +X-MS-Exchange-Organization-ExpirationStartTime: 17 Apr 2026 08:45:16.3844 + (UTC) +X-MS-Exchange-Organization-ExpirationStartTimeReason: OriginalSubmit +X-MS-Exchange-Organization-ExpirationInterval: 1:00:00:00.0000000 +X-MS-Exchange-Organization-ExpirationIntervalReason: OriginalSubmit +X-MS-Exchange-Organization-Network-Message-Id: 0fa7b70a-77fc-456c-21bb-08de9c5da5fc +X-EOPAttributedMessage: 0 +X-EOPTenantAttributedMessage: e01bd386-fa51-4210-a2a4-29e5ab6f7ab1:0 +X-MS-Exchange-Organization-MessageDirectionality: Incoming +X-MS-PublicTrafficType: Email +X-MS-TrafficTypeDiagnostic: TO1PEPF00005345:EE_|IA3PR17MB7840:EE_|CH3PR17MB6987:EE_ +X-MS-Exchange-Organization-AuthSource: TO1PEPF00005345.CANPRD01.PROD.OUTLOOK.COM +X-MS-Exchange-Organization-AuthAs: Anonymous +X-MS-Office365-Filtering-Correlation-Id: 0fa7b70a-77fc-456c-21bb-08de9c5da5fc +X-MS-Exchange-AtpMessageProperties: SA|SL +X-MS-Exchange-Organization-SCL: 1 +X-Microsoft-Antispam: BCL:2;ARA:13230040|704161411799003|69100299015|7049299003|7149299003|3109299003|12012899012|2092899012|3072899012|16102099003|19002099003|55112099003|57112099003|13003099007|8096899003|4076899003; +X-Forefront-Antispam-Report: CIP:62.216.151.5;CTRY:GB;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:MTA2.GLOBALCLOUDXCHANGE.COM;PTR:gcxsmtp01.gcxworld.com;CAT:NONE;SFS:(13230040)(704161411799003)(69100299015)(7049299003)(7149299003)(3109299003)(12012899012)(2092899012)(3072899012)(16102099003)(19002099003)(55112099003)(57112099003)(13003099007)(8096899003)(4076899003);DIR:INB; +X-MS-Exchange-CrossTenant-OriginalArrivalTime: 17 Apr 2026 08:45:15.7816 + (UTC) +X-MS-Exchange-CrossTenant-Network-Message-Id: 0fa7b70a-77fc-456c-21bb-08de9c5da5fc +X-MS-Exchange-CrossTenant-Id: e01bd386-fa51-4210-a2a4-29e5ab6f7ab1 +X-MS-Exchange-CrossTenant-AuthSource: TO1PEPF00005345.CANPRD01.PROD.OUTLOOK.COM +X-MS-Exchange-CrossTenant-AuthAs: Anonymous +X-MS-Exchange-CrossTenant-FromEntityHeader: Internet +X-MS-Exchange-Transport-CrossTenantHeadersStamped: IA3PR17MB7840 +X-MS-Exchange-Transport-EndToEndLatency: 00:00:03.2744268 +X-MS-Exchange-Processed-By-BccFoldering: 15.20.9818.014 +X-Microsoft-Antispam-Mailbox-Delivery: + ucf:0;jmr:0;auth:0;dest:I;ENG:(910005)(944490095)(944506478)(944626604)(920097)(930201)(20251009189)(140003); +X-Microsoft-Antispam-Message-Info: + =?utf-8?B?TFAvaFdWSGNqYmhkdHlHVGxPVG5qRlppUE5sMlR2czd0bENoNnZVZGtTZGRW?= + =?utf-8?B?UDBsTHhnVitSVVRjTzBTWXo4VXdxQm1hTmdyOHplYU5tamZDM01LL1pvWDVD?= + =?utf-8?B?K3c1RHBHb3hNaTRmanJydEMzRldzd1Vpb2RmbWowTWhXOXBsTmJGWUdzVlBF?= + =?utf-8?B?Skl6a3hCa1BFUlpMQXZmaTRvcjVKRDdSazgyaXZITXhiZ0JmYUdPWUVIN2g5?= + =?utf-8?B?am93OERGcmVLRnJ3WHVQZDhKMXd6cHlEUGR1V2ZsS3YraWFqZ3F2NE9VYmpP?= + =?utf-8?B?QXcyNXN2MXNva3NTbkUzNW1VV1hpWjBiSUo5aUVwQVRkTkJmZERoUjVEQ1Fx?= + =?utf-8?B?eWF6VFBLOTFWNHB2U3FkNVZJT0dZYkNXWE5obXA5RHoxWUtnRkNJLzNYZ1dE?= + =?utf-8?B?ZUJvOUh5ZjlsSlNyVGp5U0Z3ZzB3UE9sUEpYaWNmeUExeEdGRmhzZUlsd2x3?= + =?utf-8?B?b1FLQVRRQ1BZUjZHZFlDZEJHdzBlbGt4cEptcm5oTi9hYXk2MVFTbzMxRUZo?= + =?utf-8?B?Z3BrTDhsbjBTaEh2aVZCWkNCOUI0RkEvYlRoamlabkVzOGNBZllLV3p4T0Y4?= + =?utf-8?B?RGszakpwTURDQTE1a2hXR3FKZmt6elZLQlI4bUN0Ynp2VjNPVUl4VXp4UHhD?= + =?utf-8?B?ZG1FSEJRYUlYMWIvemI2WFJ6TTVXVVF4ck5oRXI5RHdYemFWNy8vaWZwVUpQ?= + =?utf-8?B?RWszMjlIbkdhTDRFM2lRUXFQM3VqUzNEcjNXbXdLUkRTM3FpcFNYclJLQzM5?= + =?utf-8?B?WkhtYlpHczloRGQ2STY0N2tNVHA0NWhJMkQ2dDVSeW13SHZ5L2lMUlp1RHlz?= + =?utf-8?B?ZERsd3JFV0Y3WEJhbU1mUFF0bWFvMXV4TGJVTTV1M3lBN3lHSlRPY1lja3Nu?= + =?utf-8?B?Mm8vWk9MNm1QWGU2S1c2cW51YWhyLzVad0RYcGowTjVRc2xmT0x0ZFFUZHNP?= + =?utf-8?B?aWJJeE1aUlAxL0Q2dlBHaDN5UmZIMDZzQVBwTnlFcGYxbk0vaHZiWWdKR3Zi?= + =?utf-8?B?aXZqbHF2TXhFUjVmM2ttcy9mTFJ6NGR1M2M3TlE4UWFOVFp0bnFQWnR2SDlD?= + =?utf-8?B?V1EwSHU1MThBZTBkMDFkUTU1VGNqTVpMVittSlBJSnZIaWJ2R3FwSnQ2c1B1?= + =?utf-8?B?OFRLK0FkYm9ETzVaNTFkV0k5aHhKdDlzd1RFNkFTdldwZDlqR1JNckFoME5a?= + =?utf-8?B?eFJVMFBHL1lZL1Y4d09kbG5BeUFpOEwxVExENjRGK291Zi9QNVI0ZGc1RHVw?= + =?utf-8?B?YnhMdTRNMG1UeEZ5ak9pbzRNY0FvRVBiUFNORi9aNzYvbWFuMXBtMmREbkhj?= + =?utf-8?B?QWRpanNUbmdlQVBOdWZNYTZkREtXeitjVlZkZDcxTUZ3V0ZEdldwaUgyQmhQ?= + =?utf-8?B?NmVJTHZHRitrM0dhdUdZYWJUa2NxYU9YcW9kRXJvU0RvS2JVMGZuU3RHWGJr?= + =?utf-8?B?cUkwY3FQekRybUw1ZmtlTGtaYUVIeXVXTE5vV1Vjelh3Rko4MVRRQ3NDbU8w?= + =?utf-8?B?Qk5oOFVRRWZkSVhjM0xIU2lISU1XbDY2SUZ4Vm1rakx3VFBGU2pBbzUxVUd5?= + =?utf-8?B?MnBPTEZlekhHZWxxbVlhcC9OOXhFcjk4Y1U1Z25WdWtCWUV0UXZSQW5FeFM5?= + =?utf-8?B?aDlKQ29ybGpNb3A1WVYwQmx1M0lvY0dIUHVTenQrdVJJcFJHSXh6R0U5amU3?= + =?utf-8?B?VGVRSXBkeTdRTTFGVHFweVJXbkFnQVFVOG5SMnFvNVk0Y1NuL1JrRjg5S1V0?= + =?utf-8?B?cldOSGZ4S1R0TkNuWnQ1WnJzVkxPZVJFd3JJV0JyUUtVSVpUTzdNa2xXbnll?= + =?utf-8?B?WThnZ2xZeXFRR1RkeG1FS0Vpa29BSWZMZnU4dGRLckU5aHdBcVcxWURzaVNn?= + =?utf-8?B?SzNwSzk4d3l2RFB3TzdRY1BLNGJLNmE1VnN0RHdXWlJxNjc1dkxMSWNsbmE2?= + =?utf-8?B?UUhUNjdjWm5ERnVoYXRPcDlQRXZtMUpYd0ZEbUV0QkpveDJqOENNU2J0L0k4?= + =?utf-8?B?Mm8yZTZycndKUU5SalUvOEZ0ZWs0RzRQL0ZkT2gwNWJ6WW1SN2ZHZHVnbkg4?= + =?utf-8?B?NklNU0hSanJ1aDlPVWw5dGlLUzdRY0lUTStHVXR1dFNUZG1VOXY0eExvRUJJ?= + =?utf-8?B?VS9leWtwRG1ocVFmc3NWYWN6ZCtXNU9VQUFGVWd4TFhXck5FZHY3eCtxZmJE?= + =?utf-8?B?WDVYY3k1V2duSzI4enlEZkVKcWNSd1NnMGdrMnJaeFF1TXZ4ZW1kVlduNS9R?= + =?utf-8?B?TW1MeFByR3pBSWlLdjRMd3c1QlV4NElnYWZCQ3E3RjVlRlRPWXg4SFNJMFky?= + =?utf-8?B?a3RBdUZ4STgveXFGUlpQZnMzbnl3alRjUnN1NjZvamNmSWNnU1FQZW5EdEsz?= + =?utf-8?B?WisyNkgzNXkvUUxFcmxxNnBydTVNeGNzTExLU3F3RTdKTld5dy9yUnNtSlU2?= + =?utf-8?B?eXJlOHlNRzJUUFdqVGxadGtlUlhKZ2xIbURHbjR0bmhmQ2hrdmROdlBZMFIy?= + =?utf-8?B?YXpYQ1IxOVlsWUFXVnUrRWEyN3JsR2NXeG5mV1FobTk2Y1VDSjBmUkJ0cjQ3?= + =?utf-8?B?ajBoazhPY1lXN2FLMEsrcDZ0eGMvemNJUlVvTVVkcmdiYkhyT0kxSzdnUk1h?= + =?utf-8?B?NEVxc3FLVzZRejBmZU9FWnRWMXlTUjMrQTROUTJkOXdYczBZN2Fpb1FoZkdO?= + =?utf-8?B?WmNCaytmUGVrZWVzcXBDMmNYSzR3UEFxd3RZUy9QdFZTMFF2Vk5VREZmcHVm?= + =?utf-8?B?L3o2UEdCVGtTQVhWUWlNQnJGMkdzeW5WenRjWENDektBaGpQU3UvOTVoOWxl?= + =?utf-8?B?QmNzY2NWbkFhVGFEMlp0dGQ4ZmQ5bWVOVE5qZG5SdnJlWW1ZVXN2eU1XZ2t0?= + =?utf-8?B?MWk3YzdqTkp1YVFBWHBRTXZkWjMyUCsza0ZTZHVZSzM3TTZWVzNWeHBkN1FN?= + =?utf-8?B?K0M4azRPNzVDeGh2S2lkb1lDRTZqeHN1K3ZVYm01bWh0NzZUT1JXTmpsT2I1?= + =?utf-8?B?VWhRUXlzRXFoVnVGUEhrOTlHNzlTclZ5VjA4T3ZRMDZtS0pabWFscm1RSDZ3?= + =?utf-8?B?QWphV2U3MVBJTGtNelZockY2dmxWQXRLcXJnVnlXeHZIMTVYZkl3SWZjUis5?= + =?utf-8?B?ZWdabFdrdXZ5WEp0QkpIYUY4OXUyTFZmWElFRmxTZXlRVmVxbEN2NzRmWC90?= + =?utf-8?B?Y1dHOFI4VEVwWG1LMG5hRzlpN01QVWV5ZDNYazgwVDZIMk10TEtTQXV0bmh5?= + =?utf-8?B?Q1RYbVo2R0Njc1NkUWpnVTJsNmVNZ1NOTElzSkQvaXJXUWgzQ0tMMWNQRDZC?= + =?utf-8?B?U21vTEZkWDl0eG1EaUxDMHFDa2YyK0RmbEJleHVNbUpvUFhCS1ZGUndqSTFJ?= + =?utf-8?B?WTJlbDI0SWJtd3pXL2kwVFJjN3p4dlVSdmN2NkdYRmRscjE0L2lRdmowamVF?= + =?utf-8?B?REwxQisyWnIyOUtTMWJIRk9hOXM0bkROU3UyWEY0d0dxQ3BZTE5aZ2dIck11?= + =?utf-8?B?a242UU5tRkQ3RlBjSzNFckRwMFl2NTJGOFJPZFhvMjdtOUJNdCtGdFNqQ2NL?= + =?utf-8?B?Vkh4WkxCOUZlaWh3WlRKcHhvZThLTlF5QjROZEM0RDZpVXpBPT0=?= +MIME-Version: 1.0 + + + + + + + + + + +
+ + + + +
+

Dear ACME CORP,

Our provider has completed planned activity of network maintenance at Westport, Connecticut +
+

+ +

+
Window Start Date (GMT) End Date (GMT) Backup Start Date (GMT) Backup End Date (GMT)
117-Apr-2026 04:0017-Apr-2026 10:00NANA
+

+ + + +
+
Service ID Impact Impact Duration
RGWLS13337Service Interruption04 hours 00 minutes
+ +
+ + Key Terms + + + + + + +
Service Interruption Services will experience Interruption /Outage.
Loss of Resiliency Services will experience loss of resiliency for the "Impact Duration" and may switch to alternate path.
No Impact Service will continue to work as normal.
+ + +

FLAG apologizes for any inconvenience this work may cause you and your customers.

Should you require any additional information, please contact us at change@flagtel.com or by phone at +44(0) 208 282 1599.


+Regards,
Vishal Parab
Change Management, Global Network Operations Center
D +91-22 303 86074
M:+91 9702427917
vvparab@flagtel.com +



LinkedIn | YouTube

+
+
+The information contained in this message is likely to be confidential and legally privileged. It is intended only for the person named above. If you have received this message in error, please notify us and delete it. Any copying or use of this message is strictly prohibited. Business e-mails are sent to you subject to our usual terms of business and Acceptable Use Policy. When the content of an e-mail is a personal message, the sender is not acting in his/her capacity as a Director, officer or employee of FLAG. We cannot guarantee the integrity or suitability of this message or that it is virus/worms free, therefore, we accept no liability for any damage caused to your systems. FLAG is a trading name of a number of organisations and legal entities around the world. For full details of the registered offices for each company together with their legal name, country of registration, VAT or equivalent number please contact the sender of this e-mail or visit the corporate website of https://www.flagtel.com. + diff --git a/tests/unit/data/flag/flag2_result.json b/tests/unit/data/flag/flag2_result.json new file mode 100644 index 00000000..48bce745 --- /dev/null +++ b/tests/unit/data/flag/flag2_result.json @@ -0,0 +1,14 @@ +[ + { + "account": "ACME CORP", + "circuits": [ + { + "circuit_id": "RGWLS13337", + "impact": "OUTAGE" + } + ], + "end": 1776420000, + "start": 1776398400, + "summary": "Our provider has completed planned activity of network maintenance at Westport, Connecticut" + } +] \ No newline at end of file diff --git a/tests/unit/data/flag/flag2_result_combined.json b/tests/unit/data/flag/flag2_result_combined.json new file mode 100644 index 00000000..ab575c97 --- /dev/null +++ b/tests/unit/data/flag/flag2_result_combined.json @@ -0,0 +1,21 @@ +[ + { + "account": "ACME CORP", + "circuits": [ + { + "circuit_id": "RGWLS13337", + "impact": "OUTAGE" + } + ], + "end": 1776420000, + "maintenance_id": "PWMR2026040300190", + "organizer": "change@flagtel.com", + "provider": "flag", + "sequence": 1, + "stamp": 1776415514, + "start": 1776398400, + "status": "COMPLETED", + "summary": "Our provider has completed planned activity of network maintenance at Westport, Connecticut", + "uid": "0" + } +] \ No newline at end of file diff --git a/tests/unit/data/flag/flag3.eml b/tests/unit/data/flag/flag3.eml new file mode 100644 index 00000000..0ebde34d --- /dev/null +++ b/tests/unit/data/flag/flag3.eml @@ -0,0 +1,183 @@ +Received: from DS1PR17MB7223.namprd17.prod.outlook.com (2603:10b6:8:1de::14) + by CH3PR17MB6987.namprd17.prod.outlook.com with HTTPS; Mon, 17 Nov 2025 + 06:40:12 +0000 +Received: from YT4PR01CA0485.CANPRD01.PROD.OUTLOOK.COM (2603:10b6:b01:10c::17) + by DS1PR17MB7223.namprd17.prod.outlook.com (2603:10b6:8:1de::14) with + Microsoft SMTP Server (version=TLS1_2, + cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9343.9; Mon, 17 Nov + 2025 06:40:09 +0000 +Received: from YT2PEPF000001CB.CANPRD01.PROD.OUTLOOK.COM + (2603:10b6:b01:10c:cafe::3e) by YT4PR01CA0485.outlook.office365.com + (2603:10b6:b01:10c::17) with Microsoft SMTP Server (version=TLS1_3, + cipher=TLS_AES_256_GCM_SHA384) id 15.20.9320.22 via Frontend Transport; Mon, + 17 Nov 2025 06:40:09 +0000 +Authentication-Results: spf=pass (sender IP is 62.216.151.5) + smtp.mailfrom=flagtel.com; dkim=none (message not signed) + header.d=none;dmarc=pass action=none header.from=flagtel.com;compauth=pass + reason=100 +Received-SPF: Pass (protection.outlook.com: domain of flagtel.com designates + 62.216.151.5 as permitted sender) receiver=protection.outlook.com; + client-ip=62.216.151.5; helo=MTA2.GLOBALCLOUDXCHANGE.COM; pr=C +Received: from MTA2.GLOBALCLOUDXCHANGE.COM (62.216.151.5) by + YT2PEPF000001CB.mail.protection.outlook.com (10.167.241.23) with Microsoft + SMTP Server id 15.20.9343.9 via Frontend Transport; Mon, 17 Nov 2025 06:40:07 + +0000 +Message-ID: +IronPort-SDR: tv6fm0T/BjhuPhvEGZTR+77YfMXDKQeboXOcpcYsur6OP5I6fm0UarAiq5eFbNIvIZpv7/kOEw + E1Y8FuiHGV5w== +Received: from unknown (HELO PORTALS-02) ([10.10.212.99]) + by MTA2.GLOBALCLOUDXCHANGE.COM with ESMTP; 17 Nov 2025 06:40:05 +0000 +Sensitivity: Normal +From: "FLAG Change Management" +To: +Reply-To: change@flagtel.com +Date: Mon, 17 Nov 2025 06:40:06 +0000 +Subject: FLAG | PE2025102750538 | Planned Event | Rescheduled +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: quoted-printable +Return-Path: change@flagtel.com +X-MS-Exchange-Organization-ExpirationStartTime: 17 Nov 2025 06:40:07.4857 + (UTC) +X-MS-Exchange-Organization-ExpirationStartTimeReason: OriginalSubmit +X-MS-Exchange-Organization-ExpirationInterval: 1:00:00:00.0000000 +X-MS-Exchange-Organization-ExpirationIntervalReason: OriginalSubmit +X-MS-Exchange-Organization-Network-Message-Id: e1ce397f-d8bd-413a-ddd4-08de25a425f4 +X-EOPAttributedMessage: 0 +X-EOPTenantAttributedMessage: e01bd386-fa51-4210-a2a4-29e5ab6f7ab1:0 +X-MS-Exchange-Organization-MessageDirectionality: Incoming +X-MS-PublicTrafficType: Email +X-MS-TrafficTypeDiagnostic: YT2PEPF000001CB:EE_|DS1PR17MB7223:EE_|CH3PR17MB6987:EE_ +X-MS-Exchange-Organization-AuthSource: YT2PEPF000001CB.CANPRD01.PROD.OUTLOOK.COM +X-MS-Exchange-Organization-AuthAs: Anonymous +X-MS-Office365-Filtering-Correlation-Id: e1ce397f-d8bd-413a-ddd4-08de25a425f4 +X-MS-Exchange-AtpMessageProperties: SA|SL +X-MS-Exchange-Organization-SCL: 1 +X-Microsoft-Antispam: BCL:2;ARA:13230040|69100299015|2092899012|12012899012|3072899012|13003099007|8096899003|4076899003; +X-Forefront-Antispam-Report: CIP:62.216.151.5;CTRY:GB;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:MTA2.GLOBALCLOUDXCHANGE.COM;PTR:ErrorRetry;CAT:NONE;SFS:(13230040)(69100299015)(2092899012)(12012899012)(3072899012)(13003099007)(8096899003)(4076899003);DIR:INB; +X-MS-Exchange-CrossTenant-OriginalArrivalTime: 17 Nov 2025 06:40:07.1726 + (UTC) +X-MS-Exchange-CrossTenant-Network-Message-Id: e1ce397f-d8bd-413a-ddd4-08de25a425f4 +X-MS-Exchange-CrossTenant-Id: e01bd386-fa51-4210-a2a4-29e5ab6f7ab1 +X-MS-Exchange-CrossTenant-AuthSource: YT2PEPF000001CB.CANPRD01.PROD.OUTLOOK.COM +X-MS-Exchange-CrossTenant-AuthAs: Anonymous +X-MS-Exchange-CrossTenant-FromEntityHeader: Internet +X-MS-Exchange-Transport-CrossTenantHeadersStamped: DS1PR17MB7223 +X-MS-Exchange-Transport-EndToEndLatency: 00:00:05.1803356 +X-MS-Exchange-Processed-By-BccFoldering: 15.20.9320.008 +X-Microsoft-Antispam-Mailbox-Delivery: + ucf:0;jmr:0;auth:0;dest:I;ENG:(910005)(944506478)(944626604)(920097)(930097)(140003); +X-Microsoft-Antispam-Message-Info: + =?utf-8?B?TU0vS21mK1ZsMmk0cVZsY25CSEhJdGtmbHJSYVJhYS9RUU1VMW9NaHZyaEhx?= + =?utf-8?B?dEQyRFk5dUNNTmNLWmdQVWFtcjUwT2V3bWthSzk3STRVa0ozVjBkV2VBTDNX?= + =?utf-8?B?eVhWWENXcGNYYTJzYURQQW1qZkhPWXVvSFdQVXIyRElhK0NrMzVVdStoRmw5?= + =?utf-8?B?R0k2bkoxaHFHQlQxTStidEo2aE0vZzNyMXp2NWU0RkQ3dmE1ZEFWR2dXeWVh?= + =?utf-8?B?NGhaUmdvWUJKNGE1Rlp0eFlLQ2dLNmFBMitLd0Z0UjZOQ1cybHl4VTl4SER2?= + =?utf-8?B?UlZkUHlST3E3UGFTWnYvR1diREcwOWVYYlA1TE0zSnlTcHpOQW1ZVEd2TmFU?= + =?utf-8?B?SG53ODhod2F3cCtENmE3RnhkRG1Sdkg2RWV0OWRIRndJb2hlMWpYRmtrdEh2?= + =?utf-8?B?NnA3enR5Mlpub1ZYcUVyNUFMYlRkQ000ZHdHVU1ob0FQY2FPTnFGSVpRaENW?= + =?utf-8?B?bVF1dy8zRzB6SDIxSGY0dGtLb1R6aldxaWFRS0l0ZFg0VGlsT3BsUFhGSkNN?= + =?utf-8?B?YWhuS1g4L2RSVC93NjM4QWdaTGp4a1BaS2tzMlArQnNMbmFJMEdMaW4zWDl4?= + =?utf-8?B?WmFkRWpNVFZoR0xkNENwN2Y2WlF2c2Q0VUFmTDY5bFVzejhNM1l2WmsvWGNi?= + =?utf-8?B?M1lmYmR1bW1nZWx2NjE3Zm1mNEhvOG8ySGlpY1Jsa3AxRm5YYnppZGVZcFNP?= + =?utf-8?B?ajVadnpRZXdzL2hzc2lFbi9vMmJwUFV2dWlXVG9wcGpHelpsczNQTmQ1dEdp?= + =?utf-8?B?OTlBWXppdDhKRStjTDF6QTU5azlEclNEbDl3TFlWRUFQaSt4TUlndHRNL01Q?= + =?utf-8?B?bmFQZWZLc0lPYXVRa0xsWlM5K1BsVUJNbnI0NURNL2p2L2lUaUhSbnJYME0x?= + =?utf-8?B?cCs2VXZvNTBMb2RKenNMQldRMGVtZWtGeVFTRUpzK0xPUHhQR29KckpGcmdP?= + =?utf-8?B?NU1vUHFMSG1qdCs2Mncrb0RJVnp2RkhzUXJaYkl2cGxVSXpJdGtIUGVnTUFW?= + =?utf-8?B?NHNLUjNWTFN6cFp5YUphaXkrNGp0M3JzdFNYaGZkcXhMZ2ZnV0RMaHpYaXEr?= + =?utf-8?B?TEx3cGdGaVlXcjhZbnFpR1RxREVEdU14ejFjWE9aemZmWCt6N3YwUjFJYnpB?= + =?utf-8?B?Qkx1Z1I0aEVxMEhWa2FXOU5JWXhZc241b3l3UGJFZmU0M1FRcnlkdjVlVFAw?= + =?utf-8?B?YUZmUTQ2cXJxSHIzcFNxUnVPM3dGci92QlZTaWQ4R2RJS2gvV3JpWWViVE5l?= + =?utf-8?B?WFNDMVNrSW96dzBlRGVPeDVyamRrbmpCV2dHcXZQWXozY3BSRFE1em9IdTA3?= + =?utf-8?B?Qlc2dGgxNENJOGJ0Vm5hL2dBbS9sYVZScTFpWWRWN3V3K3ZmU24rZXFmdkha?= + =?utf-8?B?cHZJQzA0R0tXUEN2MWMyS2tveXFJaW51UkZ0RjBkY20yL3hYVUh0ZGFNRlFW?= + =?utf-8?B?bW5LTmpzU2cwYVVNenhkL0ZjS1AwL1ZQUkpYY1dnbDVoL21UNUVOVUFhbGtL?= + =?utf-8?B?L0FwNDQ3M0x3VDNEY2p2Y0RZRFRtYWhZblMvZmpFT2pEeHFBU3FLS0xHUlVF?= + =?utf-8?B?MjZxQzhYM0Z2ZklTVGV4WXVNbDZSUTZBS0lHNCsyKzIxRU1BMGptL1p2YUE1?= + =?utf-8?B?cEFJd2JGWnQvUFdaRjdST3Erbkp2dlEyOG1XaTUrckcwbWtFNXlkVG1jV3Ji?= + =?utf-8?B?MVFxQ1FzN2pBcDQ5dmdNQktiMlZneWpnQkdkYlhZbEF3MTFHZUJIaEFrdnlD?= + =?utf-8?B?dFdGMFZlb255eUlubnQ1ZzdNTXRwbXlvVnYxWVZSVysxVC9JS0tIaTE0SzhI?= + =?utf-8?B?RjFqSXRqVXdjYy9ENFdOYkh6Z0JwdE1aWlg3Ukh0UkRtd0I5T3ZsYWliRE04?= + =?utf-8?B?WUYvZGpFbW5DNjE0TzNtVDlhWVZsTC8rMmNnU2V4RGEvT3BUd2RHNVQvMWN2?= + =?utf-8?B?SkpjTmFxOW9YYTJ1a2FYWThXcWxtckNCSFNYZ2NwN3JHeTYxOCtBYmtvTWxV?= + =?utf-8?B?M2w1Ni9aMVRhdUZHOU9LNGVkNjl6SzFBck9zQXpOS2tqcENWTTIrbXlMWGRQ?= + =?utf-8?B?dmM0UVdQS1J5WEJteEVpSDNZWStMaGNCT2kxSDVuQlZmZGpqaTFrbkVGQmVG?= + =?utf-8?B?OXpCSTllSUxUNjNCZWFOYW5BYVZWM2tXb3hnMEZibFQrYkE4TjU1OEgvMEJy?= + =?utf-8?B?SkJSTW5sWGFSbldRS3FwVkMvVldyNXVEZzZnTUZzYk9hTHRjZm1XRVF0Q3VP?= + =?utf-8?B?S3JUMDBETFlmemNIVUdUNTVTUlIvbGs5VmVNOG4rbk9nWWlBdFFyMzIwVkxH?= + =?utf-8?B?bnFPY3BDTHh2WUdMTCtwQnI4QkdyK2tmSytRYmU5Nis2emRkZ3ZZd3BRKzht?= + =?utf-8?B?ZFR2YkE1RVZYenVjOWMzMWlqZEtRZEhwU3RTdlhuSVhsZjF1NFlBWjl3a29z?= + =?utf-8?B?Q2J0NEtJakNwUjY0WDZ1dHEzR3FqMGdadXlYWEFjUHBaei9Hb20vVklpb2xC?= + =?utf-8?B?V3JTeXppN1lrZHZQL0l0Y0RhT1BTS3FaZWg5Tmh5NGQrbzZwaVlrZlpxUStz?= + =?utf-8?B?eTRYcUlvNGc0MWdlMHorWllvamg2K1lobzVWaWtqZTU5enk1YVdxZjh2ampX?= + =?utf-8?B?ajhLZHFFTDlscTdyVC9heTZWNklyeUtsejFGRXFMNGdDYlZHcWVRQWdQT1F5?= + =?utf-8?B?L3lVYm9DK29pR21jU05QZGRGNWNKOTVhbmdqMXJwZHo0Rm5HdlBIL0JXT0w5?= + =?utf-8?B?SThZMU1qVkxsMU5jWmQ3OXVmQlE2L1dkRVNuS1JBRGxwMDZjWjBYSWgxOERX?= + =?utf-8?B?cVRSQk9vekczMHg2RTNjdnlrNmxoSVZtSHVZbEhsa2QwVFFXU0MzNVJwWEpF?= + =?utf-8?B?ZGJRMlNhYmNIenR1UmJGNHZ1eWNjYVVpYkRTbVRjby9sSGRVbEJ2djBsblhX?= + =?utf-8?B?SXlseml4d1JvLzFCaTVYcXh3aVJjc2kvSE9HZzVxVEUxN2dmTVk2bnNnUnpI?= + =?utf-8?B?N3FtYUVCS3NkOHdOdUpCbU1PYlVST21pOWo4TXVCckdWenBSV3lBRjZYcFRS?= + =?utf-8?B?WjdBRGlMNVRQRjc3S3dWSFJQKzNnd0QxdzhBQXVzWUtQR2RORE15NEtDMTZy?= + =?utf-8?B?SmEyZjJRQTM4U3ZBODdreGIxM0Y1dUVwbGJiSHZOTjJpQStmM0h0MVdneGc2?= + =?utf-8?B?WDRRUjBQZGZicWtYcGFZMGRENlhaVTg1TG92M1RHZWJobUJoVVRDT1VjM3Qz?= + =?utf-8?B?WVQ0dzJwQ3hnUFpVNzZwS0grUkp5b2k3aVU3S1Q2NTVpK1JhWiswWkErOHBz?= + =?utf-8?B?QjF4R08xSGRmRi9aY1FoVXZITm9lYXErdDJyVlB2elRHenAxb1dYSnFPQWVj?= + =?utf-8?B?KzB2WDVGamZaeE1YeFB5V0Fwa0U5dlJzUVNjMWk3ditVWVVsWTFqTGl2dU00?= + =?utf-8?B?dW5xNkk1MGJXT2duL09rZGFXekQ1UDZ1ekM5b1BRNDE2QlpMcXBWVW95Ukda?= + =?utf-8?B?WkwrUTFJZWpGT3U1bWxiRXo0bWo4L1hZVk9xWVVDMkkvWkdEN1RGNWQ0N0RI?= + =?utf-8?B?TlhVOWp1Mlg3YkVTQXpEdmtoL3VTcDlZUjNYWW15a0t1d0ZscDBDcmVGblZt?= + =?utf-8?B?TlY2aHEzNVNHT21uYkZ6MzVTRVFPeFB4ZHI1SVJUR1ZEM3RHTnpPaUFQR0po?= + =?utf-8?B?WUlJSXJ5MUd6N21nTExMUVdxRW81MFZxSkVxc1M5VGVLM1NkVFFQWEhyaGZ2?= + =?utf-8?B?UFQ3b29sdmd5WFVqRDgwbzlLNmlmMmtiU245cWRldEo2Y2lweHFQRkFOaEpI?= + =?utf-8?B?N1l6L3IxSm0xcFk2MHF3TlZoVExLOGhpY3Q0OUpuTm9wWUw2c2tUeTdKcTJX?= + =?utf-8?Q?W0xGS3YutgeYRE69g/3fr67uKt+8hlxIUDHweOTQdclzA?= +MIME-Version: 1.0 + + + + + + + + + + +
+ + + + +
+

Dear ACME CORP,

FLAG has rescheduled planned activity to resolve degradation issue at Khasab +
+
The following schedule is provided as a guide, which is subject to change depending on conditions encountered during the activity: +

+ +

+
Window Start Date (GMT) End Date (GMT) Backup Start Date (GMT) Backup End Date (GMT)
101-Dec-2025 22:0002-Dec-2025 02:00NANA
+

+ + + +
+
Service ID Impact Impact Duration
RGWLS13337Service Interruption04 hours 00 minutes
+ +
+ + Key Terms + + + + + + +
Service Interruption Services will experience Interruption /Outage.
Resiliency Loss Services will experience loss of resiliency for the "Impact Duration" and may switch to alternate path.
No Impact Service will continue to work as normal.
+ + +

FLAG apologizes for any inconvenience this work may cause you and your customers.

Should you require any additional information, please contact us at change@flagtel.com or by phone at +44(0) 208 282 1599.


+Regards,
Vishal Parab
Change Management, Global Network Operations Center
D +91-22 303 86074
M:+91 9702427917
vvparab@flagtel.com +



LinkedIn | YouTube

+
+
+The information contained in this message is likely to be confidential and legally privileged. It is intended only for the person named above. If you have received this message in error, please notify us and delete it. Any copying or use of this message is strictly prohibited. Business e-mails are sent to you subject to our usual terms of business and Acceptable Use Policy. When the content of an e-mail is a personal message, the sender is not acting in his/her capacity as a Director, officer or employee of FLAG. We cannot guarantee the integrity or suitability of this message or that it is virus/worms free, therefore, we accept no liability for any damage caused to your systems. FLAG is a trading name of a number of organisations and legal entities around the world. For full details of the registered offices for each company together with their legal name, country of registration, VAT or equivalent number please contact the sender of this e-mail or visit the corporate website of https://www.flagtel.com. + diff --git a/tests/unit/data/flag/flag3_result.json b/tests/unit/data/flag/flag3_result.json new file mode 100644 index 00000000..d192d1fa --- /dev/null +++ b/tests/unit/data/flag/flag3_result.json @@ -0,0 +1,14 @@ +[ + { + "account": "ACME CORP", + "circuits": [ + { + "circuit_id": "RGWLS13337", + "impact": "OUTAGE" + } + ], + "end": 1764640800, + "start": 1764626400, + "summary": "FLAG has rescheduled planned activity to resolve degradation issue at Khasab" + } +] \ No newline at end of file diff --git a/tests/unit/data/flag/flag3_result_combined.json b/tests/unit/data/flag/flag3_result_combined.json new file mode 100644 index 00000000..0174f535 --- /dev/null +++ b/tests/unit/data/flag/flag3_result_combined.json @@ -0,0 +1,21 @@ +[ + { + "account": "ACME CORP", + "circuits": [ + { + "circuit_id": "RGWLS13337", + "impact": "OUTAGE" + } + ], + "end": 1764640800, + "maintenance_id": "PE2025102750538", + "organizer": "change@flagtel.com", + "provider": "flag", + "sequence": 1, + "stamp": 1763361606, + "start": 1764626400, + "status": "RE-SCHEDULED", + "summary": "FLAG has rescheduled planned activity to resolve degradation issue at Khasab", + "uid": "0" + } +] \ No newline at end of file diff --git a/tests/unit/data/flag/flag4_subject.eml b/tests/unit/data/flag/flag4_subject.eml new file mode 100644 index 00000000..ea52bd45 --- /dev/null +++ b/tests/unit/data/flag/flag4_subject.eml @@ -0,0 +1 @@ +FLAG | PWMR2026050900470 | Planned Work | Scheduled \ No newline at end of file diff --git a/tests/unit/data/flag/flag4_subject_result.json b/tests/unit/data/flag/flag4_subject_result.json new file mode 100644 index 00000000..4b747bcb --- /dev/null +++ b/tests/unit/data/flag/flag4_subject_result.json @@ -0,0 +1,6 @@ +[ + { + "maintenance_id": "PWMR2026050900470", + "status": "CONFIRMED" + } +] \ No newline at end of file diff --git a/tests/unit/test_e2e.py b/tests/unit/test_e2e.py index e8930493..61e63757 100644 --- a/tests/unit/test_e2e.py +++ b/tests/unit/test_e2e.py @@ -15,6 +15,7 @@ ATT, AWS, BSO, + FLAG, GTT, HGC, NTT, @@ -493,6 +494,34 @@ Path(dir_path, "data", "hgc", "hgc2_result.json"), ], ), + # FLAG + ( + FLAG, + [ + ("email", Path(dir_path, "data", "flag", "flag1.eml")), + ], + [ + Path(dir_path, "data", "flag", "flag1_result_combined.json"), + ], + ), + ( + FLAG, + [ + ("email", Path(dir_path, "data", "flag", "flag2.eml")), + ], + [ + Path(dir_path, "data", "flag", "flag2_result_combined.json"), + ], + ), + ( + FLAG, + [ + ("email", Path(dir_path, "data", "flag", "flag3.eml")), + ], + [ + Path(dir_path, "data", "flag", "flag3_result_combined.json"), + ], + ), # GlobalCloudXchange ( GlobalCloudXchange, diff --git a/tests/unit/test_parsers.py b/tests/unit/test_parsers.py index fc7d839b..c629c001 100644 --- a/tests/unit/test_parsers.py +++ b/tests/unit/test_parsers.py @@ -17,6 +17,7 @@ from circuit_maintenance_parser.parsers.colt import CsvParserColt1, SubjectParserColt1, SubjectParserColt2 from circuit_maintenance_parser.parsers.crowncastle import HtmlParserCrownCastle1 from circuit_maintenance_parser.parsers.equinix import HtmlParserEquinix, SubjectParserEquinix +from circuit_maintenance_parser.parsers.flag import HtmlParserFlag1, SubjectParserFlag1 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 @@ -370,6 +371,27 @@ def default(self, o): Path(dir_path, "data", "equinix", "equinix9.eml"), Path(dir_path, "data", "equinix", "equinix9_result.json"), ), + # FLAG + ( + HtmlParserFlag1, + Path(dir_path, "data", "flag", "flag1.eml"), + Path(dir_path, "data", "flag", "flag1_result.json"), + ), + ( + HtmlParserFlag1, + Path(dir_path, "data", "flag", "flag2.eml"), + Path(dir_path, "data", "flag", "flag2_result.json"), + ), + ( + HtmlParserFlag1, + Path(dir_path, "data", "flag", "flag3.eml"), + Path(dir_path, "data", "flag", "flag3_result.json"), + ), + ( + SubjectParserFlag1, + Path(dir_path, "data", "flag", "flag4_subject.eml"), + Path(dir_path, "data", "flag", "flag4_subject_result.json"), + ), # Global Cloud Xchange ( HtmlParserGcx1, From fd46aaabe13bbf1596c375ccc8e857364e60f042 Mon Sep 17 00:00:00 2001 From: Arjan Koopen Date: Fri, 15 May 2026 16:48:11 +0200 Subject: [PATCH 2/3] Add changelog message, plus add equinix tests 9+10 --- changes/419.added | 1 + tests/unit/test_parsers.py | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 changes/419.added diff --git a/changes/419.added b/changes/419.added new file mode 100644 index 00000000..f41491cc --- /dev/null +++ b/changes/419.added @@ -0,0 +1 @@ +Added parser for FLAG (fka Globalcloudexchange) diff --git a/tests/unit/test_parsers.py b/tests/unit/test_parsers.py index c629c001..2e61165d 100644 --- a/tests/unit/test_parsers.py +++ b/tests/unit/test_parsers.py @@ -371,6 +371,16 @@ def default(self, o): Path(dir_path, "data", "equinix", "equinix9.eml"), Path(dir_path, "data", "equinix", "equinix9_result.json"), ), + ( + HtmlParserEquinix, + Path(dir_path, "data", "equinix", "equinix10.eml"), + Path(dir_path, "data", "equinix", "equinix10_result.json"), + ), + ( + SubjectParserEquinix, + Path(dir_path, "data", "equinix", "equinix11_subject.eml"), + Path(dir_path, "data", "equinix", "equinix11_subject_result.json"), + ), # FLAG ( HtmlParserFlag1, From b6f6be848857eea2305ade05a4cc3654f9945fb6 Mon Sep 17 00:00:00 2001 From: Arjan Koopen Date: Mon, 18 May 2026 14:33:46 +0200 Subject: [PATCH 3/3] Include suggested changes during PR review --- circuit_maintenance_parser/parsers/flag.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/circuit_maintenance_parser/parsers/flag.py b/circuit_maintenance_parser/parsers/flag.py index 7df6fb74..0a8bf306 100644 --- a/circuit_maintenance_parser/parsers/flag.py +++ b/circuit_maintenance_parser/parsers/flag.py @@ -34,7 +34,7 @@ def parse_html(self, soup: BeautifulSoup) -> List[Dict]: def parse_tables(self, tables: ResultSet, data: Dict): """Parse table elements to find maintenance windows (start/end) and circuit ID's.""" - date_format = date_format = "%d-%b-%Y %H:%M" + date_format = "%d-%b-%Y %H:%M" for table in tables: table_type = "" for row in table.find_all("tr"): @@ -62,7 +62,6 @@ def parse_paragraphs(self, paragraphs: ResultSet, data: Dict): """Parse paragraph elements to find account and summary.""" for p in paragraphs: for pstring in p.strings: - # print(f"hoi: {pstring}") search = re.search("Dear (.*),", pstring) if search: data["account"] = search.group(1).strip() @@ -90,7 +89,7 @@ def parse_subject(self, subject: str) -> List[Dict]: """ data = {} search = re.search( - r"^FLAG \| ([A-Z0-9]+) \| ([\w\s]+) \| ([\w\s]+)$", + r"^FLAG \| ([A-Z0-9]+)\b", subject, ) if search: