@atmosx/event-product-parser 2.0.15 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +4 -237
  2. package/dist/cjs/index.cjs +2233 -3100
  3. package/dist/esm/index.mjs +2233 -3103
  4. package/package.json +3 -3
  5. package/src/@building/building.clean.ts +30 -0
  6. package/src/@building/building.create.ts +42 -0
  7. package/src/@building/building.enhance.ts +56 -0
  8. package/src/@building/building.geometry.ts +42 -0
  9. package/src/@building/building.headers.ts +37 -0
  10. package/src/@building/building.office.ts +43 -0
  11. package/src/@building/building.polygon.ts +71 -0
  12. package/src/@building/building.properties.ts +89 -0
  13. package/src/@building/building.signature.ts +78 -0
  14. package/src/@building/building.tags.ts +24 -0
  15. package/src/@building/building.tracking.ts +68 -0
  16. package/src/@building/building.validate.ts +132 -0
  17. package/src/@core/core.callback.ts +39 -0
  18. package/src/@core/core.getEvents.ts +25 -0
  19. package/src/@core/core.getNodes.ts +25 -0
  20. package/src/@core/core.listener.ts +24 -0
  21. package/src/@core/core.setNode.ts +81 -0
  22. package/src/@core/core.start.ts +54 -0
  23. package/src/@core/core.stop.ts +32 -0
  24. package/src/@dictionaries/dictionaries.betterEventNames.ts +85 -0
  25. package/src/@dictionaries/dictionaries.eventActions.ts +28 -0
  26. package/src/@dictionaries/{awips.ts → dictionaries.eventAwipAbreviations.ts} +12 -6
  27. package/src/@dictionaries/dictionaries.eventCancelMessages.ts +29 -0
  28. package/src/@dictionaries/dictionaries.eventCauses.ts +36 -0
  29. package/src/@dictionaries/dictionaries.eventProducts.ts +25 -0
  30. package/src/@dictionaries/dictionaries.eventRecords.ts +25 -0
  31. package/src/@dictionaries/dictionaries.eventSeverity.ts +27 -0
  32. package/src/@dictionaries/dictionaries.eventStatus.ts +31 -0
  33. package/src/@dictionaries/{signatures.ts → dictionaries.eventTags.ts} +13 -68
  34. package/src/@dictionaries/dictionaries.eventTypes.ts +82 -0
  35. package/src/@dictionaries/dictionaries.eventsOffshore.ts +31 -0
  36. package/src/@dictionaries/dictionaries.hailStrings.ts +31 -0
  37. package/src/@dictionaries/{icao.ts → dictionaries.officeICAOs.ts} +13 -6
  38. package/src/@dictionaries/dictionaries.regExp.ts +28 -0
  39. package/src/@dictionaries/dictionaries.shapefileLinks.ts +36 -0
  40. package/src/@dictionaries/dictionaries.statusCorrelationText.ts +40 -0
  41. package/src/@dictionaries/dictionaries.test_signatures.ts +23 -0
  42. package/src/@dictionaries/dictionaries.transcribedMessageReplacements.ts +68 -0
  43. package/src/@events/events.api.ts +113 -0
  44. package/src/@events/events.text.ts +79 -0
  45. package/src/@events/events.ugc.ts +83 -0
  46. package/src/@events/events.vtec.ts +87 -0
  47. package/src/@manager/manager.mkEvent.ts +79 -0
  48. package/src/@manager/manager.rmEvent.ts +44 -0
  49. package/src/@manager/manager.setHash.ts +37 -0
  50. package/src/@manager/manager.updateNodes.ts +51 -0
  51. package/src/@modules/@database/database.cache.ts +48 -0
  52. package/src/@modules/@database/database.init.ts +45 -0
  53. package/src/@modules/@database/database.shapefiles.ts +97 -0
  54. package/src/@modules/@database/database.stanza.ts +47 -0
  55. package/src/@modules/@stanza/stanza.getAwipsType.ts +46 -0
  56. package/src/@modules/@stanza/stanza.validate.ts +50 -0
  57. package/src/@modules/@utilities/utilities.createHttp.ts +75 -0
  58. package/src/@modules/@utilities/utilities.getFormattedTime.ts +43 -0
  59. package/src/@modules/@utilities/utilities.getSettings.ts +25 -0
  60. package/src/@modules/@utilities/utilities.getShapeNearestPoint.ts +114 -0
  61. package/src/@modules/@utilities/utilities.setCronSchedule.ts +38 -0
  62. package/src/@modules/@utilities/utilities.setEventEmit.ts +41 -0
  63. package/src/@modules/@utilities/utilities.setListener.ts +30 -0
  64. package/src/@modules/@utilities/utilities.setSettings.ts +42 -0
  65. package/src/@modules/@utilities/utilities.setSleep.ts +33 -0
  66. package/src/@modules/@utilities/utilities.setTimeoutAction.ts +59 -0
  67. package/src/@modules/@utilities/utilities.setWarning.ts +34 -0
  68. package/src/@modules/@xmpp/xmpp.xDeploy.ts +58 -0
  69. package/src/@modules/@xmpp/xmpp.xError.ts +38 -0
  70. package/src/@modules/@xmpp/xmpp.xOffline.ts +38 -0
  71. package/src/@modules/@xmpp/xmpp.xOnline.ts +61 -0
  72. package/src/@modules/@xmpp/xmpp.xReconnect.ts +59 -0
  73. package/src/@modules/@xmpp/xmpp.xStanza.ts +63 -0
  74. package/src/@parsers/@hvtec/hvtec.extract.ts +40 -0
  75. package/src/@parsers/@pvtec/pvtec.expires.ts +26 -0
  76. package/src/@parsers/@pvtec/pvtec.extract.ts +50 -0
  77. package/src/@parsers/@text/text.getDescriptionFromProduct.ts +53 -0
  78. package/src/@parsers/@text/text.getPolygonFromProduct.ts +32 -0
  79. package/src/@parsers/@text/text.getTextFromProduct.ts +43 -0
  80. package/src/@parsers/@text/text.getXML.ts +61 -0
  81. package/src/@parsers/@ugc/ugc.coordinates.ts +110 -0
  82. package/src/@parsers/@ugc/ugc.expiry.ts +32 -0
  83. package/src/@parsers/@ugc/ugc.extract.ts +37 -0
  84. package/src/@parsers/@ugc/ugc.header.ts +30 -0
  85. package/src/@parsers/@ugc/ugc.locations.ts +29 -0
  86. package/src/@parsers/@ugc/ugc.zones.ts +52 -0
  87. package/src/@types/type.event.ts +67 -0
  88. package/src/@types/type.properties.ts +75 -0
  89. package/src/@types/types.attributes.ts +28 -0
  90. package/src/@types/types.compiled.ts +35 -0
  91. package/src/@types/types.hash.ts +24 -0
  92. package/src/@types/types.hvtec.ts +25 -0
  93. package/src/@types/types.pvtec.ts +29 -0
  94. package/src/@types/types.settings.ts +71 -0
  95. package/src/@types/types.stanza.ts +37 -0
  96. package/src/@types/types.ugc.ts +24 -0
  97. package/src/bootstrap.ts +82 -164
  98. package/src/index.ts +48 -216
  99. package/test.js +65 -49
  100. package/src/@dictionaries/events.ts +0 -168
  101. package/src/@parsers/@events/api.ts +0 -146
  102. package/src/@parsers/@events/cap.ts +0 -123
  103. package/src/@parsers/@events/text.ts +0 -104
  104. package/src/@parsers/@events/ugc.ts +0 -107
  105. package/src/@parsers/@events/vtec.ts +0 -76
  106. package/src/@parsers/events.ts +0 -392
  107. package/src/@parsers/hvtec.ts +0 -46
  108. package/src/@parsers/pvtec.ts +0 -72
  109. package/src/@parsers/stanza.ts +0 -97
  110. package/src/@parsers/text.ts +0 -165
  111. package/src/@parsers/ugc.ts +0 -247
  112. package/src/@submodules/database.ts +0 -200
  113. package/src/@submodules/eas.ts +0 -490
  114. package/src/@submodules/utils.ts +0 -184
  115. package/src/@submodules/xmpp.ts +0 -142
  116. package/src/types.ts +0 -259
@@ -1,392 +0,0 @@
1
- /*
2
- _ _ __ __
3
- /\ | | | | (_) \ \ / /
4
- / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
- / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
- / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
- /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
- | |
9
- |_|
10
-
11
- Written by: KiyoWx (k3yomi)
12
- */
13
-
14
- import * as loader from '../bootstrap';
15
- import * as types from '../types';
16
- import TextParser from './text';
17
- import UGCParser from './ugc';
18
- import VTECAlerts from './@events/vtec';
19
- import UGCAlerts from './@events/ugc';
20
- import TextAlerts from './@events/text';
21
- import CAPAlerts from './@events/cap';
22
- import APIAlerts from './@events/api';
23
-
24
- export class EventParser {
25
-
26
- /**
27
- * @function getBaseProperties
28
- * @description
29
- * Extracts and compiles the core properties of a weather
30
- * alert message into a structured object. Combines parsed
31
- * textual data, UGC information, VTEC entries, and additional
32
- * metadata for downstream use.
33
- *
34
- * @static
35
- * @async
36
- * @param {string} message
37
- * @param {types.StanzaCompiled} metadata
38
- * @param {types.UGCEntry} [ugc=null]
39
- * @param {types.PVtecEntry} [pVtec=null]
40
- * @param {types.HVtecEntry} [hVtec=null]
41
- * @returns {Promise<Record<string, any>>}
42
- */
43
- public static async getBaseProperties(message: string, metadata: types.DefaultAttributesType, ugc: types.UGCEntry = null, pVtec: types.PVtecEntry = null, hVtec: types.HVtecEntry = null): Promise<Record<string, any>> {
44
- const settings = loader.settings as types.ClientSettingsTypes;
45
- const definitions = {
46
- tornado: TextParser.textProductToString(message, `TORNADO...`) ?? TextParser.textProductToString(message, `WATERSPOUT...`) ?? null,
47
- hail: TextParser.textProductToString(message, `MAX HAIL SIZE...`, [`IN`]) ?? TextParser.textProductToString(message, `HAIL...`, [`IN`]) ?? null,
48
- gusts: TextParser.textProductToString(message, `MAX WIND GUST...`) ?? TextParser.textProductToString(message, `WIND...`) ?? null,
49
- flood: TextParser.textProductToString(message, `FLASH FLOOD...`) ?? null,
50
- damage: TextParser.textProductToString(message, `DAMAGE THREAT...`) ?? null,
51
- source: TextParser.textProductToString(message, `SOURCE...`, [`.`]) ?? null,
52
- description: TextParser.textProductToDescription(message, pVtec?.raw ?? null),
53
- polygon: TextParser.textProductToPolygon(message),
54
- wmo: message.match(loader.definitions.regular_expressions.wmo)?.[0] ?? null,
55
- mdTorIntensity: TextParser.textProductToString(message, `MOST PROBABLE PEAK TORNADO INTENSITY...`) ?? null,
56
- mdWindGusts: TextParser.textProductToString(message, `MOST PROBABLE PEAK WIND GUST...`) ?? null,
57
- mdHailSize: TextParser.textProductToString(message, `MOST PROBABLE PEAK HAIL SIZE...`) ?? null,
58
- };
59
- const getOffice = this.getICAO(pVtec, metadata, definitions.wmo);
60
- const getCorrectIssued = this.getCorrectIssuedDate(metadata);
61
- const getCorrectExpiry = this.getCorrectExpiryDate(pVtec, ugc);
62
- const base = {
63
- locations: ugc?.locations.join(`; `) ?? `No Location Specified (UGC Missing)`,
64
- issued: getCorrectIssued,
65
- expires: getCorrectExpiry,
66
- geocode: {
67
- UGC: ugc?.zones ?? [],
68
- generated: definitions.polygon.length > 0 ? Buffer.from(JSON.stringify([definitions.polygon])).toString('base64') : null
69
- },
70
- description: definitions.description,
71
- instruction: null,
72
- sender_name: getOffice.name,
73
- sender_icao: getOffice.icao,
74
- raw: {...Object.fromEntries(Object.entries(metadata).filter(([key]) => key !== 'message'))},
75
- parameters: {
76
- wmo: Array.isArray(definitions.wmo) ? definitions.wmo[0] : (definitions.wmo ?? null),
77
- source: definitions.source,
78
- max_hail_size: definitions.hail,
79
- max_wind_gust: definitions.gusts,
80
- damage_threat: definitions.damage,
81
- tornado_detection: definitions.tornado,
82
- flood_detection: definitions.flood,
83
- discussion_tornado_intensity: definitions.mdTorIntensity,
84
- discussion_wind_intensity: definitions.mdWindGusts,
85
- discussion_hail_intensity: definitions.mdHailSize,
86
- },
87
- };
88
- return base;
89
- }
90
-
91
- /**
92
- * @function getEventGeometry
93
- * @description
94
- * Determines the geometry of an event using polygon data fromEntries
95
- * in the message or UGC shapefile coordinates if enabled in settings. Falls
96
- * back to null if no geometry can be determined.
97
- *
98
- * @static
99
- * @param {string} generated
100
- * @param {types.UGCEntry} [ugc=null]
101
- * @returns {Promise<types.geometry>}
102
- */
103
- public static async getEventGeometry(generated: string, ugc: types.UGCEntry = null, isUnion: boolean = true) : Promise<types.geometry> {
104
- const settings = loader.settings as types.ClientSettingsTypes;
105
- let geometry = { type: "Polygon", coordinates: generated != null ? JSON.parse(Buffer.from(generated, 'base64').toString('utf-8')) : null };
106
- if (settings.global_settings.shapefile_coordinates && generated == null && ugc != null) {
107
- const coordinates = await UGCParser.getCoordinates(ugc.zones, isUnion) as any;
108
- geometry = coordinates
109
- }
110
- return geometry;
111
- }
112
-
113
- /**
114
- * @function betterParsedEventName
115
- * @description
116
- * Enhances the parsing of an event name using additional criteria
117
- * from its description and parameters. Can optionally use
118
- * the original parent event name instead.
119
- *
120
- * @static
121
- * @param {types.EventCompiled} event
122
- * @param {boolean} [betterParsing=false]
123
- * @param {boolean} [useParentEvents=false]
124
- * @returns {string}
125
- */
126
- public static betterParsedEventName(event: types.EventCompiled, betterParsing?: boolean, useParentEvents?: boolean): string {
127
- let eventName = event?.properties?.event ?? `Unknown Event`;
128
- const defEventTable = loader.definitions.enhancedEvents;
129
- const properties = event?.properties;
130
- const parameters = properties?.parameters;
131
- const description = (properties?.description ?? `Unknown Description`).toLowerCase();
132
- const damageThreatTag = parameters?.damage_threat ?? null;
133
- const tornadoThreatTag = parameters?.tornado_detection ?? null;
134
- if (!betterParsing) { return eventName }
135
- for (const eventGroup of defEventTable) {
136
- const [baseEvent, conditions] = Object.entries(eventGroup)[0] as [string, Record<string, types.EnhancedEventCondition>];
137
- if (eventName === baseEvent) {
138
- for (const [specificEvent, condition] of Object.entries(conditions)) {
139
- let conditionMet = false;
140
- if (condition.description) {
141
- conditionMet = description.includes(condition.description.toLowerCase());
142
- if (!conditionMet) continue;
143
- }
144
- if (!conditionMet && condition.condition) {
145
- const tagToCheck = baseEvent.includes('Tornado') || baseEvent.includes('Special Marine Warning') ? tornadoThreatTag : damageThreatTag;
146
- conditionMet = condition.condition(tagToCheck);
147
- }
148
- if (conditionMet) {
149
- eventName = specificEvent;
150
- break;
151
- }
152
- }
153
- if (baseEvent === 'Severe Thunderstorm Warning' && tornadoThreatTag === 'POSSIBLE' && !eventName.includes('(TPROB)')) {
154
- eventName += ' (TPROB)';
155
- }
156
- break;
157
- }
158
- }
159
- return useParentEvents ? event?.properties?.event : eventName;
160
- }
161
-
162
- /**
163
- * @function validateEvents
164
- * @description
165
- * Processes an array of event objects and filters them based on
166
- * global and EAS filtering settings, and
167
- * other criteria such as expired or test products. Valid events
168
- * trigger relevant event emitters.
169
- *
170
- * @static
171
- * @param {unknown[]} events
172
- * @returns {Promise<void>}
173
- */
174
- public static async validateEvents(events: unknown[]): Promise<void> {
175
- if (events.length == 0) return;
176
- const filteringSettings = loader.settings?.global_settings?.filtering;
177
- const easSettings = loader.settings?.global_settings?.eas_settings;
178
- const globalSettings = loader.settings?.global_settings;
179
- const sets = {} as Record<string, Set<string>>;
180
- const bools = {} as Record<string, boolean>;
181
- const megered = {...filteringSettings, ...easSettings, ...globalSettings };
182
- for (const key in megered) {
183
- const setting = megered[key];
184
- if (Array.isArray(setting)) { sets[key] = new Set(setting.map(item => item.toLowerCase())); }
185
- if (typeof setting === 'boolean') { bools[key] = setting; }
186
- }
187
- const filtered = events.filter((event: types.EventCompiled) => {
188
- const originalEvent = this.buildDefaultSignature(event);
189
- const props = originalEvent?.properties;
190
- const ugcs = props?.geocode?.UGC ?? [];
191
- const { details, ...properties } = originalEvent.properties;
192
- originalEvent.properties.parent = originalEvent.properties.event;
193
- originalEvent.properties.event = this.betterParsedEventName(originalEvent, bools?.better_event_parsing, bools?.parent_events_only);
194
- originalEvent.properties.hash = loader.packages.crypto.createHash('md5').update(JSON.stringify(properties)).digest('hex');
195
- if (originalEvent.properties.is_test == true) {
196
- loader.cache.events.emit(`onTest`, originalEvent);
197
- if (bools?.ignore_test_products) {
198
- return false;
199
- }
200
- }
201
- if (originalEvent.properties.is_cancelled == true) {
202
- loader.cache.events.emit(`onExpired`, originalEvent);
203
- if (bools?.check_expired) {
204
- return false;
205
- }
206
- }
207
- loader.cache.events.emit(`on${originalEvent.properties.event.replace(/\s+/g, '')}`, originalEvent);
208
- for (const key in sets) {
209
- const setting = sets[key];
210
- if (key === 'events' && setting.size > 0 && !setting.has(originalEvent.properties.event.toLowerCase())) {
211
- loader.cache.events.emit(`onFilteredEvent`, originalEvent); return false
212
- }
213
- if (key === 'ignored_events' && setting.size > 0 && setting.has(originalEvent.properties.event.toLowerCase())) {
214
- loader.cache.events.emit(`onIgnoredEvent`, originalEvent); return false
215
- }
216
- if (key === 'filtered_icao' && setting.size > 0 && props.sender_icao != null && !setting.has(props.sender_icao.toLowerCase())) {
217
- loader.cache.events.emit(`onFilteredICAO`, originalEvent); return false
218
- }
219
- if (key === 'ignored_icao' && setting.size > 0 && props.sender_icao != null && setting.has(props.sender_icao.toLowerCase())) {
220
- loader.cache.events.emit(`onIgnoredICAO`, originalEvent); return false
221
- }
222
- if (key === 'ugc_filter' && setting.size > 0 && ugcs.length > 0 && !ugcs.some((ugc: string) => setting.has(ugc.toLowerCase()))) {
223
- loader.cache.events.emit(`onFilteredUGC`, originalEvent); return false
224
- }
225
- if (key === 'state_filter' && setting.size > 0 && ugcs.length > 0 && !ugcs.some((ugc: string) => setting.has(ugc.substring(0, 2).toLowerCase()))) {
226
- loader.cache.events.emit(`onFilteredState`, originalEvent); return false
227
- }
228
- }
229
- return true;
230
- })
231
- for (const event of filtered as types.EventCompiled[]) {
232
- if (!loader.settings.global_settings.ignore_geometry_parsing) {
233
- const geometry = await this.getEventGeometry(event.properties.geocode.generated, {
234
- zones: event.properties.geocode != null ? event.properties.geocode.UGC : null
235
- })
236
- event.geometry = geometry;
237
- }
238
- }
239
- if (filtered.length > 0) { loader.cache.events.emit(`onEvents`, filtered); }
240
- }
241
-
242
- /**
243
- * @function getHeader
244
- * @description
245
- * Constructs a standardized alert header string using provided
246
- * stanza attributes, event properties, and optional VTEC data.
247
- *
248
- * @static
249
- * @param {types.StanzaAttributes} attributes
250
- * @param {types.EventProperties} [properties]
251
- * @param {types.PVtecEntry} [pVtec]
252
- * @returns {string}
253
- */
254
- public static getHeader(attributes: types.StanzaAttributes, properties?: types.EventProperties, pVtec?: types.PVtecEntry): string {
255
- const parent = `ATSX`
256
- const alertType = attributes?.awipsType?.type ?? attributes?.getAwip?.prefix ?? `XX`;
257
- const ugc = properties?.geocode?.UGC != null ? properties?.geocode?.UGC.join(`-`) : `000000`;
258
- const status = pVtec?.status ?? 'Issued';
259
- const issued = properties?.issued != null ? new Date(properties?.issued)?.toISOString().replace(/[-:]/g, '').split('.')[0] : new Date().toISOString().replace(/[-:]/g, '').split('.')[0];
260
- const sender = properties?.sender_icao ?? `XXXX`;
261
- const header = `ZCZC-${parent}-${alertType}-${ugc}-${status}-${issued}-${sender}-`;
262
- return header
263
- }
264
-
265
- /**
266
- * @function eventHandler
267
- * @description
268
- * Routes a validated stanza object to the appropriate alert handler
269
- * based on its type flags: API, CAP, pVTEC (Primary VTEC), UGC, or plain text.
270
- *
271
- * @static
272
- * @param {types.StanzaCompiled} metadata
273
- * @returns {void}
274
- */
275
- public static eventHandler(metadata: types.StanzaCompiled): Promise<void> {
276
- const settings = loader.settings as types.ClientSettingsTypes;
277
- const preferences = settings.noaa_weather_wire_service_settings.preferences;
278
- if (metadata.isApi) return APIAlerts.event(metadata)
279
- if (metadata.isCap) return CAPAlerts.event(metadata)
280
- if (!preferences.disable_vtec && !metadata.isCap && metadata.isPVtec && metadata.isUGC) return VTECAlerts.event(metadata);
281
- if (!preferences.disable_ugc && !metadata.isCap && !metadata.isPVtec && metadata.isUGC) return UGCAlerts.event(metadata);
282
- if (!preferences.disable_text && !metadata.isCap && !metadata.isPVtec && !metadata.isUGC) return TextAlerts.event(metadata);
283
- return;
284
- }
285
-
286
- /**
287
- * @function getICAO
288
- * @description
289
- * Determines the ICAO code and corresponding name for an event.
290
- * Priority is given to the VTEC tracking code, then the attributes' `cccc` property,
291
- * and finally the WMO code if available. Returns null if none are found.
292
- *
293
- * @private
294
- * @static
295
- * @param {types.PVtecEntry | null} pVtec
296
- * @param {Record<string, string>} metadata
297
- * @param {RegExpMatchArray | string | null} WMO
298
- * @returns {{ icao: string; name: string }}
299
- */
300
- private static getICAO(pVtec: types.PVtecEntry, metadata: types.DefaultAttributesType, WMO: RegExpMatchArray | string | null): { icao: string; name: string } {
301
- const icao = pVtec != null ? pVtec?.tracking.split(`-`)[0] : (metadata.attributes?.cccc || (WMO != null ? (Array.isArray(WMO) ? WMO[0] : WMO) : null));
302
- const name = loader.definitions.ICAO?.[icao] ?? null;
303
- return { icao, name };
304
- }
305
-
306
- /**
307
- * @function getCorrectIssuedDate
308
- * @description
309
- * Determines the issued date for an event based on the provided attributes.
310
- * Falls back to the current date and time if no valid issue date is available.
311
- *
312
- * @private
313
- * @static
314
- * @param {Record<string, string>} metadata
315
- * @returns {string}
316
- */
317
- private static getCorrectIssuedDate(metadata: types.DefaultAttributesType): string {
318
- const time = metadata.attributes.issue != null ?
319
- new Date(metadata.attributes.issue).toISOString() : (metadata.attributes?.issue != null ?
320
- new Date(metadata.attributes.issue).toISOString() :
321
- new Date().toISOString());
322
- return time;
323
- }
324
-
325
- /**
326
- * @function getCorrectExpiryDate
327
- * @description
328
- * Determines the most appropriate expiry date for an event using VTEC or UGC data.
329
- * Falls back to one hour from the current time if no valid expiry is available.
330
- *
331
- * @private
332
- * @static
333
- * @param {types.PVtecEntry} pVtec
334
- * @param {types.UGCEntry} ugc
335
- * @returns {string}
336
- */
337
- private static getCorrectExpiryDate(pVtec: types.PVtecEntry, ugc: types.UGCEntry): string | Date {
338
- const time = pVtec?.expires && !isNaN(new Date(pVtec.expires).getTime()) ?
339
- new Date(pVtec.expires).toISOString() : (ugc?.expiry != null ? new Date(ugc.expiry).toISOString() : new Date(new Date().getTime() + 1 * 60 * 60 * 1000))
340
- if (isNaN(new Date(time).getTime())) {
341
- return `Until Further Notice`
342
- }
343
- return time;
344
- }
345
-
346
- /**
347
- * @function buildDefaultSignature
348
- * @description
349
- * Populates default properties for an event object, including action type flags,
350
- * tags, and status updates. Determines if the event is issued, updated, or cancelled
351
- * based on correlations, description content, VTEC codes, and expiration time.
352
- *
353
- * @private
354
- * @static
355
- * @param {any} event
356
- * @returns {types.EventCompiled}
357
- */
358
- private static buildDefaultSignature(event: types.EventCompiled): types.EventCompiled {
359
- const props = event.properties ?? {};
360
- const statusCorrelation = loader.definitions.correlations.find((c: { type: string }) => c.type === props.action_type);
361
- const defEventTags = loader.definitions.tags;
362
- const tags = Object.entries(defEventTags).filter(([key]) => props?.description?.toLowerCase().includes(key.toLowerCase())).map(([, value]) => value)
363
- props.tags = tags.length > 0 ? tags : [];
364
- if (statusCorrelation) {
365
- props.action_type = statusCorrelation.forward ?? props.action_type;
366
- props.is_updated = !!statusCorrelation.update; props.is_issued = !!statusCorrelation.new;
367
- props.is_cancelled = !!statusCorrelation.cancel;
368
- } else {
369
- props.is_issued = true
370
- }
371
- if (props.description) {
372
- const detectedPhrase = loader.definitions.cancelSignatures.find(sig => props.description.toLowerCase().includes(sig.toLowerCase()));
373
- if (detectedPhrase) {
374
- props.is_cancelled = true;
375
- }
376
- }
377
- if (props.details?.pvtec) {
378
- const getType = props.details.pvtec.split(`.`)[0]?.replace(`/`, ``)
379
- const isTestProduct = loader.definitions.productTypes[getType] == `Test Product`
380
- const isTestSig = [`This is a test message`, `THIS_MESSAGE_IS_FOR_TEST_PURPOSES_ONLY`]
381
- if (isTestProduct || isTestSig.some(sig => props?.description?.toLowerCase().includes(sig.toLowerCase()) || props?.instruction?.toLowerCase().includes(sig.toLowerCase()))) {
382
- props.is_test = true
383
- }
384
- }
385
- if (new Date(props?.expires).getTime() < new Date().getTime()) {
386
- props.is_cancelled = true
387
- }
388
- return event;
389
- }
390
- }
391
-
392
- export default EventParser
@@ -1,46 +0,0 @@
1
- /*
2
- _ _ __ __
3
- /\ | | | | (_) \ \ / /
4
- / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
- / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
- / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
- /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
- | |
9
- |_|
10
-
11
- Written by: KiyoWx (k3yomi)
12
- */
13
-
14
- import * as loader from '../bootstrap';
15
- import * as types from '../types';
16
-
17
- export class HVtecParser {
18
-
19
- /**
20
- * @function HVtecExtractor
21
- * @description
22
- * Extracts VTEC entries from a raw NWWS message string and returns
23
- * structured objects containing type, tracking, event, status,
24
- * WMO identifiers, and expiry date.
25
- *
26
- * @static
27
- * @param {string} message
28
- * @returns {Promise<types.HtecEntry[] | null>}
29
- */
30
- public static async HVtecExtractor(message: string): Promise<types.HVtecEntry[] | null> {
31
- const matches = message.match(loader.definitions.regular_expressions.hvtec);
32
- if (!matches || matches.length !== 1) return null;
33
- const hvtec = matches[0];
34
- const parts = hvtec.split('.');
35
- if (parts.length < 7) return null;
36
- const hvtecs: types.HVtecEntry[] = [{
37
- severity: loader.definitions.severity[parts[1]],
38
- cause: loader.definitions.causes[parts[2]],
39
- record: loader.definitions.records[parts[6]],
40
- raw: hvtec,
41
- }];
42
- return hvtecs;
43
- }
44
- }
45
-
46
- export default HVtecParser;
@@ -1,72 +0,0 @@
1
- /*
2
- _ _ __ __
3
- /\ | | | | (_) \ \ / /
4
- / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
- / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
- / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
- /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
- | |
9
- |_|
10
-
11
- Written by: KiyoWx (k3yomi)
12
- */
13
-
14
- import * as loader from '../bootstrap';
15
- import * as types from '../types';
16
-
17
- export class PVtecParser {
18
-
19
- /**
20
- * @function pVtecExtractor
21
- * @description
22
- * Extracts VTEC entries from a raw NWWS message string and returns
23
- * structured objects containing type, tracking, event, status,
24
- * WMO identifiers, and expiry date.
25
- *
26
- * @static
27
- * @param {string} message
28
- * @returns {Promise<types.VtecEntry[] | null>}
29
- */
30
- public static async pVtecExtractor(message: string): Promise<types.PVtecEntry[] | null> {
31
- const matches = message.match(loader.definitions.regular_expressions.pvtec) ?? [];
32
- const pVtecs: types.PVtecEntry[] = [];
33
- for (const pvtec of matches) {
34
- const parts = pvtec.split('.');
35
- if (parts.length < 7) continue;
36
- const dates = parts[6].split('-');
37
- pVtecs.push({
38
- raw: pvtec,
39
- type: loader.definitions.productTypes[parts[0]],
40
- tracking: `${parts[2]}-${parts[3]}-${parts[4]}-${parts[5]}`,
41
- event: `${loader.definitions.events[parts[3]]} ${loader.definitions.actions[parts[4]]}`,
42
- status: loader.definitions.status[parts[1]],
43
- wmo: message.match(loader.definitions.regular_expressions.wmo)?.[0] || null,
44
- expires: this.parseExpiryDate(dates),
45
- isKWNS: (parts[4] == `A` || parts[4] == `Y`) && (parts[3] == `TO` || parts[3] == `SV`) ? true : false,
46
- });
47
- }
48
- return pVtecs.length > 0 ? pVtecs : null;
49
- }
50
-
51
- /**
52
- * @function parseExpiryDate
53
- * @description
54
- * Converts a NWWS VTEC/expiry timestamp string into a formatted local ISO date string
55
- * with an Eastern Time offset (-04:00). Returns `Invalid Date Format` if the input
56
- * is `000000T0000Z`.
57
- *
58
- * @private
59
- * @static
60
- * @param {string[]} args
61
- * @returns {string}
62
- */
63
- private static parseExpiryDate(args: String[]): string {
64
- if (args[1] == `000000T0000Z`) return `Invalid Date Format`;
65
- const expires = `${new Date().getFullYear().toString().substring(0, 2)}${args[1].substring(0, 2)}-${args[1].substring(2, 4)}-${args[1].substring(4, 6)}T${args[1].substring(7, 9)}:${args[1].substring(9, 11)}:00`;
66
- const local = new Date(new Date(expires).getTime() - 4 * 60 * 60000);
67
- const pad = (n: number) => n.toString().padStart(2, '0');
68
- return `${local.getFullYear()}-${pad(local.getMonth() + 1)}-${pad(local.getDate())}T${pad(local.getHours())}:${pad(local.getMinutes())}:00.000-04:00`;
69
- }
70
- }
71
-
72
- export default PVtecParser;
@@ -1,97 +0,0 @@
1
- /*
2
- _ _ __ __
3
- /\ | | | | (_) \ \ / /
4
- / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
- / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
- / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
- /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
- | |
9
- |_|
10
-
11
- Written by: KiyoWx (k3yomi)
12
- */
13
-
14
- import * as loader from '../bootstrap';
15
- import * as types from '../types';
16
-
17
- export class StanzaParser {
18
-
19
- /**
20
- * @function validate
21
- * @description
22
- * Validates and parses a stanza message, extracting its attributes and metadata.
23
- * Handles both raw message strings (for debug/testing) and actual stanza objects.
24
- * Determines whether the message is a CAP alert, contains VTEC codes, or contains UGCs,
25
- * and identifies the AWIPS product type and prefix.
26
- *
27
- * @static
28
- * @param {any} stanza
29
- * @param {boolean | types.StanzaAttributes} [isDebug=false]
30
- * @returns {{
31
- * message: string;
32
- * attributes: types.StanzaAttributes;
33
- * isCap: boolean,
34
- * isPVtec: boolean;
35
- * isCapDescription: boolean;
36
- * awipsType: Record<string, string>;
37
- * isApi: boolean;
38
- * ignore: boolean;
39
- * isUGC?: boolean;
40
- * }}
41
- */
42
- public static validate(stanza: any, isDebug: boolean | types.StanzaAttributes = false): { message: string; attributes: types.StanzaAttributes; isCap: any; isPVtec: boolean; isCapDescription: any; awipsType: any; isApi: boolean; ignore: boolean; isUGC?: boolean; } {
43
- if (isDebug !== false) {
44
- const vTypes = isDebug as types.StanzaAttributes;
45
- const message = stanza;
46
- const attributes = vTypes;
47
- const isCap = vTypes.isCap ?? message.includes(`<?xml`);
48
- const isCapDescription = message.includes(`<areaDesc>`);
49
- const isPVtec = message.match(loader.definitions.regular_expressions.pvtec) != null;
50
- const isUGC = message.match(loader.definitions.regular_expressions.ugc1) != null;
51
- const awipsType = this.getType(attributes);
52
- return { message, attributes, isCap, isPVtec, isUGC, isCapDescription, awipsType: awipsType, isApi: false, ignore: false };
53
- }
54
- if (stanza.is(`message`)) {
55
- let cb = stanza.getChild(`x`)
56
- if (cb && cb.children) {
57
- let message = unescape(cb.children[0]);
58
- let attributes = cb.attrs
59
- if (attributes.awipsid && attributes.awipsid.length > 1) {
60
- const isCap = message.includes(`<?xml`);
61
- const isCapDescription = message.includes(`<areaDesc>`);
62
- const isPVtec = message.match(loader.definitions.regular_expressions.pvtec) != null;
63
- const isUGC = message.match(loader.definitions.regular_expressions.ugc1) != null;
64
- const awipsType = this.getType(attributes);
65
- return { message, attributes, isCap, isPVtec, isUGC, isCapDescription, awipsType: awipsType, isApi: false, ignore: false };
66
- }
67
- }
68
- }
69
- return { message: null, attributes: null, isApi: null, isCap: null, isPVtec: null, isUGC: null, isCapDescription: null, awipsType: null, ignore: true };
70
- }
71
-
72
- /**
73
- * @function getType
74
- * @description
75
- * Determines the AWIPS product type and prefix from a stanza's attributes.
76
- * Returns a default type of 'XX' if the attributes are missing or the AWIPS ID
77
- * does not match any known definitions.
78
- *
79
- * @private
80
- * @static
81
- * @param {unknown} attributes
82
- * @returns {Record<string, string>}
83
- */
84
- private static getType(attributes: unknown): Record<string, string> {
85
- const attrs = attributes as types.StanzaAttributesType | undefined;
86
- if (!attrs?.awipsid) return { type: 'XX', prefix: 'XX' };
87
- const awipsDefs = loader.definitions.awips as Record<string, string>;
88
- for (const [prefix, type] of Object.entries(awipsDefs)) {
89
- if (attrs.awipsid.startsWith(prefix)) {
90
- return { type, prefix };
91
- }
92
- }
93
- return { type: 'XX', prefix: 'XX' };
94
- }
95
- }
96
-
97
- export default StanzaParser;