local-risk-alert-feed 0.1.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 (283) hide show
  1. package/dist/cjs/adapters/index.js +8 -0
  2. package/dist/cjs/adapters/index.js.map +1 -0
  3. package/dist/cjs/adapters/lambda.js +143 -0
  4. package/dist/cjs/adapters/lambda.js.map +1 -0
  5. package/dist/cjs/adapters/vercel.js +119 -0
  6. package/dist/cjs/adapters/vercel.js.map +1 -0
  7. package/dist/cjs/core/alert-aggregator.js +207 -0
  8. package/dist/cjs/core/alert-aggregator.js.map +1 -0
  9. package/dist/cjs/core/alert-feed.js +236 -0
  10. package/dist/cjs/core/alert-feed.js.map +1 -0
  11. package/dist/cjs/core/index.js +22 -0
  12. package/dist/cjs/core/index.js.map +1 -0
  13. package/dist/cjs/core/plugin-registry.js +193 -0
  14. package/dist/cjs/core/plugin-registry.js.map +1 -0
  15. package/dist/cjs/core/plugin-resolver.js +121 -0
  16. package/dist/cjs/core/plugin-resolver.js.map +1 -0
  17. package/dist/cjs/core/time-range.js +67 -0
  18. package/dist/cjs/core/time-range.js.map +1 -0
  19. package/dist/cjs/errors/fetch-error.js +71 -0
  20. package/dist/cjs/errors/fetch-error.js.map +1 -0
  21. package/dist/cjs/errors/index.js +15 -0
  22. package/dist/cjs/errors/index.js.map +1 -0
  23. package/dist/cjs/errors/plugin-error.js +80 -0
  24. package/dist/cjs/errors/plugin-error.js.map +1 -0
  25. package/dist/cjs/errors/validation-error.js +49 -0
  26. package/dist/cjs/errors/validation-error.js.map +1 -0
  27. package/dist/cjs/geo/distance.js +94 -0
  28. package/dist/cjs/geo/distance.js.map +1 -0
  29. package/dist/cjs/geo/index.js +18 -0
  30. package/dist/cjs/geo/index.js.map +1 -0
  31. package/dist/cjs/geo/point-in-radius.js +86 -0
  32. package/dist/cjs/geo/point-in-radius.js.map +1 -0
  33. package/dist/cjs/index.js +90 -0
  34. package/dist/cjs/index.js.map +1 -0
  35. package/dist/cjs/plugins/air-quality/airnow.plugin.js +343 -0
  36. package/dist/cjs/plugins/air-quality/airnow.plugin.js.map +1 -0
  37. package/dist/cjs/plugins/air-quality/index.js +6 -0
  38. package/dist/cjs/plugins/air-quality/index.js.map +1 -0
  39. package/dist/cjs/plugins/base-plugin.js +213 -0
  40. package/dist/cjs/plugins/base-plugin.js.map +1 -0
  41. package/dist/cjs/plugins/events/index.js +6 -0
  42. package/dist/cjs/plugins/events/index.js.map +1 -0
  43. package/dist/cjs/plugins/events/phoenix-events.plugin.js +382 -0
  44. package/dist/cjs/plugins/events/phoenix-events.plugin.js.map +1 -0
  45. package/dist/cjs/plugins/fire-emt/index.js +6 -0
  46. package/dist/cjs/plugins/fire-emt/index.js.map +1 -0
  47. package/dist/cjs/plugins/fire-emt/phoenix-fire.plugin.js +262 -0
  48. package/dist/cjs/plugins/fire-emt/phoenix-fire.plugin.js.map +1 -0
  49. package/dist/cjs/plugins/index.js +28 -0
  50. package/dist/cjs/plugins/index.js.map +1 -0
  51. package/dist/cjs/plugins/police-blotter/index.js +6 -0
  52. package/dist/cjs/plugins/police-blotter/index.js.map +1 -0
  53. package/dist/cjs/plugins/police-blotter/phoenix-police.plugin.js +198 -0
  54. package/dist/cjs/plugins/police-blotter/phoenix-police.plugin.js.map +1 -0
  55. package/dist/cjs/plugins/pulsepoint/index.js +6 -0
  56. package/dist/cjs/plugins/pulsepoint/index.js.map +1 -0
  57. package/dist/cjs/plugins/pulsepoint/pulsepoint.plugin.js +275 -0
  58. package/dist/cjs/plugins/pulsepoint/pulsepoint.plugin.js.map +1 -0
  59. package/dist/cjs/plugins/traffic/arizona-traffic.plugin.js +391 -0
  60. package/dist/cjs/plugins/traffic/arizona-traffic.plugin.js.map +1 -0
  61. package/dist/cjs/plugins/traffic/index.js +6 -0
  62. package/dist/cjs/plugins/traffic/index.js.map +1 -0
  63. package/dist/cjs/plugins/weather/index.js +6 -0
  64. package/dist/cjs/plugins/weather/index.js.map +1 -0
  65. package/dist/cjs/plugins/weather/nws-weather.plugin.js +180 -0
  66. package/dist/cjs/plugins/weather/nws-weather.plugin.js.map +1 -0
  67. package/dist/cjs/schemas/alert.schema.js +93 -0
  68. package/dist/cjs/schemas/alert.schema.js.map +1 -0
  69. package/dist/cjs/schemas/index.js +24 -0
  70. package/dist/cjs/schemas/index.js.map +1 -0
  71. package/dist/cjs/schemas/query.schema.js +76 -0
  72. package/dist/cjs/schemas/query.schema.js.map +1 -0
  73. package/dist/cjs/types/alert.js +35 -0
  74. package/dist/cjs/types/alert.js.map +1 -0
  75. package/dist/cjs/types/config.js +13 -0
  76. package/dist/cjs/types/config.js.map +1 -0
  77. package/dist/cjs/types/geo.js +3 -0
  78. package/dist/cjs/types/geo.js.map +1 -0
  79. package/dist/cjs/types/index.js +16 -0
  80. package/dist/cjs/types/index.js.map +1 -0
  81. package/dist/cjs/types/plugin.js +3 -0
  82. package/dist/cjs/types/plugin.js.map +1 -0
  83. package/dist/cjs/types/query.js +28 -0
  84. package/dist/cjs/types/query.js.map +1 -0
  85. package/dist/cjs/utils/cache.js +188 -0
  86. package/dist/cjs/utils/cache.js.map +1 -0
  87. package/dist/cjs/utils/csv.js +189 -0
  88. package/dist/cjs/utils/csv.js.map +1 -0
  89. package/dist/cjs/utils/date.js +153 -0
  90. package/dist/cjs/utils/date.js.map +1 -0
  91. package/dist/cjs/utils/index.js +28 -0
  92. package/dist/cjs/utils/index.js.map +1 -0
  93. package/dist/cjs/utils/retry.js +109 -0
  94. package/dist/cjs/utils/retry.js.map +1 -0
  95. package/dist/esm/adapters/index.js +3 -0
  96. package/dist/esm/adapters/index.js.map +1 -0
  97. package/dist/esm/adapters/lambda.js +140 -0
  98. package/dist/esm/adapters/lambda.js.map +1 -0
  99. package/dist/esm/adapters/vercel.js +116 -0
  100. package/dist/esm/adapters/vercel.js.map +1 -0
  101. package/dist/esm/core/alert-aggregator.js +203 -0
  102. package/dist/esm/core/alert-aggregator.js.map +1 -0
  103. package/dist/esm/core/alert-feed.js +232 -0
  104. package/dist/esm/core/alert-feed.js.map +1 -0
  105. package/dist/esm/core/index.js +6 -0
  106. package/dist/esm/core/index.js.map +1 -0
  107. package/dist/esm/core/plugin-registry.js +189 -0
  108. package/dist/esm/core/plugin-registry.js.map +1 -0
  109. package/dist/esm/core/plugin-resolver.js +117 -0
  110. package/dist/esm/core/plugin-resolver.js.map +1 -0
  111. package/dist/esm/core/time-range.js +57 -0
  112. package/dist/esm/core/time-range.js.map +1 -0
  113. package/dist/esm/errors/fetch-error.js +67 -0
  114. package/dist/esm/errors/fetch-error.js.map +1 -0
  115. package/dist/esm/errors/index.js +4 -0
  116. package/dist/esm/errors/index.js.map +1 -0
  117. package/dist/esm/errors/plugin-error.js +71 -0
  118. package/dist/esm/errors/plugin-error.js.map +1 -0
  119. package/dist/esm/errors/validation-error.js +45 -0
  120. package/dist/esm/errors/validation-error.js.map +1 -0
  121. package/dist/esm/geo/distance.js +85 -0
  122. package/dist/esm/geo/distance.js.map +1 -0
  123. package/dist/esm/geo/index.js +3 -0
  124. package/dist/esm/geo/index.js.map +1 -0
  125. package/dist/esm/geo/point-in-radius.js +79 -0
  126. package/dist/esm/geo/point-in-radius.js.map +1 -0
  127. package/dist/esm/index.js +30 -0
  128. package/dist/esm/index.js.map +1 -0
  129. package/dist/esm/plugins/air-quality/airnow.plugin.js +339 -0
  130. package/dist/esm/plugins/air-quality/airnow.plugin.js.map +1 -0
  131. package/dist/esm/plugins/air-quality/index.js +2 -0
  132. package/dist/esm/plugins/air-quality/index.js.map +1 -0
  133. package/dist/esm/plugins/base-plugin.js +209 -0
  134. package/dist/esm/plugins/base-plugin.js.map +1 -0
  135. package/dist/esm/plugins/events/index.js +2 -0
  136. package/dist/esm/plugins/events/index.js.map +1 -0
  137. package/dist/esm/plugins/events/phoenix-events.plugin.js +378 -0
  138. package/dist/esm/plugins/events/phoenix-events.plugin.js.map +1 -0
  139. package/dist/esm/plugins/fire-emt/index.js +2 -0
  140. package/dist/esm/plugins/fire-emt/index.js.map +1 -0
  141. package/dist/esm/plugins/fire-emt/phoenix-fire.plugin.js +258 -0
  142. package/dist/esm/plugins/fire-emt/phoenix-fire.plugin.js.map +1 -0
  143. package/dist/esm/plugins/index.js +17 -0
  144. package/dist/esm/plugins/index.js.map +1 -0
  145. package/dist/esm/plugins/police-blotter/index.js +2 -0
  146. package/dist/esm/plugins/police-blotter/index.js.map +1 -0
  147. package/dist/esm/plugins/police-blotter/phoenix-police.plugin.js +194 -0
  148. package/dist/esm/plugins/police-blotter/phoenix-police.plugin.js.map +1 -0
  149. package/dist/esm/plugins/pulsepoint/index.js +2 -0
  150. package/dist/esm/plugins/pulsepoint/index.js.map +1 -0
  151. package/dist/esm/plugins/pulsepoint/pulsepoint.plugin.js +271 -0
  152. package/dist/esm/plugins/pulsepoint/pulsepoint.plugin.js.map +1 -0
  153. package/dist/esm/plugins/traffic/arizona-traffic.plugin.js +387 -0
  154. package/dist/esm/plugins/traffic/arizona-traffic.plugin.js.map +1 -0
  155. package/dist/esm/plugins/traffic/index.js +2 -0
  156. package/dist/esm/plugins/traffic/index.js.map +1 -0
  157. package/dist/esm/plugins/weather/index.js +2 -0
  158. package/dist/esm/plugins/weather/index.js.map +1 -0
  159. package/dist/esm/plugins/weather/nws-weather.plugin.js +176 -0
  160. package/dist/esm/plugins/weather/nws-weather.plugin.js.map +1 -0
  161. package/dist/esm/schemas/alert.schema.js +90 -0
  162. package/dist/esm/schemas/alert.schema.js.map +1 -0
  163. package/dist/esm/schemas/index.js +5 -0
  164. package/dist/esm/schemas/index.js.map +1 -0
  165. package/dist/esm/schemas/query.schema.js +72 -0
  166. package/dist/esm/schemas/query.schema.js.map +1 -0
  167. package/dist/esm/types/alert.js +32 -0
  168. package/dist/esm/types/alert.js.map +1 -0
  169. package/dist/esm/types/config.js +10 -0
  170. package/dist/esm/types/config.js.map +1 -0
  171. package/dist/esm/types/geo.js +2 -0
  172. package/dist/esm/types/geo.js.map +1 -0
  173. package/dist/esm/types/index.js +4 -0
  174. package/dist/esm/types/index.js.map +1 -0
  175. package/dist/esm/types/plugin.js +2 -0
  176. package/dist/esm/types/plugin.js.map +1 -0
  177. package/dist/esm/types/query.js +25 -0
  178. package/dist/esm/types/query.js.map +1 -0
  179. package/dist/esm/utils/cache.js +181 -0
  180. package/dist/esm/utils/cache.js.map +1 -0
  181. package/dist/esm/utils/csv.js +185 -0
  182. package/dist/esm/utils/csv.js.map +1 -0
  183. package/dist/esm/utils/date.js +142 -0
  184. package/dist/esm/utils/date.js.map +1 -0
  185. package/dist/esm/utils/index.js +5 -0
  186. package/dist/esm/utils/index.js.map +1 -0
  187. package/dist/esm/utils/retry.js +102 -0
  188. package/dist/esm/utils/retry.js.map +1 -0
  189. package/dist/types/adapters/index.d.ts +5 -0
  190. package/dist/types/adapters/index.d.ts.map +1 -0
  191. package/dist/types/adapters/lambda.d.ts +37 -0
  192. package/dist/types/adapters/lambda.d.ts.map +1 -0
  193. package/dist/types/adapters/vercel.d.ts +54 -0
  194. package/dist/types/adapters/vercel.d.ts.map +1 -0
  195. package/dist/types/core/alert-aggregator.d.ts +81 -0
  196. package/dist/types/core/alert-aggregator.d.ts.map +1 -0
  197. package/dist/types/core/alert-feed.d.ts +80 -0
  198. package/dist/types/core/alert-feed.d.ts.map +1 -0
  199. package/dist/types/core/index.d.ts +8 -0
  200. package/dist/types/core/index.d.ts.map +1 -0
  201. package/dist/types/core/plugin-registry.d.ts +91 -0
  202. package/dist/types/core/plugin-registry.d.ts.map +1 -0
  203. package/dist/types/core/plugin-resolver.d.ts +78 -0
  204. package/dist/types/core/plugin-resolver.d.ts.map +1 -0
  205. package/dist/types/core/time-range.d.ts +40 -0
  206. package/dist/types/core/time-range.d.ts.map +1 -0
  207. package/dist/types/errors/fetch-error.d.ts +46 -0
  208. package/dist/types/errors/fetch-error.d.ts.map +1 -0
  209. package/dist/types/errors/index.d.ts +5 -0
  210. package/dist/types/errors/index.d.ts.map +1 -0
  211. package/dist/types/errors/plugin-error.d.ts +42 -0
  212. package/dist/types/errors/plugin-error.d.ts.map +1 -0
  213. package/dist/types/errors/validation-error.d.ts +34 -0
  214. package/dist/types/errors/validation-error.d.ts.map +1 -0
  215. package/dist/types/geo/distance.d.ts +50 -0
  216. package/dist/types/geo/distance.d.ts.map +1 -0
  217. package/dist/types/geo/index.d.ts +3 -0
  218. package/dist/types/geo/index.d.ts.map +1 -0
  219. package/dist/types/geo/point-in-radius.d.ts +44 -0
  220. package/dist/types/geo/point-in-radius.d.ts.map +1 -0
  221. package/dist/types/index.d.ts +32 -0
  222. package/dist/types/index.d.ts.map +1 -0
  223. package/dist/types/plugins/air-quality/airnow.plugin.d.ts +84 -0
  224. package/dist/types/plugins/air-quality/airnow.plugin.d.ts.map +1 -0
  225. package/dist/types/plugins/air-quality/index.d.ts +3 -0
  226. package/dist/types/plugins/air-quality/index.d.ts.map +1 -0
  227. package/dist/types/plugins/base-plugin.d.ts +99 -0
  228. package/dist/types/plugins/base-plugin.d.ts.map +1 -0
  229. package/dist/types/plugins/events/index.d.ts +3 -0
  230. package/dist/types/plugins/events/index.d.ts.map +1 -0
  231. package/dist/types/plugins/events/phoenix-events.plugin.d.ts +71 -0
  232. package/dist/types/plugins/events/phoenix-events.plugin.d.ts.map +1 -0
  233. package/dist/types/plugins/fire-emt/index.d.ts +3 -0
  234. package/dist/types/plugins/fire-emt/index.d.ts.map +1 -0
  235. package/dist/types/plugins/fire-emt/phoenix-fire.plugin.d.ts +47 -0
  236. package/dist/types/plugins/fire-emt/phoenix-fire.plugin.d.ts.map +1 -0
  237. package/dist/types/plugins/index.d.ts +17 -0
  238. package/dist/types/plugins/index.d.ts.map +1 -0
  239. package/dist/types/plugins/police-blotter/index.d.ts +3 -0
  240. package/dist/types/plugins/police-blotter/index.d.ts.map +1 -0
  241. package/dist/types/plugins/police-blotter/phoenix-police.plugin.d.ts +49 -0
  242. package/dist/types/plugins/police-blotter/phoenix-police.plugin.d.ts.map +1 -0
  243. package/dist/types/plugins/pulsepoint/index.d.ts +3 -0
  244. package/dist/types/plugins/pulsepoint/index.d.ts.map +1 -0
  245. package/dist/types/plugins/pulsepoint/pulsepoint.plugin.d.ts +61 -0
  246. package/dist/types/plugins/pulsepoint/pulsepoint.plugin.d.ts.map +1 -0
  247. package/dist/types/plugins/traffic/arizona-traffic.plugin.d.ts +83 -0
  248. package/dist/types/plugins/traffic/arizona-traffic.plugin.d.ts.map +1 -0
  249. package/dist/types/plugins/traffic/index.d.ts +3 -0
  250. package/dist/types/plugins/traffic/index.d.ts.map +1 -0
  251. package/dist/types/plugins/weather/index.d.ts +3 -0
  252. package/dist/types/plugins/weather/index.d.ts.map +1 -0
  253. package/dist/types/plugins/weather/nws-weather.plugin.d.ts +50 -0
  254. package/dist/types/plugins/weather/nws-weather.plugin.d.ts.map +1 -0
  255. package/dist/types/schemas/alert.schema.d.ts +266 -0
  256. package/dist/types/schemas/alert.schema.d.ts.map +1 -0
  257. package/dist/types/schemas/index.d.ts +5 -0
  258. package/dist/types/schemas/index.d.ts.map +1 -0
  259. package/dist/types/schemas/query.schema.d.ts +150 -0
  260. package/dist/types/schemas/query.schema.d.ts.map +1 -0
  261. package/dist/types/types/alert.d.ts +96 -0
  262. package/dist/types/types/alert.d.ts.map +1 -0
  263. package/dist/types/types/config.d.ts +63 -0
  264. package/dist/types/types/config.d.ts.map +1 -0
  265. package/dist/types/types/geo.d.ts +33 -0
  266. package/dist/types/types/geo.d.ts.map +1 -0
  267. package/dist/types/types/index.d.ts +9 -0
  268. package/dist/types/types/index.d.ts.map +1 -0
  269. package/dist/types/types/plugin.d.ts +125 -0
  270. package/dist/types/types/plugin.d.ts.map +1 -0
  271. package/dist/types/types/query.d.ts +86 -0
  272. package/dist/types/types/query.d.ts.map +1 -0
  273. package/dist/types/utils/cache.d.ts +112 -0
  274. package/dist/types/utils/cache.d.ts.map +1 -0
  275. package/dist/types/utils/csv.d.ts +38 -0
  276. package/dist/types/utils/csv.d.ts.map +1 -0
  277. package/dist/types/utils/date.d.ts +47 -0
  278. package/dist/types/utils/date.d.ts.map +1 -0
  279. package/dist/types/utils/index.d.ts +7 -0
  280. package/dist/types/utils/index.d.ts.map +1 -0
  281. package/dist/types/utils/retry.d.ts +51 -0
  282. package/dist/types/utils/retry.d.ts.map +1 -0
  283. package/package.json +115 -0
@@ -0,0 +1,382 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PhoenixEventsPlugin = void 0;
4
+ const base_plugin_1 = require("../base-plugin");
5
+ /**
6
+ * Phoenix downtown center coordinates.
7
+ */
8
+ const PHOENIX_DOWNTOWN = {
9
+ latitude: 33.4484,
10
+ longitude: -112.074,
11
+ };
12
+ /**
13
+ * Coverage radius - 10km for downtown Phoenix events.
14
+ */
15
+ const COVERAGE_RADIUS_METERS = 10_000;
16
+ /**
17
+ * Major Phoenix venues with coordinates.
18
+ */
19
+ const PHOENIX_VENUES = {
20
+ 'Chase Field': { latitude: 33.4455, longitude: -112.0667 },
21
+ 'Footprint Center': { latitude: 33.4459, longitude: -112.0712 },
22
+ 'State Farm Stadium': { latitude: 33.5276, longitude: -112.2626 },
23
+ 'Arizona State University': { latitude: 33.4242, longitude: -111.9281 },
24
+ 'Phoenix Convention Center': { latitude: 33.4419, longitude: -112.0711 },
25
+ 'Comerica Theatre': { latitude: 33.4461, longitude: -112.0710 },
26
+ 'Celebrity Theatre': { latitude: 33.4870, longitude: -112.0744 },
27
+ 'Van Buren': { latitude: 33.4542, longitude: -112.0747 },
28
+ 'Crescent Ballroom': { latitude: 33.4509, longitude: -112.0714 },
29
+ };
30
+ /**
31
+ * Event types that may cause civil unrest concerns.
32
+ */
33
+ const CIVIL_UNREST_KEYWORDS = [
34
+ 'protest',
35
+ 'march',
36
+ 'rally',
37
+ 'demonstration',
38
+ 'strike',
39
+ 'political',
40
+ ];
41
+ /**
42
+ * Plugin that fetches event data from Ticketmaster and Phoenix city permits.
43
+ *
44
+ * Aggregates from:
45
+ * - Ticketmaster Discovery API for concerts, sports, theater
46
+ * - Phoenix Open Data for special event permits (street closures, parades)
47
+ */
48
+ class PhoenixEventsPlugin extends base_plugin_1.BasePlugin {
49
+ metadata = {
50
+ id: 'phoenix-events',
51
+ name: 'Phoenix Events',
52
+ version: '1.0.0',
53
+ description: 'Events from Ticketmaster and Phoenix city permits',
54
+ coverage: {
55
+ type: 'regional',
56
+ center: PHOENIX_DOWNTOWN,
57
+ radiusMeters: COVERAGE_RADIUS_METERS,
58
+ description: 'Downtown Phoenix area',
59
+ },
60
+ supportedTemporalTypes: ['scheduled'],
61
+ supportedCategories: ['event', 'civil-unrest'],
62
+ refreshIntervalMs: 30 * 60 * 1000, // 30 minutes
63
+ };
64
+ eventsConfig;
65
+ constructor(config) {
66
+ super(config);
67
+ this.eventsConfig = {
68
+ limit: 100,
69
+ enablePhoenixPermits: true,
70
+ ...config,
71
+ enableTicketmaster: config?.enableTicketmaster ?? !!config?.ticketmasterApiKey,
72
+ };
73
+ }
74
+ async fetchAlerts(options) {
75
+ const cacheKey = this.generateCacheKey(options);
76
+ const warnings = [];
77
+ try {
78
+ const { data, fromCache } = await this.getCachedOrFetch(cacheKey, () => this.fetchAllEvents(options, warnings), this.config.cacheTtlMs);
79
+ // Filter by categories if specified
80
+ let alerts = data;
81
+ if (options.categories && options.categories.length > 0) {
82
+ alerts = alerts.filter((alert) => options.categories.includes(alert.category));
83
+ }
84
+ return {
85
+ alerts,
86
+ fromCache,
87
+ cacheKey,
88
+ warnings: warnings.length > 0 ? warnings : undefined,
89
+ };
90
+ }
91
+ catch (error) {
92
+ console.error('Phoenix Events fetch error:', error);
93
+ throw error;
94
+ }
95
+ }
96
+ /**
97
+ * Fetch events from all enabled sources.
98
+ */
99
+ async fetchAllEvents(options, warnings) {
100
+ const allAlerts = [];
101
+ // Fetch from Ticketmaster
102
+ if (this.eventsConfig.enableTicketmaster && this.eventsConfig.ticketmasterApiKey) {
103
+ try {
104
+ const tmAlerts = await this.fetchTicketmasterEvents(options);
105
+ allAlerts.push(...tmAlerts);
106
+ }
107
+ catch (error) {
108
+ warnings.push(`Ticketmaster fetch failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
109
+ }
110
+ }
111
+ // Fetch from Phoenix permits
112
+ if (this.eventsConfig.enablePhoenixPermits) {
113
+ try {
114
+ const permitAlerts = await this.fetchPhoenixPermitEvents(options);
115
+ allAlerts.push(...permitAlerts);
116
+ }
117
+ catch (error) {
118
+ warnings.push(`Phoenix permits fetch failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
119
+ }
120
+ }
121
+ // Deduplicate events by venue + date
122
+ return this.deduplicateEvents(allAlerts);
123
+ }
124
+ /**
125
+ * Fetch events from Ticketmaster Discovery API.
126
+ */
127
+ async fetchTicketmasterEvents(options) {
128
+ const { location, timeRange } = options;
129
+ const apiKey = this.eventsConfig.ticketmasterApiKey;
130
+ const params = new URLSearchParams({
131
+ apikey: apiKey,
132
+ latlong: `${location.latitude},${location.longitude}`,
133
+ radius: '25', // miles
134
+ unit: 'miles',
135
+ startDateTime: new Date(timeRange.start).toISOString().replace('.000', ''),
136
+ endDateTime: new Date(timeRange.end).toISOString().replace('.000', ''),
137
+ size: String(this.eventsConfig.limit),
138
+ sort: 'date,asc',
139
+ });
140
+ const url = `https://app.ticketmaster.com/discovery/v2/events.json?${params}`;
141
+ const response = await this.fetchJson(url);
142
+ if (!response._embedded?.events) {
143
+ return [];
144
+ }
145
+ return response._embedded.events.map((event) => this.transformTicketmasterEvent(event));
146
+ }
147
+ /**
148
+ * Transform a Ticketmaster event to our Alert format.
149
+ */
150
+ transformTicketmasterEvent(event) {
151
+ const venue = event._embedded?.venues?.[0];
152
+ const classification = event.classifications?.[0];
153
+ // Determine location
154
+ let latitude = PHOENIX_DOWNTOWN.latitude;
155
+ let longitude = PHOENIX_DOWNTOWN.longitude;
156
+ if (venue?.location) {
157
+ latitude = parseFloat(venue.location.latitude);
158
+ longitude = parseFloat(venue.location.longitude);
159
+ }
160
+ else if (venue?.name && PHOENIX_VENUES[venue.name]) {
161
+ latitude = PHOENIX_VENUES[venue.name].latitude;
162
+ longitude = PHOENIX_VENUES[venue.name].longitude;
163
+ }
164
+ // Build timestamps
165
+ const startDateTime = event.dates.start.dateTime ?? `${event.dates.start.localDate}T${event.dates.start.localTime ?? '00:00:00'}`;
166
+ const endDateTime = event.dates.end?.dateTime;
167
+ // Determine risk level based on event type/venue
168
+ const riskLevel = this.assessEventRisk(event, venue?.name);
169
+ return this.createAlert({
170
+ id: `tm-${event.id}`,
171
+ externalId: event.id,
172
+ title: event.name,
173
+ description: this.buildTicketmasterDescription(event, classification, venue),
174
+ riskLevel,
175
+ priority: this.riskLevelToPriority(riskLevel),
176
+ category: 'event',
177
+ temporalType: 'scheduled',
178
+ location: {
179
+ point: { latitude, longitude },
180
+ address: venue?.address?.line1,
181
+ city: venue?.city?.name ?? 'Phoenix',
182
+ state: venue?.state?.stateCode ?? 'AZ',
183
+ zipCode: venue?.postalCode,
184
+ },
185
+ timestamps: {
186
+ issued: new Date().toISOString(),
187
+ eventStart: startDateTime,
188
+ eventEnd: endDateTime,
189
+ },
190
+ url: event.url,
191
+ metadata: {
192
+ source: 'ticketmaster',
193
+ venue: venue?.name,
194
+ segment: classification?.segment?.name,
195
+ genre: classification?.genre?.name,
196
+ status: event.dates.status?.code,
197
+ },
198
+ });
199
+ }
200
+ /**
201
+ * Fetch events from Phoenix Open Data special events permits.
202
+ */
203
+ async fetchPhoenixPermitEvents(options) {
204
+ const { timeRange } = options;
205
+ // Phoenix special events dataset (if available)
206
+ // Note: This endpoint may need to be updated based on actual Phoenix Open Data availability
207
+ const baseUrl = 'https://www.phoenixopendata.com/resource/yqvh-8bti.json';
208
+ const params = new URLSearchParams({
209
+ $limit: String(this.eventsConfig.limit),
210
+ $order: 'start_date ASC',
211
+ });
212
+ // Filter by date range
213
+ const startDate = timeRange.start.split('T')[0];
214
+ const endDate = timeRange.end.split('T')[0];
215
+ params.set('$where', `start_date >= '${startDate}' AND start_date <= '${endDate}'`);
216
+ if (this.eventsConfig.socrataAppToken) {
217
+ params.set('$$app_token', this.eventsConfig.socrataAppToken);
218
+ }
219
+ const url = `${baseUrl}?${params}`;
220
+ try {
221
+ const events = await this.fetchJson(url);
222
+ return events.map((event) => this.transformPhoenixPermitEvent(event));
223
+ }
224
+ catch (error) {
225
+ // Phoenix permits endpoint might not always be available
226
+ console.warn('Phoenix permits fetch failed, continuing with other sources:', error);
227
+ return [];
228
+ }
229
+ }
230
+ /**
231
+ * Transform a Phoenix permit event to our Alert format.
232
+ */
233
+ transformPhoenixPermitEvent(event) {
234
+ // Parse location
235
+ const latitude = event.latitude ? parseFloat(event.latitude) : PHOENIX_DOWNTOWN.latitude;
236
+ const longitude = event.longitude ? parseFloat(event.longitude) : PHOENIX_DOWNTOWN.longitude;
237
+ // Build timestamps
238
+ const startDate = event.start_date ?? new Date().toISOString().split('T')[0];
239
+ const startTime = event.start_time ?? '00:00:00';
240
+ const startDateTime = `${startDate}T${startTime}`;
241
+ let endDateTime;
242
+ if (event.end_date) {
243
+ const endTime = event.end_time ?? '23:59:59';
244
+ endDateTime = `${event.end_date}T${endTime}`;
245
+ }
246
+ // Determine category and risk
247
+ const { category, riskLevel } = this.assessPermitEventRisk(event);
248
+ return this.createAlert({
249
+ id: `phoenix-permit-${event.permit_number ?? Date.now()}`,
250
+ externalId: event.permit_number,
251
+ title: event.event_name ?? 'Special Event',
252
+ description: this.buildPermitDescription(event),
253
+ riskLevel,
254
+ priority: this.riskLevelToPriority(riskLevel),
255
+ category,
256
+ temporalType: 'scheduled',
257
+ location: {
258
+ point: { latitude, longitude },
259
+ address: event.event_location,
260
+ city: 'Phoenix',
261
+ state: 'AZ',
262
+ },
263
+ timestamps: {
264
+ issued: new Date().toISOString(),
265
+ eventStart: startDateTime,
266
+ eventEnd: endDateTime,
267
+ },
268
+ metadata: {
269
+ source: 'phoenix-permits',
270
+ eventType: event.event_type,
271
+ streetClosure: event.street_closure,
272
+ permitNumber: event.permit_number,
273
+ },
274
+ });
275
+ }
276
+ /**
277
+ * Assess risk level for a Ticketmaster event.
278
+ */
279
+ assessEventRisk(event, venueName) {
280
+ // Large venue events have higher crowd impact
281
+ const largeVenues = ['Chase Field', 'Footprint Center', 'State Farm Stadium'];
282
+ if (venueName && largeVenues.includes(venueName)) {
283
+ return 'moderate';
284
+ }
285
+ // Sports events typically have high attendance
286
+ const segment = event.classifications?.[0]?.segment?.name?.toLowerCase();
287
+ if (segment === 'sports') {
288
+ return 'moderate';
289
+ }
290
+ // Music events can vary
291
+ if (segment === 'music') {
292
+ return 'low';
293
+ }
294
+ return 'low';
295
+ }
296
+ /**
297
+ * Assess risk and category for a permit event.
298
+ */
299
+ assessPermitEventRisk(event) {
300
+ const name = (event.event_name ?? '').toLowerCase();
301
+ const type = (event.event_type ?? '').toLowerCase();
302
+ const description = (event.description ?? '').toLowerCase();
303
+ const combined = `${name} ${type} ${description}`;
304
+ // Check for civil unrest indicators
305
+ for (const keyword of CIVIL_UNREST_KEYWORDS) {
306
+ if (combined.includes(keyword)) {
307
+ return { category: 'civil-unrest', riskLevel: 'moderate' };
308
+ }
309
+ }
310
+ // Check for street closures
311
+ if (event.street_closure && event.street_closure.toLowerCase() !== 'no') {
312
+ return { category: 'event', riskLevel: 'moderate' };
313
+ }
314
+ // Default
315
+ return { category: 'event', riskLevel: 'low' };
316
+ }
317
+ /**
318
+ * Build description for Ticketmaster event.
319
+ */
320
+ buildTicketmasterDescription(event, classification, venue) {
321
+ const parts = [];
322
+ if (classification?.segment?.name) {
323
+ parts.push(`Type: ${classification.segment.name}`);
324
+ }
325
+ if (classification?.genre?.name) {
326
+ parts.push(`Genre: ${classification.genre.name}`);
327
+ }
328
+ if (venue?.name) {
329
+ parts.push(`Venue: ${venue.name}`);
330
+ }
331
+ if (event.dates.start.localDate) {
332
+ const time = event.dates.start.localTime ?? '';
333
+ parts.push(`Date: ${event.dates.start.localDate} ${time}`.trim());
334
+ }
335
+ return parts.join('\n');
336
+ }
337
+ /**
338
+ * Build description for permit event.
339
+ */
340
+ buildPermitDescription(event) {
341
+ const parts = [];
342
+ if (event.event_type) {
343
+ parts.push(`Type: ${event.event_type}`);
344
+ }
345
+ if (event.event_location) {
346
+ parts.push(`Location: ${event.event_location}`);
347
+ }
348
+ if (event.description) {
349
+ parts.push(`\n${event.description}`);
350
+ }
351
+ if (event.street_closure && event.street_closure.toLowerCase() !== 'no') {
352
+ parts.push(`\nStreet Closure: ${event.street_closure}`);
353
+ }
354
+ return parts.join('\n');
355
+ }
356
+ /**
357
+ * Deduplicate events that might appear in both sources.
358
+ */
359
+ deduplicateEvents(alerts) {
360
+ const seen = new Map();
361
+ for (const alert of alerts) {
362
+ // Create dedup key from venue + date
363
+ const venue = alert.metadata?.venue ?? alert.location.address ?? 'unknown';
364
+ const date = alert.timestamps.eventStart?.split('T')[0] ?? '';
365
+ const key = `${venue.toLowerCase()}-${date}`;
366
+ const existing = seen.get(key);
367
+ if (!existing) {
368
+ seen.set(key, alert);
369
+ }
370
+ else {
371
+ // Prefer Ticketmaster data (usually more complete)
372
+ if (alert.metadata?.source === 'ticketmaster' &&
373
+ existing.metadata?.source !== 'ticketmaster') {
374
+ seen.set(key, alert);
375
+ }
376
+ }
377
+ }
378
+ return Array.from(seen.values());
379
+ }
380
+ }
381
+ exports.PhoenixEventsPlugin = PhoenixEventsPlugin;
382
+ //# sourceMappingURL=phoenix-events.plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phoenix-events.plugin.js","sourceRoot":"","sources":["../../../../src/plugins/events/phoenix-events.plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PhoenixFirePlugin = void 0;
4
+ var phoenix_fire_plugin_1 = require("./phoenix-fire.plugin");
5
+ Object.defineProperty(exports, "PhoenixFirePlugin", { enumerable: true, get: function () { return phoenix_fire_plugin_1.PhoenixFirePlugin; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/plugins/fire-emt/index.ts"],"names":[],"mappings":";;;AAAA,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA"}
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PhoenixFirePlugin = void 0;
4
+ const base_plugin_1 = require("../base-plugin");
5
+ /**
6
+ * Phoenix center coordinates.
7
+ */
8
+ const PHOENIX_CENTER = {
9
+ latitude: 33.4484,
10
+ longitude: -112.074,
11
+ };
12
+ /**
13
+ * Coverage radius in meters (approximately 50km covers greater Phoenix metro).
14
+ */
15
+ const COVERAGE_RADIUS_METERS = 50_000;
16
+ /**
17
+ * Call type to category and risk mappings.
18
+ */
19
+ const CALL_TYPE_MAP = {
20
+ // Fire-related
21
+ STRUCTURE_FIRE: { category: 'fire', risk: 'extreme' },
22
+ 'STRUCTURE FIRE': { category: 'fire', risk: 'extreme' },
23
+ FIRE: { category: 'fire', risk: 'severe' },
24
+ BRUSH_FIRE: { category: 'fire', risk: 'severe' },
25
+ 'BRUSH FIRE': { category: 'fire', risk: 'severe' },
26
+ VEHICLE_FIRE: { category: 'fire', risk: 'high' },
27
+ 'VEHICLE FIRE': { category: 'fire', risk: 'high' },
28
+ DUMPSTER_FIRE: { category: 'fire', risk: 'moderate' },
29
+ 'DUMPSTER FIRE': { category: 'fire', risk: 'moderate' },
30
+ SMOKE_INVESTIGATION: { category: 'fire', risk: 'moderate' },
31
+ 'SMOKE INVESTIGATION': { category: 'fire', risk: 'moderate' },
32
+ ALARM: { category: 'fire', risk: 'low' },
33
+ 'FIRE ALARM': { category: 'fire', risk: 'low' },
34
+ // Medical-related
35
+ CARDIAC: { category: 'medical', risk: 'severe' },
36
+ 'CARDIAC ARREST': { category: 'medical', risk: 'severe' },
37
+ STROKE: { category: 'medical', risk: 'severe' },
38
+ TRAUMA: { category: 'medical', risk: 'severe' },
39
+ 'MAJOR TRAUMA': { category: 'medical', risk: 'severe' },
40
+ MEDICAL: { category: 'medical', risk: 'high' },
41
+ 'MEDICAL AID': { category: 'medical', risk: 'high' },
42
+ 'MEDICAL EMERGENCY': { category: 'medical', risk: 'high' },
43
+ OVERDOSE: { category: 'medical', risk: 'high' },
44
+ 'DRUG OVERDOSE': { category: 'medical', risk: 'high' },
45
+ BREATHING: { category: 'medical', risk: 'high' },
46
+ 'BREATHING PROBLEMS': { category: 'medical', risk: 'high' },
47
+ UNCONSCIOUS: { category: 'medical', risk: 'high' },
48
+ 'UNCONSCIOUS PERSON': { category: 'medical', risk: 'high' },
49
+ FALL: { category: 'medical', risk: 'moderate' },
50
+ SICK: { category: 'medical', risk: 'moderate' },
51
+ 'SICK PERSON': { category: 'medical', risk: 'moderate' },
52
+ TRANSFER: { category: 'medical', risk: 'low' },
53
+ 'PATIENT TRANSFER': { category: 'medical', risk: 'low' },
54
+ // Traffic/accident-related (categorize as medical for injuries)
55
+ ACCIDENT: { category: 'medical', risk: 'high' },
56
+ 'TRAFFIC ACCIDENT': { category: 'medical', risk: 'high' },
57
+ 'MOTOR VEHICLE ACCIDENT': { category: 'medical', risk: 'high' },
58
+ MVA: { category: 'medical', risk: 'high' },
59
+ EXTRICATION: { category: 'medical', risk: 'severe' },
60
+ 'VEHICLE EXTRICATION': { category: 'medical', risk: 'severe' },
61
+ // Hazmat
62
+ HAZMAT: { category: 'fire', risk: 'severe' },
63
+ 'HAZARDOUS MATERIALS': { category: 'fire', risk: 'severe' },
64
+ 'GAS LEAK': { category: 'fire', risk: 'high' },
65
+ 'CARBON MONOXIDE': { category: 'fire', risk: 'high' },
66
+ };
67
+ /**
68
+ * Plugin that fetches fire and EMS incident data from Phoenix Open Data Portal.
69
+ *
70
+ * @see https://www.phoenixopendata.com/dataset/fire-incidents
71
+ */
72
+ class PhoenixFirePlugin extends base_plugin_1.BasePlugin {
73
+ metadata = {
74
+ id: 'phoenix-fire',
75
+ name: 'Phoenix Fire Department',
76
+ version: '1.0.0',
77
+ description: 'Fire and EMS incidents from Phoenix Fire Department',
78
+ coverage: {
79
+ type: 'regional',
80
+ center: PHOENIX_CENTER,
81
+ radiusMeters: COVERAGE_RADIUS_METERS,
82
+ description: 'Phoenix, AZ metropolitan area',
83
+ },
84
+ supportedTemporalTypes: ['historical', 'real-time'],
85
+ supportedCategories: ['fire', 'medical'],
86
+ refreshIntervalMs: 15 * 60 * 1000, // 15 minutes
87
+ };
88
+ fireConfig;
89
+ constructor(config) {
90
+ super(config);
91
+ this.fireConfig = {
92
+ limit: 1000,
93
+ ...config,
94
+ };
95
+ }
96
+ async fetchAlerts(options) {
97
+ const { location, timeRange, radiusMeters, limit, categories } = options;
98
+ const cacheKey = this.generateCacheKey(options);
99
+ // Build Socrata API URL
100
+ const url = this.buildApiUrl(location, timeRange, radiusMeters, limit);
101
+ try {
102
+ const { data, fromCache } = await this.getCachedOrFetch(cacheKey, () => this.fetchIncidents(url), this.config.cacheTtlMs);
103
+ let alerts = data.map((incident) => this.transformIncident(incident));
104
+ // Filter by categories if specified
105
+ if (categories && categories.length > 0) {
106
+ alerts = alerts.filter((alert) => categories.includes(alert.category));
107
+ }
108
+ return {
109
+ alerts,
110
+ fromCache,
111
+ cacheKey,
112
+ };
113
+ }
114
+ catch (error) {
115
+ console.error('Phoenix Fire fetch error:', error);
116
+ throw error;
117
+ }
118
+ }
119
+ /**
120
+ * Build the Socrata API URL for Phoenix fire data.
121
+ */
122
+ buildApiUrl(location, timeRange, radiusMeters, limit) {
123
+ // Phoenix Fire incidents dataset
124
+ const baseUrl = 'https://www.phoenixopendata.com/resource/m3af-pnrm.json';
125
+ const params = new URLSearchParams();
126
+ // Limit results
127
+ params.set('$limit', String(limit ?? this.fireConfig.limit));
128
+ // Order by most recent
129
+ params.set('$order', 'incident_date DESC');
130
+ // Build WHERE clause
131
+ const whereClauses = [];
132
+ // Time filter
133
+ const startDate = timeRange.start.split('T')[0];
134
+ const endDate = timeRange.end.split('T')[0];
135
+ whereClauses.push(`incident_date >= '${startDate}'`);
136
+ whereClauses.push(`incident_date <= '${endDate}'`);
137
+ // Location filter using within_circle
138
+ whereClauses.push(`within_circle(geocoded_column, ${location.latitude}, ${location.longitude}, ${radiusMeters})`);
139
+ params.set('$where', whereClauses.join(' AND '));
140
+ // Add app token if provided
141
+ if (this.fireConfig.appToken) {
142
+ params.set('$$app_token', this.fireConfig.appToken);
143
+ }
144
+ return `${baseUrl}?${params.toString()}`;
145
+ }
146
+ /**
147
+ * Fetch incidents from the API.
148
+ */
149
+ async fetchIncidents(url) {
150
+ return this.fetchJson(url);
151
+ }
152
+ /**
153
+ * Transform a Phoenix Fire incident to our Alert format.
154
+ */
155
+ transformIncident(incident) {
156
+ const callType = (incident.call_type_final ?? incident.call_type ?? 'UNKNOWN').toUpperCase();
157
+ const { category, risk } = this.mapCallType(callType);
158
+ // Parse coordinates
159
+ const latitude = incident.latitude ? parseFloat(incident.latitude) : PHOENIX_CENTER.latitude;
160
+ const longitude = incident.longitude
161
+ ? parseFloat(incident.longitude)
162
+ : PHOENIX_CENTER.longitude;
163
+ // Build timestamp from incident_date and dispatch_time
164
+ const incidentDate = incident.incident_date ?? new Date().toISOString().split('T')[0];
165
+ const dispatchTime = incident.dispatch_time ?? '00:00:00';
166
+ const issued = `${incidentDate}T${dispatchTime}`;
167
+ // Determine if this is real-time (within last hour) or historical
168
+ const issuedTime = new Date(issued).getTime();
169
+ const oneHourAgo = Date.now() - 60 * 60 * 1000;
170
+ const temporalType = issuedTime > oneHourAgo ? 'real-time' : 'historical';
171
+ return this.createAlert({
172
+ id: `phoenix-fire-${incident.incident_number}`,
173
+ externalId: incident.incident_number,
174
+ title: this.formatCallType(callType),
175
+ description: this.buildDescription(incident),
176
+ riskLevel: risk,
177
+ priority: this.riskLevelToPriority(risk),
178
+ category,
179
+ temporalType,
180
+ location: {
181
+ point: { latitude, longitude },
182
+ address: incident.address,
183
+ city: incident.city ?? 'Phoenix',
184
+ state: 'AZ',
185
+ zipCode: incident.zip,
186
+ },
187
+ timestamps: {
188
+ issued,
189
+ eventStart: issued,
190
+ eventEnd: incident.clear_time ? `${incidentDate}T${incident.clear_time}` : undefined,
191
+ },
192
+ metadata: {
193
+ incidentNumber: incident.incident_number,
194
+ callType,
195
+ priority: incident.priority,
196
+ station: incident.station,
197
+ battalion: incident.battalion,
198
+ arrivalTime: incident.arrival_time,
199
+ clearTime: incident.clear_time,
200
+ },
201
+ });
202
+ }
203
+ /**
204
+ * Map a call type to category and risk level.
205
+ */
206
+ mapCallType(callType) {
207
+ // Try exact match first
208
+ if (CALL_TYPE_MAP[callType]) {
209
+ return CALL_TYPE_MAP[callType];
210
+ }
211
+ // Try partial match
212
+ for (const [key, value] of Object.entries(CALL_TYPE_MAP)) {
213
+ if (callType.includes(key) || key.includes(callType)) {
214
+ return value;
215
+ }
216
+ }
217
+ // Default based on common keywords
218
+ if (callType.includes('FIRE') || callType.includes('SMOKE') || callType.includes('BURN')) {
219
+ return { category: 'fire', risk: 'high' };
220
+ }
221
+ if (callType.includes('MEDICAL') ||
222
+ callType.includes('EMS') ||
223
+ callType.includes('AMBULANCE')) {
224
+ return { category: 'medical', risk: 'moderate' };
225
+ }
226
+ // Default
227
+ return { category: 'fire', risk: 'moderate' };
228
+ }
229
+ /**
230
+ * Format call type for display.
231
+ */
232
+ formatCallType(callType) {
233
+ return callType
234
+ .toLowerCase()
235
+ .replace(/_/g, ' ')
236
+ .split(' ')
237
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
238
+ .join(' ');
239
+ }
240
+ /**
241
+ * Build description from incident data.
242
+ */
243
+ buildDescription(incident) {
244
+ const parts = [];
245
+ const callType = incident.call_type_final ?? incident.call_type;
246
+ if (callType) {
247
+ parts.push(`Type: ${this.formatCallType(callType)}`);
248
+ }
249
+ if (incident.address) {
250
+ parts.push(`Location: ${incident.address}`);
251
+ }
252
+ if (incident.priority) {
253
+ parts.push(`Priority: ${incident.priority}`);
254
+ }
255
+ if (incident.station) {
256
+ parts.push(`Station: ${incident.station}`);
257
+ }
258
+ return parts.join('\n');
259
+ }
260
+ }
261
+ exports.PhoenixFirePlugin = PhoenixFirePlugin;
262
+ //# sourceMappingURL=phoenix-fire.plugin.js.map