@atmosx/event-product-parser 3.0.4 → 3.0.6

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 (162) hide show
  1. package/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +1 -1
  2. package/.github/README.md +163 -0
  3. package/.github/SUPPORTED_EVENTS.md +370 -0
  4. package/.github/docs/docs-functions.md +30 -0
  5. package/.github/docs/docs-general-configurations.md +209 -0
  6. package/.github/docs/docs-integrations.md +37 -0
  7. package/.github/docs/docs-listeners.md +28 -0
  8. package/.github/docs/docs-source-configuration.md +218 -0
  9. package/.github/docs/docs-technical.md +50 -0
  10. package/.github/logo.png +0 -0
  11. package/.github/samples/SAMPLE_EAS_GENERATION.wav +0 -0
  12. package/.github/samples/SAMPLE_GEOJSON.json +204 -0
  13. package/.github/samples/SAMPLE_TEXT_PRODUCT.txt +119 -0
  14. package/dist/cjs/index.cjs +4677 -13422
  15. package/dist/esm/index.mjs +3159 -11906
  16. package/package.json +3 -9
  17. package/src/@building/{building.create.ts → CreateEvent.ts} +25 -11
  18. package/src/@building/{building.clean.ts → GetCleanedEvent.ts} +2 -2
  19. package/src/@building/GetEventAttachments.ts +86 -0
  20. package/src/@building/{building.enhance.ts → GetEventEnhancedName.ts} +11 -12
  21. package/src/@building/{building.geometry.ts → GetEventGeometry.ts} +7 -7
  22. package/src/@building/{building.headers.ts → GetEventHeader.ts} +3 -3
  23. package/src/@building/{building.polygon.ts → GetEventNodes.ts} +13 -11
  24. package/src/@building/{building.office.ts → GetEventOffice.ts} +6 -6
  25. package/src/@building/GetEventProperties.ts +96 -0
  26. package/src/@building/{building.signature.ts → GetEventSignature.ts} +14 -15
  27. package/src/@building/{building.tags.ts → GetEventTags.ts} +9 -4
  28. package/src/@building/{building.tracking.ts → GetEventTracking.ts} +15 -15
  29. package/src/@building/{building.validate.ts → ValidateEvents.ts} +67 -60
  30. package/src/@core/{core.clearEvents.ts → ClearEvents.ts} +3 -4
  31. package/src/@core/{core.createListener.ts → CreateListener.ts} +3 -3
  32. package/src/@core/{core.getEvents.ts → GetEvents.ts} +1 -2
  33. package/src/@core/{core.getNodes.ts → GetNodes.ts} +1 -2
  34. package/src/@core/{core.getRandomEvent.ts → GetRandomEvent.ts} +1 -2
  35. package/src/@core/{core.getVersion.ts → GetVersion.ts} +1 -2
  36. package/src/@core/ManualEvent.ts +56 -0
  37. package/src/@core/QueryStanza.ts +39 -0
  38. package/src/@core/{core.setNode.ts → SetNode.ts} +8 -8
  39. package/src/@core/{core.start.ts → StartService.ts} +21 -21
  40. package/src/@core/{core.stop.ts → StopService.ts} +1 -1
  41. package/src/@enums/AWIPS.ts +366 -0
  42. package/src/{@dictionaries/dictionaries.eventActions.ts → @enums/Actions.ts} +1 -1
  43. package/src/{@dictionaries/dictionaries.eventCancelMessages.ts → @enums/Cancellation.ts} +2 -1
  44. package/src/{@dictionaries/dictionaries.eventCauses.ts → @enums/Causes.ts} +1 -1
  45. package/src/{@dictionaries/dictionaries.statusCorrelationText.ts → @enums/Correlations.ts} +1 -1
  46. package/src/@enums/Enhanced.ts +70 -0
  47. package/src/{@dictionaries/dictionaries.eventTypes.ts → @enums/Events.ts} +1 -1
  48. package/src/{@dictionaries/dictionaries.regExp.ts → @enums/Expressions.ts} +2 -1
  49. package/src/@enums/GlobalFilter.ts +24 -0
  50. package/src/{@dictionaries/dictionaries.hailStrings.ts → @enums/Hail.ts} +7 -2
  51. package/src/{@dictionaries/dictionaries.officeICAOs.ts → @enums/ICAO.ts} +3 -1
  52. package/src/@enums/Matches.ts +55 -0
  53. package/src/{@dictionaries/dictionaries.eventProducts.ts → @enums/Products.ts} +1 -1
  54. package/src/{@dictionaries/dictionaries.eventRecords.ts → @enums/Records.ts} +1 -1
  55. package/src/{@dictionaries/dictionaries.eventSeverity.ts → @enums/Severity.ts} +1 -1
  56. package/src/{@dictionaries/dictionaries.shapefileLinks.ts → @enums/Shapefiles.ts} +1 -1
  57. package/src/@enums/States.ts +72 -0
  58. package/src/{@dictionaries/dictionaries.eventStatus.ts → @enums/Status.ts} +1 -1
  59. package/src/@enums/Strings.ts +22 -0
  60. package/src/@enums/Tags.ts +167 -0
  61. package/src/{@dictionaries/dictionaries.testSignatures.ts → @enums/Testing.ts} +3 -2
  62. package/src/@events/{events.api.ts → ParseAPI.ts} +42 -38
  63. package/src/@events/{events.text.ts → ParseText.ts} +25 -18
  64. package/src/@events/{events.ugc.ts → ParseUGC.ts} +28 -19
  65. package/src/@events/{events.vtec.ts → ParseVTEC.ts} +21 -21
  66. package/src/@manager/CreateActions.ts +213 -0
  67. package/src/@manager/{manager.mkEvent.ts → MakeEvent.ts} +34 -31
  68. package/src/@manager/RemoveEvent.ts +70 -0
  69. package/src/@manager/{manager.setHash.ts → SetHash.ts} +8 -9
  70. package/src/@manager/{manager.updateEvents.ts → UpdateEvents.ts} +9 -9
  71. package/src/@manager/{manager.updateNodes.ts → UpdateNode.ts} +24 -17
  72. package/src/@modules/@database/{database.cache.ts → GetCachedEvents.ts} +10 -8
  73. package/src/@modules/@database/ImportBroadcastify.ts +70 -0
  74. package/src/@modules/@database/{database.shapefiles.ts → ImportShapefiles.ts} +17 -20
  75. package/src/@modules/@database/{database.stanza.ts → ImportStanza.ts} +5 -6
  76. package/src/@modules/@database/{database.init.ts → InitializeDatabase.ts} +25 -11
  77. package/src/@modules/@eas/GenerateEASMessage.ts +144 -0
  78. package/src/@modules/@eas/GetCleanDescription.ts +91 -0
  79. package/src/@modules/@eas/{eas.getFloatPCM16.ts → GetFloatPCM16.ts} +1 -1
  80. package/src/@modules/@eas/{eas.getMergedPCM16.ts → GetMergedPCM16.ts} +1 -1
  81. package/src/@modules/@eas/{eas.getPCM16.ts → GetPCM16.ts} +1 -1
  82. package/src/@modules/@eas/{eas.getPCMToFloat.ts → GetPCMToFloat.ts} +1 -1
  83. package/src/@modules/@eas/{eas.getSampledPCM16.ts → GetSampledPCM16.ts} +1 -1
  84. package/src/@modules/@eas/GetTTS.ts +54 -0
  85. package/src/@modules/@eas/{eas.getWavPCM16.ts → GetWavPCM16.ts} +1 -1
  86. package/src/@modules/@eas/{eas.setAFSK.ts → SetAFSK.ts} +1 -2
  87. package/src/@modules/@eas/{eas.setAsciiToBits.ts → SetAsciiToBits.ts} +1 -2
  88. package/src/@modules/@eas/{eas.setAttentionTone.ts → SetAttentionTone.ts} +1 -1
  89. package/src/@modules/@eas/{eas.setNoise.ts → SetNoise.ts} +6 -5
  90. package/src/@modules/@eas/{eas.setRadioEffect.ts → SetRadioEffect.ts} +6 -5
  91. package/src/@modules/@eas/{eas.setSameHeader.ts → SetSameHeader.ts} +7 -8
  92. package/src/@modules/@stanza/{stanza.getAwipsType.ts → GetAwipsType.ts} +11 -9
  93. package/src/@modules/@stanza/{stanza.validate.ts → ValidateStanza.ts} +9 -9
  94. package/src/@modules/@utilities/CreateHttp.ts +83 -0
  95. package/src/@modules/@utilities/{utilities.getFormattedTime.ts → GetFormattedTime.ts} +1 -1
  96. package/src/@modules/@utilities/GetLatestIssuance.ts +32 -0
  97. package/src/@modules/@utilities/GetMatched.ts +33 -0
  98. package/src/@modules/@utilities/{utilities.getSettings.ts → GetSettings.ts} +2 -2
  99. package/src/@modules/@utilities/{utilities.getShapeNearestPoint.ts → GetShapeNearestPoint.ts} +1 -1
  100. package/src/@modules/@utilities/{utilities.setCronSchedule.ts → SetCronSchedule.ts} +41 -12
  101. package/src/{@types/types.webhook.ts → @modules/@utilities/SetDebug.ts} +16 -6
  102. package/src/@modules/@utilities/{utilities.setEventEmit.ts → SetEventEmit.ts} +5 -5
  103. package/src/@modules/@utilities/{utilities.setListener.ts → SetListener.ts} +1 -1
  104. package/src/@modules/@utilities/{utilities.setSettings.ts → SetSettings.ts} +2 -2
  105. package/src/@modules/@utilities/{utilities.setSleep.ts → SetSleep.ts} +1 -1
  106. package/src/@modules/@utilities/{utilities.setTimeoutAction.ts → SetTimeoutAction.ts} +8 -3
  107. package/src/@modules/@utilities/{utilities.setWarning.ts → SetWarning.ts} +2 -2
  108. package/src/@modules/@xmpp/{xmpp.xDeploy.ts → DeployXMPP.ts} +13 -14
  109. package/src/@modules/@xmpp/{xmpp.xError.ts → ErrorXMPP.ts} +1 -3
  110. package/src/@modules/@xmpp/{xmpp.xOffline.ts → OfflineXMPP.ts} +3 -4
  111. package/src/@modules/@xmpp/{xmpp.xOnline.ts → OnlineXMPP.ts} +6 -5
  112. package/src/@modules/@xmpp/{xmpp.xReconnect.ts → ReconnectXMPP.ts} +8 -8
  113. package/src/@modules/@xmpp/{xmpp.xStanza.ts → StanzaXMPP.ts} +11 -11
  114. package/src/@parsers/@hvtec/{hvtec.extract.ts → HVExtract.ts} +10 -10
  115. package/src/@parsers/@pvtec/{pvtec.expires.ts → GetExpiry.ts} +1 -1
  116. package/src/@parsers/@pvtec/{pvtec.extract.ts → VTECExtract.ts} +17 -17
  117. package/src/@parsers/@text/{text.getDescriptionFromProduct.ts → GetDescriptionFromProduct.ts} +12 -5
  118. package/src/@parsers/@text/GetEmbededText.ts +56 -0
  119. package/src/@parsers/@text/{text.getPolygonFromProduct.ts → GetPolygonFromProduct.ts} +31 -10
  120. package/src/@parsers/@text/GetStringText.ts +58 -0
  121. package/src/@parsers/@text/{text.getTextFromProduct.ts → GetTextFromProduct.ts} +1 -1
  122. package/src/@parsers/@text/{text.getXML.ts → GetXML.ts} +1 -1
  123. package/src/@parsers/@ugc/GetCache.ts +24 -0
  124. package/src/@parsers/@ugc/{ugc.expiry.ts → GetExpiry.ts} +1 -1
  125. package/src/@parsers/@ugc/{ugc.locations.ts → GetLocations.ts} +35 -7
  126. package/src/@parsers/@ugc/{ugc.header.ts → GetUGCHeader.ts} +3 -3
  127. package/src/@parsers/@ugc/{ugc.coordinates.ts → GetZonePolygon.ts} +4 -3
  128. package/src/@parsers/@ugc/SetCache.ts +28 -0
  129. package/src/@parsers/@ugc/{ugc.extract.ts → UGCExtract.ts} +6 -6
  130. package/src/@types/Actions.ts +37 -0
  131. package/src/@types/{type.event.ts → Event.ts} +7 -5
  132. package/src/@types/{type.properties.ts → Properties.ts} +11 -8
  133. package/src/@types/{types.settings.ts → Settings.ts} +31 -7
  134. package/src/@types/{types.compiled.ts → StanzaCompiled.ts} +2 -1
  135. package/src/bootstrap.ts +33 -9
  136. package/src/index.ts +26 -24
  137. package/tsup.config.ts +1 -1
  138. package/README.md +0 -349
  139. package/dist/index.d.mts +0 -239
  140. package/dist/index.d.ts +0 -239
  141. package/src/@building/building.properties.ts +0 -89
  142. package/src/@dictionaries/dictionaries.betterEventNames.ts +0 -99
  143. package/src/@dictionaries/dictionaries.eventAwipAbreviations.ts +0 -364
  144. package/src/@dictionaries/dictionaries.eventTags.ts +0 -93
  145. package/src/@dictionaries/dictionaries.eventsMatchText.ts +0 -36
  146. package/src/@dictionaries/dictionaries.transcribedMessageReplacements.ts +0 -68
  147. package/src/@manager/manager.rmEvent.ts +0 -49
  148. package/src/@manager/manager.updateWebhooks.ts +0 -50
  149. package/src/@modules/@eas/eas.setEasTone.ts +0 -137
  150. package/src/@modules/@utilities/utilities.createHttp.ts +0 -85
  151. package/src/@modules/@utilities/utilities.createWebhook.ts +0 -115
  152. package/test.js +0 -91
  153. /package/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +0 -0
  154. /package/{LICENSE → .github/LICENSE} +0 -0
  155. /package/{SECURITY.md → .github/SECURITY.md} +0 -0
  156. /package/src/@parsers/@ugc/{ugc.zones.ts → GetZones.ts} +0 -0
  157. /package/src/@types/{types.attributes.ts → Attributes.ts} +0 -0
  158. /package/src/@types/{types.hvtec.ts → HVTEC.ts} +0 -0
  159. /package/src/@types/{types.hash.ts → Hash.ts} +0 -0
  160. /package/src/@types/{types.stanza.ts → Stanza.ts} +0 -0
  161. /package/src/@types/{types.ugc.ts → UGC.ts} +0 -0
  162. /package/src/@types/{types.pvtec.ts → VTEC.ts} +0 -0
package/package.json CHANGED
@@ -1,18 +1,17 @@
1
1
  {
2
2
  "name": "@atmosx/event-product-parser",
3
- "version": "3.0.4",
3
+ "version": "3.0.6",
4
4
  "description": "NOAA Weather Wire & NWS API Parser - Built for standalone and Project AtmosphericX Integration.",
5
5
  "main": "dist/cjs/index.cjs",
6
6
  "module": "dist/esm/index.mjs",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/AtmosphericX/event-product-parser",
10
- "branch": "beta"
10
+ "branch": "v3-beta-branch"
11
11
  },
12
12
  "scripts": {
13
13
  "start": "cd ./test && node ./index.js",
14
14
  "build": "tsup && cd ./test && node ./index.js",
15
- "heap-memory-errors": "node --heap-prof --heap-prof-name=heap-profile-%p.heapsnapshot ./test/index.js",
16
15
  "publish": "tsup && npm publish --access public",
17
16
  "publish-beta-branch": "tsup && npm publish --tag v3-beta-branch --access public"
18
17
  },
@@ -33,18 +32,13 @@
33
32
  },
34
33
  "homepage": "https://github.com/AtmosphericX/event-product-parser#readme",
35
34
  "dependencies": {
36
- "express-ws": "5.0.2",
37
- "express": "5.2.1",
38
35
  "@xmpp/client": "0.14.0",
39
36
  "better-sqlite3": "12.9.0",
40
37
  "croner": "10.0.1",
41
38
  "jszip": "3.10.1",
42
39
  "polygon-clipping": "0.15.7",
43
- "request": "2.88.2",
44
- "say": "0.16.0",
45
40
  "shapefile": "0.6.6",
46
- "typescript": "5.9.3",
47
- "xml2js": "0.6.2"
41
+ "typescript": "5.9.3"
48
42
  },
49
43
  "devDependencies": {
50
44
  "@types/node": "25.2.0",
@@ -17,15 +17,16 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeStanzaCompiled } from "../@types/types.compiled"
21
- import { TypeSettings } from "../@types/types.settings"
20
+ import { TypeStanzaCompiled } from "../@types/StanzaCompiled"
21
+ import { TypeSettings } from "../@types/Settings"
22
22
  import { bootstrap } from "../bootstrap"
23
- import { text } from "../@events/events.text"
24
- import { ugc } from "../@events/events.ugc"
25
- import { vtec } from "../@events/events.vtec"
26
- import { api } from "../@events/events.api"
23
+ import { ParseText } from "../@events/ParseText"
24
+ import { ParseUGC } from "../@events/ParseUGC"
25
+ import { ParseVTEC } from "../@events/ParseVTEC"
26
+ import { ParseAPI } from "../@events/ParseAPI"
27
+ import { ValidateEvents } from "./ValidateEvents"
27
28
 
28
- export const createEvent = async (stanza: TypeStanzaCompiled): Promise<void | string> => {
29
+ export const CreateEvent = async (stanza: TypeStanzaCompiled, ignorePushing?: boolean): Promise<void | string> => {
29
30
  const settings = bootstrap.settings as TypeSettings
30
31
  const StanzaSettings = settings.NOAAWeatherWireServiceSettings.StanzaSettings;
31
32
 
@@ -34,9 +35,22 @@ export const createEvent = async (stanza: TypeStanzaCompiled): Promise<void | st
34
35
  const isTextEvent = (!stanza.isVTEC && !stanza.isUGC)
35
36
  const isNWWS = (stanza.isNWWS)
36
37
 
37
- if (!isNWWS) return await api(stanza)
38
- if (!StanzaSettings.DisableVTEC && isVtecEvent) return await vtec(stanza)
39
- if (!StanzaSettings.DisableUGC && isUgcEvent) return await ugc(stanza);
40
- if (!StanzaSettings.DisableText && isTextEvent) return await text(stanza);
38
+ switch (true) {
39
+ case (!isNWWS):
40
+ await ParseAPI(stanza)
41
+ break;
42
+ case (isNWWS && !StanzaSettings.DisableVTEC && isVtecEvent):
43
+ await ParseVTEC(stanza)
44
+ break;
45
+ case (isNWWS && !StanzaSettings.DisableUGC && isUgcEvent):
46
+ await ParseUGC(stanza)
47
+ break;
48
+ case (isNWWS && !StanzaSettings.DisableText && isTextEvent):
49
+ await ParseText(stanza)
50
+ break;
51
+ }
52
+ if (!ignorePushing) {
53
+ await ValidateEvents(bootstrap.cache.processed)
54
+ }
41
55
  return 'nothing picked';
42
56
  }
@@ -17,13 +17,13 @@
17
17
 
18
18
  */
19
19
 
20
- export const getCleanedEvent = <T extends Record<string, any>>(event: T): T => {
20
+ export const GetCleanedEvent = <T extends Record<string, any>>(event: T): T => {
21
21
  for (const key of Object.keys(event)) {
22
22
  const value = event[key];
23
23
  if (value === null || value === undefined) {
24
24
  delete event[key];
25
25
  } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
26
- (event as Record<string, any>)[key] = getCleanedEvent(value as Record<string, any>) as any;
26
+ (event as Record<string, any>)[key] = GetCleanedEvent(value as Record<string, any>) as any;
27
27
  }
28
28
  }
29
29
  return event;
@@ -0,0 +1,86 @@
1
+ /*
2
+ _ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
12
+ Discord: https://atmosphericx-discord.scriptkitty.cafe
13
+ Ko-Fi: https://ko-fi.com/k3yomi
14
+ Documentation: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
15
+
16
+ Internal Package: @atmosx/event-product-parser
17
+
18
+ */
19
+
20
+ import { TypeEvent } from "../@types/Event"
21
+ import { bootstrap } from "../bootstrap"
22
+ import { EnumStates } from "../@enums/States"
23
+ import { EnumExpressions } from "../@enums/Expressions"
24
+ import { GetLatestIssuance } from "../@modules/@utilities/GetLatestIssuance";
25
+
26
+ interface GetEventAttachmentsResponse {
27
+ name: string
28
+ link: string
29
+ }
30
+
31
+ type BroadcastifyResponse = {
32
+ state: string
33
+ county: string
34
+ feed: string
35
+ type: string
36
+ link: string
37
+ }
38
+
39
+ export const GetEventAttachments = (event: TypeEvent): GetEventAttachmentsResponse[] | null => {
40
+ let attachments = [];
41
+ const settings = bootstrap.settings;
42
+ const issuanceTime = GetLatestIssuance();
43
+ const spcNumber = event?.properties?.discussion_parameters?.discussion_number;
44
+ const watchNumber = event?.properties?.watch_parameters?.watch_number;
45
+ const locations = event?.properties?.locations_array;
46
+ const events = [
47
+ { target: "Storm Prediction Center Day 1 Outlook", attachment: `https://www.spc.noaa.gov/products/outlook/day1otlk_${issuanceTime}.png` },
48
+ { target: "Storm Prediction Center Day 2 Outlook", attachment: `https://www.spc.noaa.gov/products/outlook/day2otlk.png` },
49
+ { target: "Storm Prediction Center Day 3 Outlook", attachment: `https://www.spc.noaa.gov/products/outlook/day3otlk.png` },
50
+ { target: "Mesoscale Discussion", attachment: `https://www.spc.noaa.gov/products/md/mcd${spcNumber}.png` },
51
+ { target: "Tornado Watch", attachment: `https://www.spc.noaa.gov/products/watch/ww${watchNumber}_radar_big.gif` },
52
+ { target: "PDS Tornado Watch", attachment: `https://www.spc.noaa.gov/products/watch/ww${watchNumber}_radar_big.gif` },
53
+ { target: "Severe Thunderstorm Watch", attachment: `https://www.spc.noaa.gov/products/watch/ww${watchNumber}_radar_big.gif` },
54
+ { target: "PDS Severe Thunderstorm Watch", attachment: `https://www.spc.noaa.gov/products/watch/ww${watchNumber}_radar_big.gif` }
55
+ ]
56
+
57
+ if (events.find((e) => e.target === event.properties.event)) {
58
+ attachments.push({ name: `Image: Graphic`, link: events.find((e) => e.target.toLowerCase() === event.properties.event.toLowerCase()).attachment });
59
+ }
60
+
61
+ if (settings.BroadcastifySettings.BroadcastifyAttachments) {
62
+ for (const location of locations) {
63
+ if (EnumExpressions.location.test(location)) {
64
+ const lines = [`Northern`, `Southern`, `Eastern`, `Western`, `Inland`, `Costal`, `County`]
65
+ const county = location?.split(',')[0]?.trim().replace(new RegExp(`^(${lines.join('|')}) `), '')
66
+ const state = EnumStates[location?.split(',')[1]?.trim()]
67
+ const feeds = bootstrap.database.prepare(`SELECT * FROM broadcastify WHERE state = ? AND county = ?`).all(state, county).sort((a, b) => {
68
+ const typeOrder = ['Other', 'Public Safety'];
69
+ const indexA = typeOrder.indexOf(a.type);
70
+ const indexB = typeOrder.indexOf(b.type);
71
+ return indexA - indexB;
72
+ })
73
+ const tags = bootstrap.settings.BroadcastifySettings.BroadcastifyTags;
74
+ const filtered = feeds.filter((feed: BroadcastifyResponse) => tags.includes(feed.type));
75
+ if (filtered.length > 0) {
76
+ filtered.map((filtered: BroadcastifyResponse) => {
77
+ if (!attachments.some((a) => a.link === filtered.link)) {
78
+ attachments.push({ name: `${filtered.type}: ${filtered.feed}`, link: filtered.link });
79
+ }
80
+ });
81
+ }
82
+ }
83
+ }
84
+ }
85
+ return attachments;
86
+ }
@@ -17,23 +17,19 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeEvent } from "../@types/type.event";
21
- import { TypeSettings } from "../@types/types.settings";
20
+ import { TypeEvent } from "../@types/Event";
21
+ import { TypeSettings } from "../@types/Settings";
22
+ import { EnumEnhanced } from "../@enums/Enhanced"
22
23
  import { bootstrap } from "../bootstrap"
23
- import { betterEventNames } from "../@dictionaries/dictionaries.betterEventNames"
24
24
 
25
- export const getEventEnhancedName = (event: TypeEvent): string => {
26
- const configurations = bootstrap.settings as TypeSettings
27
- let name = event?.properties?.event
28
-
29
- if (!configurations?.GlobalSettings?.BetterEventNames) {
30
- return name
31
- }
25
+ export const GetEventEnhancedName = (event: TypeEvent): string => {
26
+ let name = event?.properties?.event;
32
27
 
33
28
  const damage = event?.properties?.parameters?.damage_threat
34
- const tornado = event?.properties?.parameters.tornado_threat;
29
+ const tornado = event?.properties?.parameters?.tornado_threat;
30
+ const pdswatch = event?.properties?.watch_parameters?.pds_watch;
35
31
  const description = event?.properties?.description?.toLowerCase()
36
- for (const [eventKey, eventConfig] of Object.entries(betterEventNames)) {
32
+ for (const [eventKey, eventConfig] of Object.entries(EnumEnhanced)) {
37
33
  if (eventKey !== name) continue;
38
34
  for (const [paramKey, paramValue] of Object.entries(eventConfig)) {
39
35
  let matches = true;
@@ -46,6 +42,9 @@ export const getEventEnhancedName = (event: TypeEvent): string => {
46
42
  if (paramValue?.tornado) {
47
43
  if (paramValue.tornado !== tornado) matches = false;
48
44
  }
45
+ if (paramValue?.pdswatch) {
46
+ if (paramValue.pdswatch !== pdswatch) matches = false;
47
+ }
49
48
  if (matches) {
50
49
  name = paramKey;
51
50
  break;
@@ -17,18 +17,18 @@
17
17
 
18
18
  */
19
19
 
20
- import { getSettings } from "../@modules/@utilities/utilities.getSettings";
21
- import { TypeSettings } from "../@types/types.settings"
22
- import { TypeEvent } from "../@types/type.event"
23
- import { getZonePolygon } from "../@parsers/@ugc/ugc.coordinates";
20
+ import { TypeSettings } from "../@types/Settings"
21
+ import { TypeEvent } from "../@types/Event"
22
+ import { GetSettings } from "../@modules/@utilities/GetSettings";
23
+ import { getZonePolygon } from "../@parsers/@ugc/GetZonePolygon";
24
24
 
25
25
  interface GetGeometryResponse {
26
26
  type: `Polygon` | `MultiPolygon`
27
27
  coordinates: any[]
28
28
  }
29
29
 
30
- export const getEventGeometry = async (event: TypeEvent): Promise<GetGeometryResponse> => {
31
- const settings = getSettings() as TypeSettings
30
+ export const GetEventGeometry = (event: TypeEvent): GetGeometryResponse => {
31
+ const settings = GetSettings() as TypeSettings
32
32
  const generated = event?.properties?.geocode?.polygon ?? null;
33
33
  const ugc = event?.properties?.geocode?.ugc ?? null;
34
34
  let geo: GetGeometryResponse = {
@@ -36,7 +36,7 @@ export const getEventGeometry = async (event: TypeEvent): Promise<GetGeometryRes
36
36
  coordinates: generated != null ? JSON.parse(Buffer.from(generated, 'base64').toString('utf-8')) : null
37
37
  }
38
38
  if (settings.GlobalSettings.UseShapefileCoordinates && generated == null && ugc != null) {
39
- geo = await getZonePolygon({zones: ugc, isUnion: false})
39
+ geo = getZonePolygon({zones: ugc, isUnion: false})
40
40
  if (geo == null) {
41
41
  geo = {
42
42
  type: `Polygon`,
@@ -17,8 +17,8 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypePVTEC } from "../@types/types.pvtec";
21
- import { TypeEventProperties } from "../@types/type.properties";
20
+ import { TypePVTEC } from "../@types/VTEC";
21
+ import { TypeEventProperties } from "../@types/Properties";
22
22
 
23
23
  interface GetHeaderOptions {
24
24
  properties: TypeEventProperties
@@ -29,7 +29,7 @@ interface GetHeaderOptions {
29
29
  }
30
30
  }
31
31
 
32
- export const getEventHeader = (options: GetHeaderOptions): string => {
32
+ export const GetEventHeader = (options: GetHeaderOptions): string => {
33
33
  const properties = options.properties
34
34
  const vtec = options.vtec ?? null
35
35
  const ugc = properties.geocode.ugc != null ? properties.geocode.ugc.join(`-`) : `0`;
@@ -17,11 +17,10 @@
17
17
 
18
18
  */
19
19
 
20
-
21
- import { TypeEvent } from "../@types/type.event"
22
- import { getEventGeometry } from "./building.geometry"
20
+ import { TypeEvent } from "../@types/Event"
23
21
  import { bootstrap } from "../bootstrap"
24
- import { getShapeNearestPoint } from "../@modules/@utilities/utilities.getShapeNearestPoint"
22
+ import { GetEventGeometry } from "./GetEventGeometry"
23
+ import { GetShapeNearestPoint } from "../@modules/@utilities/GetShapeNearestPoint"
25
24
 
26
25
  interface GetEventNodesResponse {
27
26
  nodes: {
@@ -36,16 +35,19 @@ interface GetEventNodesResponse {
36
35
  updated: number
37
36
  }
38
37
 
39
- export const getEventNodes = async (event: TypeEvent): Promise<GetEventNodesResponse> => {
40
- const metadata = { nodes: [], proximity: false, filtered: false }
41
- const geometry = await getEventGeometry(event);
38
+ export const GetEventNodes = async (event: TypeEvent): Promise<GetEventNodesResponse> => {
39
+ const nodes = bootstrap.cache.nodes.features;
40
+ if (!nodes || nodes.length === 0) {
41
+ return { nodes: [], filtered: false, updated: Date.now() };
42
+ }
43
+ const metadata = { nodes: [], proximity: false, filtered: false };
44
+ const geometry = await GetEventGeometry(event);
42
45
  if (!geometry || !geometry.coordinates) {
43
46
  return { nodes: [], filtered: false, updated: Date.now() }
44
47
  }
45
- const nodes = bootstrap.cache.nodes.features;
46
48
  for (const node of nodes) {
47
49
  const [longitude, latitude] = node.geometry.coordinates;
48
- const getPoint = getShapeNearestPoint(geometry.coordinates, [longitude, latitude])
50
+ const getPoint = GetShapeNearestPoint(geometry.coordinates, [longitude, latitude])
49
51
  const miles = getPoint.distance ?? null;
50
52
  const kilometers = Number((miles * 1.609344).toFixed(3));
51
53
 
@@ -55,10 +57,10 @@ export const getEventNodes = async (event: TypeEvent): Promise<GetEventNodesResp
55
57
  nearest: getPoint.point,
56
58
  miles,
57
59
  kilometers,
58
- proximity: getPoint.proximity
60
+ proximity: false
59
61
  }
60
62
  metadata.nodes.push(info)
61
- if (bootstrap.settings.GlobalSettings.EventFiltering.NodeLocationFiltering && miles < bootstrap.settings.GlobalSettings.NodeMinDistance) {
63
+ if (miles != null && (bootstrap.settings.GlobalSettings.EventFiltering.NodeLocationFiltering) && (miles < bootstrap.settings.GlobalSettings.NodeMaxDistance)) {
62
64
  metadata.proximity = true;
63
65
  info.proximity = true;
64
66
  }
@@ -17,9 +17,9 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeAttributes } from "../@types/types.attributes";
21
- import { TypePVTEC } from "../@types/types.pvtec";
22
- import { officeICAOs } from "../@dictionaries/dictionaries.officeICAOs";
20
+ import { TypeAttributes } from "../@types/Attributes";
21
+ import { TypePVTEC } from "../@types/VTEC";
22
+ import { EnumICAO } from "../@enums/ICAO";
23
23
 
24
24
  interface GetOfficeOptions {
25
25
  attributes: TypeAttributes
@@ -32,12 +32,12 @@ interface GetOfficeResponse {
32
32
  name: string | null
33
33
  }
34
34
 
35
- export const getEventOffice = (options: GetOfficeOptions): GetOfficeResponse => {
35
+ export const GetEventOffice = (options: GetOfficeOptions): GetOfficeResponse => {
36
36
  const office = options.pVtec != null
37
- ? options.pVtec?.tracking?.split(`-`)[0] : (options.attributes?.cccc ||
37
+ ? options.pVtec?.tracking?.split(`.`)[0] : (options.attributes?.cccc ??
38
38
  (options.organization != null ?
39
39
  (Array.isArray(options.organization) ? options.organization?.[0] : options.organization)
40
40
  : null));
41
- const name = officeICAOs?.[office] ?? null;
41
+ const name = EnumICAO?.[office] ?? null;
42
42
  return { office, name };
43
43
  }
@@ -0,0 +1,96 @@
1
+ /*
2
+ _ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
12
+ Discord: https://atmosphericx-discord.scriptkitty.cafe
13
+ Ko-Fi: https://ko-fi.com/k3yomi
14
+ Documentation: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
15
+
16
+ Internal Package: @atmosx/event-product-parser
17
+
18
+ */
19
+
20
+ import { TypeEventProperties } from "../@types/Properties";
21
+ import { TypeAttributes } from "../@types/Attributes";
22
+ import { TypeUGC } from "../@types/UGC";
23
+ import { TypePVTEC } from "../@types/VTEC";
24
+ import { TypeHVTEC } from "../@types/HVTEC";
25
+ import { EnumExpressions } from "../@enums/Expressions";
26
+ import { GetDescriptionFromProduct } from "../@parsers/@text/GetDescriptionFromProduct";
27
+ import { getPolygonFromProduct } from "../@parsers/@text/GetPolygonFromProduct";
28
+ import { GetTextFromProduct } from "../@parsers/@text/GetTextFromProduct";
29
+ import { GetEventOffice } from "./GetEventOffice";
30
+ import { GetEventTags } from "./GetEventTags";
31
+
32
+ interface GetEventPropertiesOptions {
33
+ message: string
34
+ attributes: TypeAttributes
35
+ ugc?: TypeUGC
36
+ pVtec?: TypePVTEC
37
+ hVtec?: TypeHVTEC
38
+ }
39
+
40
+ export const GetEventProperties = (options: GetEventPropertiesOptions): TypeEventProperties => {
41
+ const organization = options.message.match(EnumExpressions.wmo)?.[0] ?? null
42
+ const polygons = getPolygonFromProduct(options.message)
43
+ const properties = {
44
+ locations: options?.ugc?.locations?.join(`; `) ?? null,
45
+ locations_array: options?.ugc?.locations ?? [],
46
+ description: GetDescriptionFromProduct({ message: options.message, handle: options?.pVtec?.vtec ?? null }),
47
+ attributes: options.attributes,
48
+ geocode: {
49
+ office: GetEventOffice({ attributes: options.attributes, organization: organization, pVtec: options.pVtec }),
50
+ organization: organization,
51
+ ugc: options?.ugc?.zones ?? [],
52
+ polygon: polygons.length > 0 ? Buffer.from(JSON.stringify([polygons])).toString('base64') : null,
53
+ polygon_generated: polygons.length > 0 ? true : false
54
+ },
55
+ parameters: {
56
+ tags: GetEventTags(options.message),
57
+ instructions: GetTextFromProduct({ message: options.message, find: [`For your protection`, `do not`, `use extreme caution`], append: `...`, removal: [`.`]}) ?? null,
58
+ source: GetTextFromProduct({ message: options.message, find: [`SOURCE...`], removal: [`.`]}) ?? null,
59
+ hazards: GetTextFromProduct({ message: options.message, find: [`HAZARD...`], removal: [`.`]}) ?? null,
60
+ impacts: GetTextFromProduct({ message: options.message, find: [`IMPACT...`], removal: [`.`]}) ?? null,
61
+ estimated_hail_size: GetTextFromProduct({ message: options.message, find: [`MAX HAIL SIZE...`, `HAIL...`], removal: ['in']}) ?? null,
62
+ estimated_wind_gusts: GetTextFromProduct({ message: options.message, find: [`MAX WIND GUST...`, `WIND...`]}) ?? null,
63
+ damage_threat: GetTextFromProduct({ message: options.message, find: [`DAMAGE THREAT...`], removal: []}) ?? null,
64
+ tornado_threat: GetTextFromProduct({ message: options.message, find: [`TORNADO...`, `WATERSPOUT...`] }) ?? null,
65
+ flood_threat: GetTextFromProduct({ message: options.message, find: [`FLASH FLOOD...`]}) ?? null,
66
+ wind_threat: GetTextFromProduct({ message: options.message, find: [`WIND THREAT...`]}) ?? null,
67
+ hail_threat: GetTextFromProduct({ message: options.message, find: [`HAIL THREAT...`], removal: []}) ?? null,
68
+ },
69
+ discussion_parameters: {
70
+ discussion_number: GetTextFromProduct({ message: options.message, find: [`Mesoscale Discussion `], removal: [`Mesoscale Discussion`, `Number`, `...`] })?.toString()?.padStart(4, "0") ?? null,
71
+ discussion_concerning: GetTextFromProduct({ message: options.message, find: [`Concerning...`] }) ?? null,
72
+ discussion_max_tornado: GetTextFromProduct({ message: options.message, find: [`MOST PROBABLE PEAK TORNADO INTENSITY...`] }) ?? null,
73
+ discussion_max_hail: GetTextFromProduct({ message: options.message, find: [`MOST PROBABLE PEAK HAIL SIZE...`] }) ?? null,
74
+ discussion_max_wind: GetTextFromProduct({ message: options.message, find: [`MOST PROBABLE PEAK WIND GUST...`] }) ?? null,
75
+ discussion_watch_issuance: GetTextFromProduct({ message: options.message, find: [`Probability of Watch Issuance...`], removal: [`percent`]}) ?? null,
76
+ },
77
+ watch_parameters: {
78
+ watch_number: (options?.pVtec?.is_watch) && (GetTextFromProduct({ message: options.message, find: [`ITIES FOR`, `UPDATE FOR`, `Watch Number `], removal: [`%`, `<`, `:`] })?.replace(/(WT|WS|)/g, '')?.trim()?.toString()?.padStart(4, "0") ?? options?.pVtec?.tracking?.slice(-4)?.toString()?.padStart(4, "0") ?? null),
79
+ watch_type: options.message.includes(`TORNADO WATCH`) ? `Tornado` : options?.message.includes(`SEVERE`) ? `Severe` : null,
80
+ additional_tornadoes_probability: GetTextFromProduct({ message: options.message, find: [`PROB OF 2 OR MORE TORNADOES`], removal: [`%`, `<`, `:`] }) ?? null,
81
+ strong_tornadoes_probability: GetTextFromProduct({ message: options.message, find: [`PROB OF 1 OR MORE STRONG /EF2-EF5/ TORNADOES`], removal: [`%`, `<`, `:`] }) ?? null,
82
+ severe_wind_probability: GetTextFromProduct({ message: options.message, find: [`PROB OF 10 OR MORE SEVERE WIND EVENTS`], removal: [`%`, `<`, `:`] }) ?? null,
83
+ severe_hail_probability: GetTextFromProduct({ message: options.message, find: [`PROB OF 10 OR MORE SEVERE HAIL EVENTS`], removal: [`%`, `<`, `:`] }) ?? null,
84
+ hail_2in_probability: GetTextFromProduct({ message: options.message, find: [`PROB OF 1 OR MORE HAIL EVENTS >= 2 INCHES`], removal: [`%`, `<`, `:`] }) ?? null,
85
+ combined_hail_wind_probability: GetTextFromProduct({ message: options.message, find: [`PROB OF 6 OR MORE COMBINED SEVERE HAIL/WIND EVENTS`], removal: [`%`, `<`, `:`] }) ?? null,
86
+ max_hail_in: GetTextFromProduct({ message: options.message, find: [`MAX HAIL /INCHES/`], removal: [`%`, `<`, `:`] }) ?? null,
87
+ max_wind_surface: GetTextFromProduct({ message: options.message, find: [`MAX WIND GUSTS SURFACE /KNOTS/`], removal: [`%`, `<`, `:`] }) ?? null,
88
+ max_tops_x100feet: GetTextFromProduct({ message: options.message, find: [`MAX TOPS /X 100 FEET/`], removal: [`%`, `<`, `:`] }) ?? null,
89
+ pds_watch: (GetTextFromProduct({ message: options.message, find: [`PARTICULARLY DANGEROUS SITUATION`], removal: [`%`, `<`, `:`] }) === `YES` ? true : null)
90
+ }
91
+ }
92
+ if (isNaN(Number(properties.watch_parameters.watch_number))) {
93
+ properties.watch_parameters.watch_number = null
94
+ }
95
+ return properties;
96
+ }
@@ -17,24 +17,23 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeEvent } from "../@types/type.event";
21
- import { statusCorrelationText } from "../@dictionaries/dictionaries.statusCorrelationText";
22
- import { eventCancelMessages } from "../@dictionaries/dictionaries.eventCancelMessages";
23
- import { testSignatures} from "../@dictionaries/dictionaries.testSignatures"
24
- import { eventProducts } from "../@dictionaries/dictionaries.eventProducts";
25
- import { hailStrings } from "../@dictionaries/dictionaries.hailStrings"
26
-
27
-
28
- export const getEventSignature = (event: TypeEvent): TypeEvent => {
20
+ import { TypeEvent } from "../@types/Event";
21
+ import { EnumCorrelations } from "../@enums/Correlations";
22
+ import { EnumCancellation } from "../@enums/Cancellation";
23
+ import { EnumTesting} from "../@enums/Testing"
24
+ import { EnumProducts } from "../@enums/Products";
25
+ import { EnumHail } from "../@enums/Hail"
26
+
27
+ export const GetEventSignature = (event: TypeEvent): TypeEvent => {
29
28
  const properties = event?.properties;
30
29
  const vtec = event?.properties?.metadata?.vtec
31
- const status = statusCorrelationText
30
+ const status = EnumCorrelations
32
31
  .find((c: { type: string }) => c.type === properties?.status);
33
- const csig = eventCancelMessages.find(sig => properties.description.toLowerCase().includes(sig.toLowerCase()));
32
+ const csig = EnumCancellation.find(sig => properties.description.toLowerCase().includes(sig.toLowerCase()));
34
33
  properties.status_metadata = { ...properties.status_metadata, is_issued: true, is_test: false};
35
34
 
36
- if (properties.parameters.estimated_hail_size) {
37
- properties.parameters.estimated_hail_size += ` (${hailStrings[properties.parameters.estimated_hail_size] ?? '--'})`
35
+ if (properties?.parameters?.estimated_hail_size) {
36
+ properties.parameters.estimated_hail_size += ` (${EnumHail[properties?.parameters?.estimated_hail_size] ?? '--'})`
38
37
  }
39
38
 
40
39
  if (status) {
@@ -46,8 +45,8 @@ export const getEventSignature = (event: TypeEvent): TypeEvent => {
46
45
  }
47
46
 
48
47
  const getProduct = vtec?.vtec?.split(`.`)[0]?.replace(`/`, ``)
49
- const isTestProduct = eventProducts[getProduct] == `Test Product`
50
- if (isTestProduct || testSignatures.some(sig => properties.description?.toLowerCase().includes(sig.toLowerCase()) || properties?.parameters?.instructions?.toLowerCase().includes(sig.toLowerCase()))) {
48
+ const isTestProduct = EnumProducts[getProduct] == `Test Product`
49
+ if (isTestProduct || EnumTesting.some(sig => properties.description?.toLowerCase().includes(sig.toLowerCase()) ?? properties?.parameters?.instructions?.toLowerCase().includes(sig.toLowerCase()))) {
51
50
  properties.status_metadata = { ...properties.status_metadata, is_test: true }
52
51
  }
53
52
 
@@ -17,8 +17,13 @@
17
17
 
18
18
  */
19
19
 
20
- import { eventTags } from "../@dictionaries/dictionaries.eventTags";
20
+ import { EnumTags } from "../@enums/Tags";
21
21
 
22
- export const getEventTags = (message: string): string[] => {
23
- return Object.entries(eventTags).filter(([key]) => message?.toLowerCase().includes(key.toLowerCase())).map(([, value]) => value)
24
- }
22
+ export const GetEventTags = (message: string): string[] => {
23
+ if (!message) return [];
24
+ return [...new Set(Object.values(EnumTags)
25
+ .flat()
26
+ .filter(({ match }) => match.test(message))
27
+ .map(({ tag }) => tag)
28
+ )];
29
+ };
@@ -17,10 +17,10 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeAttributes } from "../@types/types.attributes";
21
- import { TypeEventProperties } from "../@types/type.properties";
22
- import { TypeStanzaCompiled } from "../@types/types.compiled";
23
- import { TypePVTEC } from "../@types/types.pvtec";
20
+ import { TypeAttributes } from "../@types/Attributes";
21
+ import { TypeEventProperties } from "../@types/Properties";
22
+ import { TypeStanzaCompiled } from "../@types/StanzaCompiled";
23
+ import { TypePVTEC } from "../@types/VTEC";
24
24
 
25
25
  interface GetTrackingOptions {
26
26
  type: `RAW` | `VTEC` | `API`
@@ -34,17 +34,17 @@ interface GetTrackingOptions {
34
34
  vtec?: TypePVTEC
35
35
  }
36
36
 
37
- export const getEventTracking = (options: GetTrackingOptions): string => {
38
- const proprties = options.properties
37
+ export const GetEventTracking = (options: GetTrackingOptions): string => {
38
+ const properties = options.properties
39
39
  const attributes = options.attributes
40
40
  const stanza = options.stanza
41
41
  const vtec = options.vtec
42
42
  if (options.type === `RAW`) {
43
- const getWatchNumber = proprties.watch_parameters.watch_number ?? null
43
+ const getWatchNumber = properties.watch_parameters.watch_number ?? null
44
44
  if (getWatchNumber) {
45
- return `${proprties.geocode.office.office}-${stanza.getType.prefix}-A-${getWatchNumber}`
45
+ return `${properties.geocode.office.office}.${stanza.getType.prefix}.A.${getWatchNumber}`
46
46
  }
47
- return `${proprties.geocode.office.office}-${attributes.ttaaii}-${attributes.id.slice(-4).replace(`.`, ``) ?? '0'}`
47
+ return `${properties.geocode.office.office}.${attributes.ttaaii}.${attributes.id.slice(-4).replace(`.`, ``) ?? '0'}`
48
48
  }
49
49
  if (options.type === `VTEC`) {
50
50
  return vtec.tracking;
@@ -52,17 +52,17 @@ export const getEventTracking = (options: GetTrackingOptions): string => {
52
52
  if (options.type === `API`) {
53
53
  if (options.vtec) {
54
54
  const vtecValue = Array.isArray(options.vtec)
55
- ? options.vtec[0] : options.vtec;
55
+ ? options.vtec[0].vtec : options.vtec?.vtec;
56
56
  const splitPVTEC = vtecValue.split('.');
57
- return `${splitPVTEC[2]}-${splitPVTEC[3]}-${splitPVTEC[4]}-${splitPVTEC[5]}`;
57
+ return `${splitPVTEC[2]}.${splitPVTEC[3]}.${splitPVTEC[4]}.${splitPVTEC[5]}`;
58
58
  }
59
59
  const wmoMatch = options.organization?.wmoidentifier?.match(/([A-Z]{4}\d{2})\s+([A-Z]{4})/);
60
- const station = wmoMatch?.[2] ?? 'N/A';
60
+ const station = wmoMatch?.[2] ?? '---';
61
61
  if (options.organization.featureId) {
62
62
  const idMatch = options.organization.featureId.match(/([a-f0-9]+)\.(\d+)\.(\d+)$/);
63
- return `${station}-${idMatch?.[1] ?? 'N/A'}`;
63
+ return `${station}.${idMatch?.[0]?.replace(/\./g, '') ?? '---'}`;
64
64
  }
65
- const id = wmoMatch?.[1] ?? 'N/A';
66
- return `${station}-${id}`;
65
+ const id = wmoMatch?.[1] ?? '---';
66
+ return `${station}.${id?.replace(/\./g, '') ?? '---'}`;
67
67
  }
68
68
  }