@cyanheads/epa-mcp-server 0.3.3 → 0.4.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/AGENTS.md +2 -2
- package/CLAUDE.md +2 -2
- package/README.md +38 -8
- package/changelog/0.4.x/0.4.0.md +35 -0
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.d.ts +23 -9
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.js +165 -93
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-facility.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-facility.tool.js +4 -11
- package/dist/mcp-server/tools/definitions/get-facility.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.d.ts +2 -1
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.js +10 -14
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +51 -18
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-facilities.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-facilities.tool.js +7 -12
- package/dist/mcp-server/tools/definitions/search-facilities.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-superfund.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-superfund.tool.js +2 -8
- package/dist/mcp-server/tools/definitions/search-superfund.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.d.ts +10 -3
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.js +50 -10
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.d.ts +3 -1
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.js +22 -7
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.js.map +1 -1
- package/dist/mcp-server/tools/format-lbs.d.ts +14 -0
- package/dist/mcp-server/tools/format-lbs.d.ts.map +1 -0
- package/dist/mcp-server/tools/format-lbs.js +16 -0
- package/dist/mcp-server/tools/format-lbs.js.map +1 -0
- package/dist/mcp-server/tools/format-location.d.ts +18 -0
- package/dist/mcp-server/tools/format-location.d.ts.map +1 -0
- package/dist/mcp-server/tools/format-location.js +26 -0
- package/dist/mcp-server/tools/format-location.js.map +1 -0
- package/dist/mcp-server/tools/format-tri-release.d.ts +16 -0
- package/dist/mcp-server/tools/format-tri-release.d.ts.map +1 -0
- package/dist/mcp-server/tools/format-tri-release.js +23 -0
- package/dist/mcp-server/tools/format-tri-release.js.map +1 -0
- package/dist/services/airnow/airnow-service.d.ts +10 -29
- package/dist/services/airnow/airnow-service.d.ts.map +1 -1
- package/dist/services/airnow/airnow-service.js +148 -95
- package/dist/services/airnow/airnow-service.js.map +1 -1
- package/dist/services/airnow/types.d.ts +62 -23
- package/dist/services/airnow/types.d.ts.map +1 -1
- package/dist/services/airnow/types.js +2 -1
- package/dist/services/airnow/types.js.map +1 -1
- package/dist/services/dmap/dmap-service.d.ts +41 -2
- package/dist/services/dmap/dmap-service.d.ts.map +1 -1
- package/dist/services/dmap/dmap-service.js +231 -116
- package/dist/services/dmap/dmap-service.js.map +1 -1
- package/dist/services/dmap/types.d.ts +17 -4
- package/dist/services/dmap/types.d.ts.map +1 -1
- package/dist/services/echo/echo-service.js +1 -1
- package/package.json +3 -3
- package/server.json +9 -3
|
@@ -1,43 +1,122 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview AirNow API service for
|
|
3
|
-
* Wraps www.airnowapi.org/aq
|
|
4
|
-
* AirNow API's
|
|
2
|
+
* @fileoverview AirNow API service for current observations and issued forecasts.
|
|
3
|
+
* Wraps the 2026 www.airnowapi.org/aq web services `observation/current/ziplatlong/`
|
|
4
|
+
* and `forecast/current/`. Responses cached at ~1 hour TTL per the AirNow API's
|
|
5
|
+
* recommendation to avoid rate-limiting.
|
|
5
6
|
* @module services/airnow/airnow-service
|
|
6
7
|
*/
|
|
7
|
-
import { serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
import { serializationError, serviceUnavailable, validationError, } from '@cyanheads/mcp-ts-core/errors';
|
|
8
9
|
import { httpErrorFromResponse, withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
9
10
|
import { getServerConfig } from '../../config/server-config.js';
|
|
10
11
|
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
for
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
/** The six AirNow AQI descriptors (lowercased) and their category numbers. */
|
|
13
|
+
const AQI_CATEGORY_NUMBERS = new Map([
|
|
14
|
+
['good', 1],
|
|
15
|
+
['moderate', 2],
|
|
16
|
+
['unhealthy for sensitive groups', 3],
|
|
17
|
+
['unhealthy', 4],
|
|
18
|
+
['very unhealthy', 5],
|
|
19
|
+
['hazardous', 6],
|
|
20
|
+
]);
|
|
21
|
+
/** The message(s) of an `{"WebServiceError":[{"Message":…}]}` body; undefined for any other shape. */
|
|
22
|
+
function webServiceErrorMessage(body) {
|
|
23
|
+
if (typeof body !== 'object' || body === null || !('WebServiceError' in body))
|
|
24
|
+
return;
|
|
25
|
+
const entries = body.WebServiceError;
|
|
26
|
+
if (!Array.isArray(entries))
|
|
27
|
+
return;
|
|
28
|
+
const messages = entries.flatMap((entry) => typeof entry?.Message === 'string' && entry.Message.trim() !== '' ? [entry.Message] : []);
|
|
29
|
+
return messages.length > 0 ? messages.join(' ') : undefined;
|
|
30
|
+
}
|
|
31
|
+
/** Classify a 200 body. Anything but a record array or a WebServiceError message fails loudly. */
|
|
32
|
+
function parseBody(text) {
|
|
33
|
+
let body;
|
|
34
|
+
try {
|
|
35
|
+
body = JSON.parse(text);
|
|
36
|
+
}
|
|
37
|
+
catch (cause) {
|
|
38
|
+
throw serializationError('AirNow returned a response that is not JSON.', undefined, { cause });
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(body))
|
|
41
|
+
return { rows: body };
|
|
42
|
+
const noDataMessage = webServiceErrorMessage(body);
|
|
43
|
+
if (noDataMessage !== undefined)
|
|
44
|
+
return { noDataMessage };
|
|
45
|
+
throw serializationError('AirNow returned an unrecognized response: expected an array of records or a WebServiceError message.');
|
|
46
|
+
}
|
|
47
|
+
/** An AQI value, or undefined when upstream sent none (forecasts use -1 for category-only). */
|
|
48
|
+
function validAqi(value) {
|
|
49
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
50
|
+
}
|
|
51
|
+
/** Group observation records by reporting area and observed hour; one reading per pollutant. */
|
|
52
|
+
function normalizeObservations(rows) {
|
|
53
|
+
const areas = new Map();
|
|
54
|
+
for (const r of rows) {
|
|
55
|
+
const aqi = validAqi(r.nowcastAQI);
|
|
56
|
+
if (!r.parameterName || (aqi === undefined && !r.aqiCategoryName))
|
|
57
|
+
continue;
|
|
58
|
+
const key = [r.reportingAreaName, r.dateObserved, r.hourObserved, r.localTimeZone].join('|');
|
|
59
|
+
let area = areas.get(key);
|
|
60
|
+
if (!area) {
|
|
61
|
+
area = {
|
|
62
|
+
...(r.reportingAreaName && { reportingArea: r.reportingAreaName }),
|
|
63
|
+
...(r.dateObserved && { dateObserved: r.dateObserved }),
|
|
64
|
+
...(r.hourObserved && { hourObserved: r.hourObserved }),
|
|
65
|
+
...(r.localTimeZone && { localTimeZone: r.localTimeZone }),
|
|
27
66
|
readings: [],
|
|
28
67
|
};
|
|
29
|
-
|
|
68
|
+
areas.set(key, area);
|
|
30
69
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
})
|
|
70
|
+
const categoryNumber = r.aqiCategoryName
|
|
71
|
+
? AQI_CATEGORY_NUMBERS.get(r.aqiCategoryName.trim().toLowerCase())
|
|
72
|
+
: undefined;
|
|
73
|
+
area.readings.push({
|
|
74
|
+
parameterName: r.parameterName,
|
|
75
|
+
...(aqi !== undefined && { aqi }),
|
|
76
|
+
...(r.aqiCategoryName && { categoryName: r.aqiCategoryName }),
|
|
77
|
+
...(categoryNumber !== undefined && { categoryNumber }),
|
|
78
|
+
...(r.siteName && { siteName: r.siteName }),
|
|
79
|
+
...(r.siteID && { siteID: r.siteID }),
|
|
80
|
+
...(r.reportingAgency && { reportingAgency: r.reportingAgency }),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return [...areas.values()];
|
|
84
|
+
}
|
|
85
|
+
/** Group forecast records by reporting area; one reading per pollutant per valid date. */
|
|
86
|
+
function normalizeForecasts(rows) {
|
|
87
|
+
const areas = new Map();
|
|
88
|
+
for (const r of rows) {
|
|
89
|
+
const aqi = validAqi(r.aqi);
|
|
90
|
+
if (!r.parameterName ||
|
|
91
|
+
(aqi === undefined && !r.categoryName && r.categoryNumber === undefined)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const key = r.reportingAreaCode ?? r.reportingArea ?? '';
|
|
95
|
+
let area = areas.get(key);
|
|
96
|
+
if (!area) {
|
|
97
|
+
area = {
|
|
98
|
+
...(r.reportingArea && { reportingArea: r.reportingArea }),
|
|
99
|
+
...(r.reportingAreaCode && { reportingAreaCode: r.reportingAreaCode }),
|
|
100
|
+
...(r.stateCode && { stateCode: r.stateCode }),
|
|
101
|
+
readings: [],
|
|
102
|
+
};
|
|
103
|
+
areas.set(key, area);
|
|
38
104
|
}
|
|
105
|
+
// AirNow repeats the area's discussion on every row; keep it once, verbatim.
|
|
106
|
+
if (!area.discussion && r.discussion)
|
|
107
|
+
area.discussion = r.discussion;
|
|
108
|
+
area.readings.push({
|
|
109
|
+
parameterName: r.parameterName,
|
|
110
|
+
...(aqi !== undefined && { aqi }),
|
|
111
|
+
...(r.categoryNumber !== undefined && { categoryNumber: r.categoryNumber }),
|
|
112
|
+
...(r.categoryName && { categoryName: r.categoryName }),
|
|
113
|
+
...(r.dateIssue && { dateIssue: r.dateIssue }),
|
|
114
|
+
...(r.dateValid && { dateValid: r.dateValid }),
|
|
115
|
+
...(r.forecastAgency && { forecastAgency: r.forecastAgency }),
|
|
116
|
+
...(typeof r.actionDay === 'boolean' && { actionDay: r.actionDay }),
|
|
117
|
+
});
|
|
39
118
|
}
|
|
40
|
-
return
|
|
119
|
+
return [...areas.values()];
|
|
41
120
|
}
|
|
42
121
|
export class AirNowService {
|
|
43
122
|
baseUrl;
|
|
@@ -52,20 +131,31 @@ export class AirNowService {
|
|
|
52
131
|
this.baseUrl = serverConfig.airNowBaseUrl;
|
|
53
132
|
this.apiKey = serverConfig.airNowApiKey;
|
|
54
133
|
}
|
|
134
|
+
/** Current hourly observations: per pollutant, the closest monitor within AirNow's 50-mile boundary. */
|
|
135
|
+
async getCurrent(location, ctx) {
|
|
136
|
+
const body = await this.fetchBody('observation', location, ctx);
|
|
137
|
+
return 'noDataMessage' in body
|
|
138
|
+
? { areas: [], noDataMessage: body.noDataMessage }
|
|
139
|
+
: { areas: normalizeObservations(body.rows) };
|
|
140
|
+
}
|
|
141
|
+
/** Every forecast day the location's reporting area has issued. */
|
|
142
|
+
async getForecast(location, ctx) {
|
|
143
|
+
const body = await this.fetchBody('forecast', location, ctx);
|
|
144
|
+
return 'noDataMessage' in body
|
|
145
|
+
? { areas: [], noDataMessage: body.noDataMessage }
|
|
146
|
+
: { areas: normalizeForecasts(body.rows) };
|
|
147
|
+
}
|
|
55
148
|
buildUrl(path, params) {
|
|
56
149
|
const url = new URL(`${this.baseUrl}/${path}`);
|
|
57
150
|
url.searchParams.set('format', 'application/json');
|
|
58
151
|
url.searchParams.set('API_KEY', this.apiKey);
|
|
59
152
|
for (const [key, val] of Object.entries(params)) {
|
|
60
|
-
|
|
61
|
-
url.searchParams.set(key, String(val));
|
|
62
|
-
}
|
|
153
|
+
url.searchParams.set(key, String(val));
|
|
63
154
|
}
|
|
64
155
|
return url.toString();
|
|
65
156
|
}
|
|
66
157
|
cacheKey(kind, params) {
|
|
67
158
|
const stable = Object.entries(params)
|
|
68
|
-
.filter(([, v]) => v !== undefined)
|
|
69
159
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
70
160
|
.map(([k, v]) => `${k}-${String(v).replace(/[^a-zA-Z0-9_.\-/]/g, '_')}`)
|
|
71
161
|
.join('_');
|
|
@@ -73,15 +163,33 @@ export class AirNowService {
|
|
|
73
163
|
const safeKind = kind.replace(/:/g, '/');
|
|
74
164
|
return `airnow/${safeKind}/${stable}`;
|
|
75
165
|
}
|
|
76
|
-
async
|
|
77
|
-
|
|
166
|
+
async fetchBody(service, location, ctx) {
|
|
167
|
+
const params = location.kind === 'zip'
|
|
168
|
+
? { zipCode: location.zipCode }
|
|
169
|
+
: { latitude: location.latitude, longitude: location.longitude };
|
|
170
|
+
const cacheKey = this.cacheKey(`${service}:${location.kind}`, params);
|
|
78
171
|
const cached = await ctx.state.get(cacheKey);
|
|
79
172
|
if (cached && cached.expiresAt > Date.now()) {
|
|
80
173
|
ctx.log.debug('AirNow cache hit', { cacheKey });
|
|
81
|
-
return cached.
|
|
174
|
+
return cached.body;
|
|
82
175
|
}
|
|
83
|
-
const
|
|
176
|
+
const path = service === 'observation' ? 'observation/current/ziplatlong/' : 'forecast/current/';
|
|
177
|
+
const url = this.buildUrl(path, params);
|
|
178
|
+
ctx.log.debug('AirNow request', { service, ...params });
|
|
179
|
+
const body = await withRetry(async () => {
|
|
84
180
|
const response = await fetch(url, { signal: ctx.signal });
|
|
181
|
+
// A ZIP is the only caller-supplied input on a ZIP request, so a 400 rejects it.
|
|
182
|
+
if (response.status === 400 && location.kind === 'zip') {
|
|
183
|
+
const upstream = await response.text();
|
|
184
|
+
let detail;
|
|
185
|
+
try {
|
|
186
|
+
detail = webServiceErrorMessage(JSON.parse(upstream));
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// Non-JSON 400 body: report the status alone.
|
|
190
|
+
}
|
|
191
|
+
throw validationError(`AirNow rejected zip_code "${location.zipCode}": ${detail ?? 'HTTP 400'}.`, { reason: 'invalid_zip_code', zipCode: location.zipCode });
|
|
192
|
+
}
|
|
85
193
|
if (!response.ok) {
|
|
86
194
|
throw await httpErrorFromResponse(response, { service: 'AirNow' });
|
|
87
195
|
}
|
|
@@ -89,69 +197,14 @@ export class AirNowService {
|
|
|
89
197
|
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
90
198
|
throw serviceUnavailable('AirNow API returned HTML instead of JSON — likely rate-limited.');
|
|
91
199
|
}
|
|
92
|
-
|
|
93
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
200
|
+
return parseBody(text);
|
|
94
201
|
}, {
|
|
95
202
|
operation: 'AirNowService.fetch',
|
|
96
203
|
baseDelayMs: 2000,
|
|
97
204
|
signal: ctx.signal,
|
|
98
205
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return data;
|
|
102
|
-
}
|
|
103
|
-
/** Get current air quality by ZIP code. */
|
|
104
|
-
async getCurrentByZip(params, ctx) {
|
|
105
|
-
const qparams = { zipCode: params.zipCode, distance: params.distanceMiles ?? 25 };
|
|
106
|
-
const url = this.buildUrl('observation/zipCode/current/', qparams);
|
|
107
|
-
const ck = this.cacheKey('current:zip', qparams);
|
|
108
|
-
ctx.log.debug('AirNow current by ZIP', { zipCode: params.zipCode });
|
|
109
|
-
const records = await this.fetchWithCache(url, ck, ctx);
|
|
110
|
-
return normalizeRecords(records);
|
|
111
|
-
}
|
|
112
|
-
/** Get current air quality by lat/lng. */
|
|
113
|
-
async getCurrentByLatLng(params, ctx) {
|
|
114
|
-
const qparams = {
|
|
115
|
-
latitude: params.latitude,
|
|
116
|
-
longitude: params.longitude,
|
|
117
|
-
distance: params.distanceMiles ?? 25,
|
|
118
|
-
};
|
|
119
|
-
const url = this.buildUrl('observation/latLong/current/', qparams);
|
|
120
|
-
const ck = this.cacheKey('current:latlng', qparams);
|
|
121
|
-
ctx.log.debug('AirNow current by lat/lng', { lat: params.latitude, lng: params.longitude });
|
|
122
|
-
const records = await this.fetchWithCache(url, ck, ctx);
|
|
123
|
-
return normalizeRecords(records);
|
|
124
|
-
}
|
|
125
|
-
/** Get air quality forecast by ZIP code. */
|
|
126
|
-
async getForecastByZip(params, ctx) {
|
|
127
|
-
const qparams = {
|
|
128
|
-
zipCode: params.zipCode,
|
|
129
|
-
date: params.date,
|
|
130
|
-
distance: params.distanceMiles ?? 25,
|
|
131
|
-
};
|
|
132
|
-
const url = this.buildUrl('forecast/zipCode/', qparams);
|
|
133
|
-
const ck = this.cacheKey('forecast:zip', qparams);
|
|
134
|
-
ctx.log.debug('AirNow forecast by ZIP', { zipCode: params.zipCode, date: params.date });
|
|
135
|
-
const records = await this.fetchWithCache(url, ck, ctx);
|
|
136
|
-
return normalizeRecords(records);
|
|
137
|
-
}
|
|
138
|
-
/** Get air quality forecast by lat/lng. */
|
|
139
|
-
async getForecastByLatLng(params, ctx) {
|
|
140
|
-
const qparams = {
|
|
141
|
-
latitude: params.latitude,
|
|
142
|
-
longitude: params.longitude,
|
|
143
|
-
date: params.date,
|
|
144
|
-
distance: params.distanceMiles ?? 25,
|
|
145
|
-
};
|
|
146
|
-
const url = this.buildUrl('forecast/latLong/', qparams);
|
|
147
|
-
const ck = this.cacheKey('forecast:latlng', qparams);
|
|
148
|
-
ctx.log.debug('AirNow forecast by lat/lng', {
|
|
149
|
-
lat: params.latitude,
|
|
150
|
-
lng: params.longitude,
|
|
151
|
-
date: params.date,
|
|
152
|
-
});
|
|
153
|
-
const records = await this.fetchWithCache(url, ck, ctx);
|
|
154
|
-
return normalizeRecords(records);
|
|
206
|
+
await ctx.state.set(cacheKey, { body, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
207
|
+
return body;
|
|
155
208
|
}
|
|
156
209
|
}
|
|
157
210
|
// --- Init/accessor pattern ---
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"airnow-service.js","sourceRoot":"","sources":["../../../src/services/airnow/airnow-service.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"airnow-service.js","sourceRoot":"","sources":["../../../src/services/airnow/airnow-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,39 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview AirNow API response types and normalized domain types
|
|
2
|
+
* @fileoverview AirNow API response types and normalized domain types for the 2026
|
|
3
|
+
* web services: `observation/current/ziplatlong/` and `forecast/current/`.
|
|
3
4
|
* @module services/airnow/types
|
|
4
5
|
*/
|
|
5
|
-
/**
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
/** Where to look up air quality: a ZIP code, or a latitude/longitude point. */
|
|
7
|
+
export type AirNowLocation = {
|
|
8
|
+
kind: 'zip';
|
|
9
|
+
zipCode: string;
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'latlng';
|
|
12
|
+
latitude: number;
|
|
13
|
+
longitude: number;
|
|
14
|
+
};
|
|
15
|
+
/** Raw record from `observation/current/ziplatlong/` — one pollutant from its closest monitor. */
|
|
16
|
+
export interface RawObservation {
|
|
17
|
+
aqiCategoryName?: string;
|
|
18
|
+
dateObserved?: string;
|
|
19
|
+
/** Local hour as `"HH:00"`. */
|
|
20
|
+
hourObserved?: string;
|
|
21
|
+
localTimeZone?: string;
|
|
22
|
+
nowcastAQI?: number;
|
|
23
|
+
parameterName?: string;
|
|
24
|
+
reportingAgency?: string;
|
|
25
|
+
reportingAreaName?: string;
|
|
26
|
+
siteID?: string;
|
|
27
|
+
siteName?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Raw record from `forecast/current/` — one pollutant for one valid date. */
|
|
30
|
+
export interface RawForecast {
|
|
31
|
+
actionDay?: boolean;
|
|
32
|
+
/** Forecast AQI, or `-1` when the agency issued a category only. */
|
|
33
|
+
aqi?: number;
|
|
34
|
+
categoryName?: string;
|
|
35
|
+
categoryNumber?: number;
|
|
36
|
+
dateIssue?: string;
|
|
37
|
+
dateValid?: string;
|
|
38
|
+
/** Agency discussion, repeated on every row of a reporting area; often empty. */
|
|
39
|
+
discussion?: string;
|
|
40
|
+
forecastAgency?: string;
|
|
41
|
+
parameterName?: string;
|
|
42
|
+
reportingArea?: string;
|
|
43
|
+
reportingAreaCode?: string;
|
|
44
|
+
stateCode?: string;
|
|
20
45
|
}
|
|
21
|
-
/** Normalized AQI reading for a single pollutant. */
|
|
46
|
+
/** Normalized AQI reading for a single pollutant (observation or forecast day). */
|
|
22
47
|
export interface AqiReading {
|
|
23
|
-
|
|
48
|
+
actionDay?: boolean;
|
|
49
|
+
/** Omitted when upstream supplied no AQI number (category-only forecast). */
|
|
50
|
+
aqi?: number;
|
|
24
51
|
categoryName?: string;
|
|
25
52
|
categoryNumber?: number;
|
|
53
|
+
dateIssue?: string;
|
|
54
|
+
dateValid?: string;
|
|
55
|
+
forecastAgency?: string;
|
|
26
56
|
parameterName: string;
|
|
57
|
+
reportingAgency?: string;
|
|
58
|
+
siteID?: string;
|
|
59
|
+
siteName?: string;
|
|
27
60
|
}
|
|
28
|
-
/** Normalized
|
|
29
|
-
export interface
|
|
61
|
+
/** Normalized readings for one reporting area (and, for observations, one observed hour). */
|
|
62
|
+
export interface AirQualityArea {
|
|
30
63
|
dateObserved?: string;
|
|
31
|
-
|
|
32
|
-
|
|
64
|
+
discussion?: string;
|
|
65
|
+
hourObserved?: string;
|
|
33
66
|
localTimeZone?: string;
|
|
34
|
-
longitude?: number;
|
|
35
67
|
readings: AqiReading[];
|
|
36
68
|
reportingArea?: string;
|
|
69
|
+
reportingAreaCode?: string;
|
|
37
70
|
stateCode?: string;
|
|
38
71
|
}
|
|
72
|
+
/** Result of one AirNow lookup. */
|
|
73
|
+
export interface AirQualityLookup {
|
|
74
|
+
areas: AirQualityArea[];
|
|
75
|
+
/** AirNow's own explanation, verbatim, when it answered HTTP 200 with no data for the location. */
|
|
76
|
+
noDataMessage?: string;
|
|
77
|
+
}
|
|
39
78
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/airnow/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/airnow/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,+EAA+E;AAC/E,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,kGAAkG;AAClG,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,mFAAmF;AACnF,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,6FAA6F;AAC7F,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,mCAAmC;AACnC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,mGAAmG;IACnG,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview AirNow API response types and normalized domain types
|
|
2
|
+
* @fileoverview AirNow API response types and normalized domain types for the 2026
|
|
3
|
+
* web services: `observation/current/ziplatlong/` and `forecast/current/`.
|
|
3
4
|
* @module services/airnow/types
|
|
4
5
|
*/
|
|
5
6
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/airnow/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/airnow/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -13,6 +13,13 @@ export declare class DmapService {
|
|
|
13
13
|
/**
|
|
14
14
|
* Build a DMAP REST URL for a multi-filter query.
|
|
15
15
|
* Format: {base}/{schema}.{table}/{col1}/{op1}/{val1}/and/{col2}/{op2}/{val2}/{first}:{last}
|
|
16
|
+
* Rows are 1-based and inclusive: `1:50` is 50 rows. DMAP reads a `first` of 0 as row 1, so
|
|
17
|
+
* `0:49` would return only 49.
|
|
18
|
+
*
|
|
19
|
+
* With `join`, an inner join follows the filters:
|
|
20
|
+
* `/join/{schema}.{table}/{leftColumn}/equals/{rightColumn}/and/{filter}…`. Filters before the
|
|
21
|
+
* join bind to the first table only (DMAP rejects a column the first table lacks); filters
|
|
22
|
+
* after the join comparison bind to the joined table. The range then counts joined rows.
|
|
16
23
|
*/
|
|
17
24
|
private buildTableUrl;
|
|
18
25
|
/** Fetch a DMAP table result as JSON array. */
|
|
@@ -23,25 +30,41 @@ export declare class DmapService {
|
|
|
23
30
|
year?: number;
|
|
24
31
|
chemicalName?: string;
|
|
25
32
|
}, ctx: Context): Promise<TriRelease[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Add each release's per-medium routine breakdown (tri.tri_release_qty, joined on doc_ctrl_num)
|
|
35
|
+
* with one batched request. `releases[i]` is the normalized form of `rows[i]`.
|
|
36
|
+
*/
|
|
37
|
+
private withReleaseBreakdown;
|
|
26
38
|
/**
|
|
27
39
|
* Batch-fetch the per-medium routine releases (tri.tri_release_qty) for a set of TRI
|
|
28
40
|
* submissions and roll them up into air/water/land/underground-injection sums keyed by
|
|
29
|
-
* doc_ctrl_num.
|
|
41
|
+
* doc_ctrl_num. getTriReleases makes this one request per call; searchTriReleases makes it
|
|
42
|
+
* once per call, and only when includeReleaseBreakdown is set — an uncached tri_release_qty
|
|
43
|
+
* query takes ~20 s upstream.
|
|
30
44
|
*
|
|
31
45
|
* Sparsity is preserved: a row flagged release_na="1" (medium not applicable) or carrying only
|
|
32
46
|
* a release_range_code (no hard total_release) contributes nothing — a null quantity is never
|
|
33
47
|
* coerced to 0.
|
|
34
48
|
*/
|
|
35
49
|
private fetchReleaseBreakdown;
|
|
36
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Search TRI releases by state, optionally filtered by county, year, or chemical.
|
|
52
|
+
* tri.tri_reporting_form carries no state, so one request joins it to tri.tri_facility: the
|
|
53
|
+
* state and county filters bind to the facility table, the year and chemical filters to the
|
|
54
|
+
* forms, and the row range caps the joined rows at `limit`. However sparse the filters, the
|
|
55
|
+
* search is one request; with includeReleaseBreakdown, one fetchReleaseBreakdown request adds
|
|
56
|
+
* the per-medium routine releases to every kept record.
|
|
57
|
+
*/
|
|
37
58
|
searchTriReleases(params: {
|
|
38
59
|
state: string;
|
|
39
60
|
county?: string;
|
|
40
61
|
year?: number;
|
|
41
62
|
chemicalName?: string;
|
|
42
63
|
limit?: number;
|
|
64
|
+
includeReleaseBreakdown?: boolean;
|
|
43
65
|
}, ctx: Context): Promise<(TriRelease & {
|
|
44
66
|
facilityName?: string;
|
|
67
|
+
countyName?: string;
|
|
45
68
|
})[]>;
|
|
46
69
|
/** Search Superfund sites by state or coordinates + radius. */
|
|
47
70
|
searchSuperfund(params: {
|
|
@@ -64,6 +87,22 @@ export declare class DmapService {
|
|
|
64
87
|
pwsType?: string;
|
|
65
88
|
limit?: number;
|
|
66
89
|
}, ctx: Context): Promise<WaterSystem[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Water systems in `state` with an open violation. water_system has no violation flag, so this
|
|
92
|
+
* reads the first VIOLATION_SCAN_ROWS open rows of sdwis.violation, then looks up the systems
|
|
93
|
+
* those rows name, PWSID_CHUNK_SIZE IDs per `pwsid in` request, until `limit` is filled. That is
|
|
94
|
+
* at most 1 + VIOLATION_SCAN_ROWS / PWSID_CHUNK_SIZE requests. A state with more open violation
|
|
95
|
+
* rows than the scan reads can have violators it never sees; the result then carries a notice.
|
|
96
|
+
*/
|
|
97
|
+
private searchViolatingWaterSystems;
|
|
98
|
+
/**
|
|
99
|
+
* Water systems in `zipCode` with an open violation. sdwis.violation has no zip column, so this
|
|
100
|
+
* reads the ZIP's systems (up to ZIP_SYSTEM_SCAN_ROWS), then asks sdwis.violation for their open
|
|
101
|
+
* rows PWSID_CHUNK_SIZE IDs at a time, in ZIP order, until `limit` violators are found. A ZIP
|
|
102
|
+
* past the system read, or a chunk whose violation rows fill VIOLATION_SCAN_ROWS, can hide
|
|
103
|
+
* violators; the result then carries a notice.
|
|
104
|
+
*/
|
|
105
|
+
private searchViolatingWaterSystemsInZip;
|
|
67
106
|
}
|
|
68
107
|
export declare function initDmapService(config: AppConfig, storage: StorageService): void;
|
|
69
108
|
export declare function getDmapService(): DmapService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dmap-service.d.ts","sourceRoot":"","sources":["../../../src/services/dmap/dmap-service.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAGrE,OAAO,KAAK,EAOV,aAAa,EACb,UAAU,EACV,WAAW,EACZ,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"dmap-service.d.ts","sourceRoot":"","sources":["../../../src/services/dmap/dmap-service.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAGrE,OAAO,KAAK,EAOV,aAAa,EACb,UAAU,EACV,WAAW,EACZ,MAAM,YAAY,CAAC;AAiMpB,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,YAAY,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAEvD;IAED;;;;;;;;;;OAUG;IACH,OAAO,CAAC,aAAa;IAsBrB,+CAA+C;YAEjC,UAAU;IA4BxB,gDAAgD;IAC1C,cAAc,CAClB,MAAM,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,EACD,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,UAAU,EAAE,CAAC,CAqBvB;IAED;;;OAGG;YACW,oBAAoB;IAoBlC;;;;;;;;;;OAUG;YACW,qBAAqB;IAiCnC;;;;;;;OAOG;IACG,iBAAiB,CACrB,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,uBAAuB,CAAC,EAAE,OAAO,CAAC;KACnC,EACD,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,CAAC,UAAU,GAAG;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC,CAyD1E;IAED,+DAA+D;IACzD,eAAe,CACnB,MAAM,EAAE;QACN,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,EACD,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,aAAa,EAAE,CAAC,CAmD1B;IAED,2DAA2D;IACrD,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAUhF;IAED,0DAA0D;IACpD,kBAAkB,CACtB,MAAM,EAAE;QACN,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,EACD,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,WAAW,EAAE,CAAC,CAmCxB;IAED;;;;;;OAMG;YACW,2BAA2B;IAyCzC;;;;;;OAMG;YACW,gCAAgC;CA4D/C;AAMD,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAEhF;AAED,wBAAgB,cAAc,IAAI,WAAW,CAK5C"}
|