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.
- package/dist/cjs/adapters/index.js +8 -0
- package/dist/cjs/adapters/index.js.map +1 -0
- package/dist/cjs/adapters/lambda.js +143 -0
- package/dist/cjs/adapters/lambda.js.map +1 -0
- package/dist/cjs/adapters/vercel.js +119 -0
- package/dist/cjs/adapters/vercel.js.map +1 -0
- package/dist/cjs/core/alert-aggregator.js +207 -0
- package/dist/cjs/core/alert-aggregator.js.map +1 -0
- package/dist/cjs/core/alert-feed.js +236 -0
- package/dist/cjs/core/alert-feed.js.map +1 -0
- package/dist/cjs/core/index.js +22 -0
- package/dist/cjs/core/index.js.map +1 -0
- package/dist/cjs/core/plugin-registry.js +193 -0
- package/dist/cjs/core/plugin-registry.js.map +1 -0
- package/dist/cjs/core/plugin-resolver.js +121 -0
- package/dist/cjs/core/plugin-resolver.js.map +1 -0
- package/dist/cjs/core/time-range.js +67 -0
- package/dist/cjs/core/time-range.js.map +1 -0
- package/dist/cjs/errors/fetch-error.js +71 -0
- package/dist/cjs/errors/fetch-error.js.map +1 -0
- package/dist/cjs/errors/index.js +15 -0
- package/dist/cjs/errors/index.js.map +1 -0
- package/dist/cjs/errors/plugin-error.js +80 -0
- package/dist/cjs/errors/plugin-error.js.map +1 -0
- package/dist/cjs/errors/validation-error.js +49 -0
- package/dist/cjs/errors/validation-error.js.map +1 -0
- package/dist/cjs/geo/distance.js +94 -0
- package/dist/cjs/geo/distance.js.map +1 -0
- package/dist/cjs/geo/index.js +18 -0
- package/dist/cjs/geo/index.js.map +1 -0
- package/dist/cjs/geo/point-in-radius.js +86 -0
- package/dist/cjs/geo/point-in-radius.js.map +1 -0
- package/dist/cjs/index.js +90 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/plugins/air-quality/airnow.plugin.js +343 -0
- package/dist/cjs/plugins/air-quality/airnow.plugin.js.map +1 -0
- package/dist/cjs/plugins/air-quality/index.js +6 -0
- package/dist/cjs/plugins/air-quality/index.js.map +1 -0
- package/dist/cjs/plugins/base-plugin.js +213 -0
- package/dist/cjs/plugins/base-plugin.js.map +1 -0
- package/dist/cjs/plugins/events/index.js +6 -0
- package/dist/cjs/plugins/events/index.js.map +1 -0
- package/dist/cjs/plugins/events/phoenix-events.plugin.js +382 -0
- package/dist/cjs/plugins/events/phoenix-events.plugin.js.map +1 -0
- package/dist/cjs/plugins/fire-emt/index.js +6 -0
- package/dist/cjs/plugins/fire-emt/index.js.map +1 -0
- package/dist/cjs/plugins/fire-emt/phoenix-fire.plugin.js +262 -0
- package/dist/cjs/plugins/fire-emt/phoenix-fire.plugin.js.map +1 -0
- package/dist/cjs/plugins/index.js +28 -0
- package/dist/cjs/plugins/index.js.map +1 -0
- package/dist/cjs/plugins/police-blotter/index.js +6 -0
- package/dist/cjs/plugins/police-blotter/index.js.map +1 -0
- package/dist/cjs/plugins/police-blotter/phoenix-police.plugin.js +198 -0
- package/dist/cjs/plugins/police-blotter/phoenix-police.plugin.js.map +1 -0
- package/dist/cjs/plugins/pulsepoint/index.js +6 -0
- package/dist/cjs/plugins/pulsepoint/index.js.map +1 -0
- package/dist/cjs/plugins/pulsepoint/pulsepoint.plugin.js +275 -0
- package/dist/cjs/plugins/pulsepoint/pulsepoint.plugin.js.map +1 -0
- package/dist/cjs/plugins/traffic/arizona-traffic.plugin.js +391 -0
- package/dist/cjs/plugins/traffic/arizona-traffic.plugin.js.map +1 -0
- package/dist/cjs/plugins/traffic/index.js +6 -0
- package/dist/cjs/plugins/traffic/index.js.map +1 -0
- package/dist/cjs/plugins/weather/index.js +6 -0
- package/dist/cjs/plugins/weather/index.js.map +1 -0
- package/dist/cjs/plugins/weather/nws-weather.plugin.js +180 -0
- package/dist/cjs/plugins/weather/nws-weather.plugin.js.map +1 -0
- package/dist/cjs/schemas/alert.schema.js +93 -0
- package/dist/cjs/schemas/alert.schema.js.map +1 -0
- package/dist/cjs/schemas/index.js +24 -0
- package/dist/cjs/schemas/index.js.map +1 -0
- package/dist/cjs/schemas/query.schema.js +76 -0
- package/dist/cjs/schemas/query.schema.js.map +1 -0
- package/dist/cjs/types/alert.js +35 -0
- package/dist/cjs/types/alert.js.map +1 -0
- package/dist/cjs/types/config.js +13 -0
- package/dist/cjs/types/config.js.map +1 -0
- package/dist/cjs/types/geo.js +3 -0
- package/dist/cjs/types/geo.js.map +1 -0
- package/dist/cjs/types/index.js +16 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/types/plugin.js +3 -0
- package/dist/cjs/types/plugin.js.map +1 -0
- package/dist/cjs/types/query.js +28 -0
- package/dist/cjs/types/query.js.map +1 -0
- package/dist/cjs/utils/cache.js +188 -0
- package/dist/cjs/utils/cache.js.map +1 -0
- package/dist/cjs/utils/csv.js +189 -0
- package/dist/cjs/utils/csv.js.map +1 -0
- package/dist/cjs/utils/date.js +153 -0
- package/dist/cjs/utils/date.js.map +1 -0
- package/dist/cjs/utils/index.js +28 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/cjs/utils/retry.js +109 -0
- package/dist/cjs/utils/retry.js.map +1 -0
- package/dist/esm/adapters/index.js +3 -0
- package/dist/esm/adapters/index.js.map +1 -0
- package/dist/esm/adapters/lambda.js +140 -0
- package/dist/esm/adapters/lambda.js.map +1 -0
- package/dist/esm/adapters/vercel.js +116 -0
- package/dist/esm/adapters/vercel.js.map +1 -0
- package/dist/esm/core/alert-aggregator.js +203 -0
- package/dist/esm/core/alert-aggregator.js.map +1 -0
- package/dist/esm/core/alert-feed.js +232 -0
- package/dist/esm/core/alert-feed.js.map +1 -0
- package/dist/esm/core/index.js +6 -0
- package/dist/esm/core/index.js.map +1 -0
- package/dist/esm/core/plugin-registry.js +189 -0
- package/dist/esm/core/plugin-registry.js.map +1 -0
- package/dist/esm/core/plugin-resolver.js +117 -0
- package/dist/esm/core/plugin-resolver.js.map +1 -0
- package/dist/esm/core/time-range.js +57 -0
- package/dist/esm/core/time-range.js.map +1 -0
- package/dist/esm/errors/fetch-error.js +67 -0
- package/dist/esm/errors/fetch-error.js.map +1 -0
- package/dist/esm/errors/index.js +4 -0
- package/dist/esm/errors/index.js.map +1 -0
- package/dist/esm/errors/plugin-error.js +71 -0
- package/dist/esm/errors/plugin-error.js.map +1 -0
- package/dist/esm/errors/validation-error.js +45 -0
- package/dist/esm/errors/validation-error.js.map +1 -0
- package/dist/esm/geo/distance.js +85 -0
- package/dist/esm/geo/distance.js.map +1 -0
- package/dist/esm/geo/index.js +3 -0
- package/dist/esm/geo/index.js.map +1 -0
- package/dist/esm/geo/point-in-radius.js +79 -0
- package/dist/esm/geo/point-in-radius.js.map +1 -0
- package/dist/esm/index.js +30 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/plugins/air-quality/airnow.plugin.js +339 -0
- package/dist/esm/plugins/air-quality/airnow.plugin.js.map +1 -0
- package/dist/esm/plugins/air-quality/index.js +2 -0
- package/dist/esm/plugins/air-quality/index.js.map +1 -0
- package/dist/esm/plugins/base-plugin.js +209 -0
- package/dist/esm/plugins/base-plugin.js.map +1 -0
- package/dist/esm/plugins/events/index.js +2 -0
- package/dist/esm/plugins/events/index.js.map +1 -0
- package/dist/esm/plugins/events/phoenix-events.plugin.js +378 -0
- package/dist/esm/plugins/events/phoenix-events.plugin.js.map +1 -0
- package/dist/esm/plugins/fire-emt/index.js +2 -0
- package/dist/esm/plugins/fire-emt/index.js.map +1 -0
- package/dist/esm/plugins/fire-emt/phoenix-fire.plugin.js +258 -0
- package/dist/esm/plugins/fire-emt/phoenix-fire.plugin.js.map +1 -0
- package/dist/esm/plugins/index.js +17 -0
- package/dist/esm/plugins/index.js.map +1 -0
- package/dist/esm/plugins/police-blotter/index.js +2 -0
- package/dist/esm/plugins/police-blotter/index.js.map +1 -0
- package/dist/esm/plugins/police-blotter/phoenix-police.plugin.js +194 -0
- package/dist/esm/plugins/police-blotter/phoenix-police.plugin.js.map +1 -0
- package/dist/esm/plugins/pulsepoint/index.js +2 -0
- package/dist/esm/plugins/pulsepoint/index.js.map +1 -0
- package/dist/esm/plugins/pulsepoint/pulsepoint.plugin.js +271 -0
- package/dist/esm/plugins/pulsepoint/pulsepoint.plugin.js.map +1 -0
- package/dist/esm/plugins/traffic/arizona-traffic.plugin.js +387 -0
- package/dist/esm/plugins/traffic/arizona-traffic.plugin.js.map +1 -0
- package/dist/esm/plugins/traffic/index.js +2 -0
- package/dist/esm/plugins/traffic/index.js.map +1 -0
- package/dist/esm/plugins/weather/index.js +2 -0
- package/dist/esm/plugins/weather/index.js.map +1 -0
- package/dist/esm/plugins/weather/nws-weather.plugin.js +176 -0
- package/dist/esm/plugins/weather/nws-weather.plugin.js.map +1 -0
- package/dist/esm/schemas/alert.schema.js +90 -0
- package/dist/esm/schemas/alert.schema.js.map +1 -0
- package/dist/esm/schemas/index.js +5 -0
- package/dist/esm/schemas/index.js.map +1 -0
- package/dist/esm/schemas/query.schema.js +72 -0
- package/dist/esm/schemas/query.schema.js.map +1 -0
- package/dist/esm/types/alert.js +32 -0
- package/dist/esm/types/alert.js.map +1 -0
- package/dist/esm/types/config.js +10 -0
- package/dist/esm/types/config.js.map +1 -0
- package/dist/esm/types/geo.js +2 -0
- package/dist/esm/types/geo.js.map +1 -0
- package/dist/esm/types/index.js +4 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/esm/types/plugin.js +2 -0
- package/dist/esm/types/plugin.js.map +1 -0
- package/dist/esm/types/query.js +25 -0
- package/dist/esm/types/query.js.map +1 -0
- package/dist/esm/utils/cache.js +181 -0
- package/dist/esm/utils/cache.js.map +1 -0
- package/dist/esm/utils/csv.js +185 -0
- package/dist/esm/utils/csv.js.map +1 -0
- package/dist/esm/utils/date.js +142 -0
- package/dist/esm/utils/date.js.map +1 -0
- package/dist/esm/utils/index.js +5 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/dist/esm/utils/retry.js +102 -0
- package/dist/esm/utils/retry.js.map +1 -0
- package/dist/types/adapters/index.d.ts +5 -0
- package/dist/types/adapters/index.d.ts.map +1 -0
- package/dist/types/adapters/lambda.d.ts +37 -0
- package/dist/types/adapters/lambda.d.ts.map +1 -0
- package/dist/types/adapters/vercel.d.ts +54 -0
- package/dist/types/adapters/vercel.d.ts.map +1 -0
- package/dist/types/core/alert-aggregator.d.ts +81 -0
- package/dist/types/core/alert-aggregator.d.ts.map +1 -0
- package/dist/types/core/alert-feed.d.ts +80 -0
- package/dist/types/core/alert-feed.d.ts.map +1 -0
- package/dist/types/core/index.d.ts +8 -0
- package/dist/types/core/index.d.ts.map +1 -0
- package/dist/types/core/plugin-registry.d.ts +91 -0
- package/dist/types/core/plugin-registry.d.ts.map +1 -0
- package/dist/types/core/plugin-resolver.d.ts +78 -0
- package/dist/types/core/plugin-resolver.d.ts.map +1 -0
- package/dist/types/core/time-range.d.ts +40 -0
- package/dist/types/core/time-range.d.ts.map +1 -0
- package/dist/types/errors/fetch-error.d.ts +46 -0
- package/dist/types/errors/fetch-error.d.ts.map +1 -0
- package/dist/types/errors/index.d.ts +5 -0
- package/dist/types/errors/index.d.ts.map +1 -0
- package/dist/types/errors/plugin-error.d.ts +42 -0
- package/dist/types/errors/plugin-error.d.ts.map +1 -0
- package/dist/types/errors/validation-error.d.ts +34 -0
- package/dist/types/errors/validation-error.d.ts.map +1 -0
- package/dist/types/geo/distance.d.ts +50 -0
- package/dist/types/geo/distance.d.ts.map +1 -0
- package/dist/types/geo/index.d.ts +3 -0
- package/dist/types/geo/index.d.ts.map +1 -0
- package/dist/types/geo/point-in-radius.d.ts +44 -0
- package/dist/types/geo/point-in-radius.d.ts.map +1 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/plugins/air-quality/airnow.plugin.d.ts +84 -0
- package/dist/types/plugins/air-quality/airnow.plugin.d.ts.map +1 -0
- package/dist/types/plugins/air-quality/index.d.ts +3 -0
- package/dist/types/plugins/air-quality/index.d.ts.map +1 -0
- package/dist/types/plugins/base-plugin.d.ts +99 -0
- package/dist/types/plugins/base-plugin.d.ts.map +1 -0
- package/dist/types/plugins/events/index.d.ts +3 -0
- package/dist/types/plugins/events/index.d.ts.map +1 -0
- package/dist/types/plugins/events/phoenix-events.plugin.d.ts +71 -0
- package/dist/types/plugins/events/phoenix-events.plugin.d.ts.map +1 -0
- package/dist/types/plugins/fire-emt/index.d.ts +3 -0
- package/dist/types/plugins/fire-emt/index.d.ts.map +1 -0
- package/dist/types/plugins/fire-emt/phoenix-fire.plugin.d.ts +47 -0
- package/dist/types/plugins/fire-emt/phoenix-fire.plugin.d.ts.map +1 -0
- package/dist/types/plugins/index.d.ts +17 -0
- package/dist/types/plugins/index.d.ts.map +1 -0
- package/dist/types/plugins/police-blotter/index.d.ts +3 -0
- package/dist/types/plugins/police-blotter/index.d.ts.map +1 -0
- package/dist/types/plugins/police-blotter/phoenix-police.plugin.d.ts +49 -0
- package/dist/types/plugins/police-blotter/phoenix-police.plugin.d.ts.map +1 -0
- package/dist/types/plugins/pulsepoint/index.d.ts +3 -0
- package/dist/types/plugins/pulsepoint/index.d.ts.map +1 -0
- package/dist/types/plugins/pulsepoint/pulsepoint.plugin.d.ts +61 -0
- package/dist/types/plugins/pulsepoint/pulsepoint.plugin.d.ts.map +1 -0
- package/dist/types/plugins/traffic/arizona-traffic.plugin.d.ts +83 -0
- package/dist/types/plugins/traffic/arizona-traffic.plugin.d.ts.map +1 -0
- package/dist/types/plugins/traffic/index.d.ts +3 -0
- package/dist/types/plugins/traffic/index.d.ts.map +1 -0
- package/dist/types/plugins/weather/index.d.ts +3 -0
- package/dist/types/plugins/weather/index.d.ts.map +1 -0
- package/dist/types/plugins/weather/nws-weather.plugin.d.ts +50 -0
- package/dist/types/plugins/weather/nws-weather.plugin.d.ts.map +1 -0
- package/dist/types/schemas/alert.schema.d.ts +266 -0
- package/dist/types/schemas/alert.schema.d.ts.map +1 -0
- package/dist/types/schemas/index.d.ts +5 -0
- package/dist/types/schemas/index.d.ts.map +1 -0
- package/dist/types/schemas/query.schema.d.ts +150 -0
- package/dist/types/schemas/query.schema.d.ts.map +1 -0
- package/dist/types/types/alert.d.ts +96 -0
- package/dist/types/types/alert.d.ts.map +1 -0
- package/dist/types/types/config.d.ts +63 -0
- package/dist/types/types/config.d.ts.map +1 -0
- package/dist/types/types/geo.d.ts +33 -0
- package/dist/types/types/geo.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +9 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/types/plugin.d.ts +125 -0
- package/dist/types/types/plugin.d.ts.map +1 -0
- package/dist/types/types/query.d.ts +86 -0
- package/dist/types/types/query.d.ts.map +1 -0
- package/dist/types/utils/cache.d.ts +112 -0
- package/dist/types/utils/cache.d.ts.map +1 -0
- package/dist/types/utils/csv.d.ts +38 -0
- package/dist/types/utils/csv.d.ts.map +1 -0
- package/dist/types/utils/date.d.ts +47 -0
- package/dist/types/utils/date.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +7 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/dist/types/utils/retry.d.ts +51 -0
- package/dist/types/utils/retry.d.ts.map +1 -0
- 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
|