@cyanheads/epa-mcp-server 0.3.2 → 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.
Files changed (72) hide show
  1. package/AGENTS.md +13 -10
  2. package/CLAUDE.md +13 -10
  3. package/Dockerfile +108 -37
  4. package/README.md +67 -44
  5. package/changelog/0.3.x/0.3.3.md +34 -0
  6. package/changelog/0.4.x/0.4.0.md +35 -0
  7. package/dist/mcp-server/tools/definitions/get-air-quality.tool.d.ts +23 -9
  8. package/dist/mcp-server/tools/definitions/get-air-quality.tool.d.ts.map +1 -1
  9. package/dist/mcp-server/tools/definitions/get-air-quality.tool.js +166 -98
  10. package/dist/mcp-server/tools/definitions/get-air-quality.tool.js.map +1 -1
  11. package/dist/mcp-server/tools/definitions/get-ejscreen.tool.d.ts.map +1 -1
  12. package/dist/mcp-server/tools/definitions/get-ejscreen.tool.js +1 -1
  13. package/dist/mcp-server/tools/definitions/get-ejscreen.tool.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/get-facility.tool.d.ts.map +1 -1
  15. package/dist/mcp-server/tools/definitions/get-facility.tool.js +5 -14
  16. package/dist/mcp-server/tools/definitions/get-facility.tool.js.map +1 -1
  17. package/dist/mcp-server/tools/definitions/get-tri-releases.tool.d.ts +2 -1
  18. package/dist/mcp-server/tools/definitions/get-tri-releases.tool.d.ts.map +1 -1
  19. package/dist/mcp-server/tools/definitions/get-tri-releases.tool.js +10 -14
  20. package/dist/mcp-server/tools/definitions/get-tri-releases.tool.js.map +1 -1
  21. package/dist/mcp-server/tools/definitions/index.d.ts +51 -18
  22. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  23. package/dist/mcp-server/tools/definitions/search-facilities.tool.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/definitions/search-facilities.tool.js +9 -16
  25. package/dist/mcp-server/tools/definitions/search-facilities.tool.js.map +1 -1
  26. package/dist/mcp-server/tools/definitions/search-superfund.tool.d.ts.map +1 -1
  27. package/dist/mcp-server/tools/definitions/search-superfund.tool.js +4 -14
  28. package/dist/mcp-server/tools/definitions/search-superfund.tool.js.map +1 -1
  29. package/dist/mcp-server/tools/definitions/search-tri-releases.tool.d.ts +10 -3
  30. package/dist/mcp-server/tools/definitions/search-tri-releases.tool.d.ts.map +1 -1
  31. package/dist/mcp-server/tools/definitions/search-tri-releases.tool.js +50 -10
  32. package/dist/mcp-server/tools/definitions/search-tri-releases.tool.js.map +1 -1
  33. package/dist/mcp-server/tools/definitions/search-violations.tool.d.ts.map +1 -1
  34. package/dist/mcp-server/tools/definitions/search-violations.tool.js +1 -3
  35. package/dist/mcp-server/tools/definitions/search-violations.tool.js.map +1 -1
  36. package/dist/mcp-server/tools/definitions/search-water-systems.tool.d.ts +3 -1
  37. package/dist/mcp-server/tools/definitions/search-water-systems.tool.d.ts.map +1 -1
  38. package/dist/mcp-server/tools/definitions/search-water-systems.tool.js +23 -10
  39. package/dist/mcp-server/tools/definitions/search-water-systems.tool.js.map +1 -1
  40. package/dist/mcp-server/tools/format-lbs.d.ts +14 -0
  41. package/dist/mcp-server/tools/format-lbs.d.ts.map +1 -0
  42. package/dist/mcp-server/tools/format-lbs.js +16 -0
  43. package/dist/mcp-server/tools/format-lbs.js.map +1 -0
  44. package/dist/mcp-server/tools/format-location.d.ts +18 -0
  45. package/dist/mcp-server/tools/format-location.d.ts.map +1 -0
  46. package/dist/mcp-server/tools/format-location.js +26 -0
  47. package/dist/mcp-server/tools/format-location.js.map +1 -0
  48. package/dist/mcp-server/tools/format-tri-release.d.ts +16 -0
  49. package/dist/mcp-server/tools/format-tri-release.d.ts.map +1 -0
  50. package/dist/mcp-server/tools/format-tri-release.js +23 -0
  51. package/dist/mcp-server/tools/format-tri-release.js.map +1 -0
  52. package/dist/services/airnow/airnow-service.d.ts +10 -29
  53. package/dist/services/airnow/airnow-service.d.ts.map +1 -1
  54. package/dist/services/airnow/airnow-service.js +148 -95
  55. package/dist/services/airnow/airnow-service.js.map +1 -1
  56. package/dist/services/airnow/types.d.ts +62 -23
  57. package/dist/services/airnow/types.d.ts.map +1 -1
  58. package/dist/services/airnow/types.js +2 -1
  59. package/dist/services/airnow/types.js.map +1 -1
  60. package/dist/services/dmap/dmap-service.d.ts +41 -2
  61. package/dist/services/dmap/dmap-service.d.ts.map +1 -1
  62. package/dist/services/dmap/dmap-service.js +231 -116
  63. package/dist/services/dmap/dmap-service.js.map +1 -1
  64. package/dist/services/dmap/types.d.ts +17 -4
  65. package/dist/services/dmap/types.d.ts.map +1 -1
  66. package/dist/services/echo/echo-service.d.ts.map +1 -1
  67. package/dist/services/echo/echo-service.js +8 -5
  68. package/dist/services/echo/echo-service.js.map +1 -1
  69. package/dist/services/ejscreen/ejscreen-service.js +1 -1
  70. package/dist/services/ejscreen/ejscreen-service.js.map +1 -1
  71. package/package.json +11 -10
  72. package/server.json +16 -24
@@ -1,44 +1,25 @@
1
1
  /**
2
- * @fileoverview AirNow API service for real-time and forecast air quality data.
3
- * Wraps www.airnowapi.org/aq endpoints. Responses cached at ~1 hour TTL per the
4
- * AirNow API's recommendation to avoid rate-limiting.
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
8
  import type { Context } from '@cyanheads/mcp-ts-core';
8
9
  import type { AppConfig } from '@cyanheads/mcp-ts-core/config';
9
10
  import type { StorageService } from '@cyanheads/mcp-ts-core/storage';
10
- import type { AirQualityResult } from './types.js';
11
+ import type { AirNowLocation, AirQualityLookup } from './types.js';
11
12
  export declare class AirNowService {
12
13
  private readonly baseUrl;
13
14
  private readonly apiKey;
14
15
  constructor(_config: AppConfig, _storage: StorageService);
16
+ /** Current hourly observations: per pollutant, the closest monitor within AirNow's 50-mile boundary. */
17
+ getCurrent(location: AirNowLocation, ctx: Context): Promise<AirQualityLookup>;
18
+ /** Every forecast day the location's reporting area has issued. */
19
+ getForecast(location: AirNowLocation, ctx: Context): Promise<AirQualityLookup>;
15
20
  private buildUrl;
16
21
  private cacheKey;
17
- private fetchWithCache;
18
- /** Get current air quality by ZIP code. */
19
- getCurrentByZip(params: {
20
- zipCode: string;
21
- distanceMiles?: number;
22
- }, ctx: Context): Promise<AirQualityResult[]>;
23
- /** Get current air quality by lat/lng. */
24
- getCurrentByLatLng(params: {
25
- latitude: number;
26
- longitude: number;
27
- distanceMiles?: number;
28
- }, ctx: Context): Promise<AirQualityResult[]>;
29
- /** Get air quality forecast by ZIP code. */
30
- getForecastByZip(params: {
31
- zipCode: string;
32
- date: string;
33
- distanceMiles?: number;
34
- }, ctx: Context): Promise<AirQualityResult[]>;
35
- /** Get air quality forecast by lat/lng. */
36
- getForecastByLatLng(params: {
37
- latitude: number;
38
- longitude: number;
39
- date: string;
40
- distanceMiles?: number;
41
- }, ctx: Context): Promise<AirQualityResult[]>;
22
+ private fetchBody;
42
23
  }
43
24
  export declare function initAirNowService(config: AppConfig, storage: StorageService): void;
44
25
  export declare function getAirNowService(): AirNowService;
@@ -1 +1 @@
1
- {"version":3,"file":"airnow-service.d.ts","sourceRoot":"","sources":["../../../src/services/airnow/airnow-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;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,EAAE,gBAAgB,EAAmB,MAAM,YAAY,CAAC;AAsCpE,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,YAAY,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAWvD;IAED,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,QAAQ;YAWF,cAAc;IAuC5B,2CAA2C;IACrC,eAAe,CACnB,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,EACD,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAO7B;IAED,0CAA0C;IACpC,kBAAkB,CACtB,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,EACD,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAW7B;IAED,4CAA4C;IACtC,gBAAgB,CACpB,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,EACD,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAW7B;IAED,2CAA2C;IACrC,mBAAmB,CACvB,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,EACD,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAgB7B;CACF;AAMD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAElF;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAKhD"}
1
+ {"version":3,"file":"airnow-service.d.ts","sourceRoot":"","sources":["../../../src/services/airnow/airnow-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAM/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAGrE,OAAO,KAAK,EACV,cAAc,EAEd,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAyHpB,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,YAAY,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAWvD;IAED,wGAAwG;IAClG,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAKlF;IAED,mEAAmE;IAC7D,WAAW,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAKnF;IAED,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,QAAQ;YAUF,SAAS;CA4DxB;AAMD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAElF;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAKhD"}
@@ -1,43 +1,122 @@
1
1
  /**
2
- * @fileoverview AirNow API service for real-time and forecast air quality data.
3
- * Wraps www.airnowapi.org/aq endpoints. Responses cached at ~1 hour TTL per the
4
- * AirNow API's recommendation to avoid rate-limiting.
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
- /** Normalize raw AirNow records (grouped by reporting area) into AirQualityResult[]. */
12
- function normalizeRecords(records) {
13
- // Group by reporting area + date/hour
14
- const grouped = new Map();
15
- for (const r of records) {
16
- const key = `${r.ReportingArea ?? ''}|${r.DateObserved ?? ''}|${r.HourObserved ?? ''}`;
17
- let entry = grouped.get(key);
18
- if (!entry) {
19
- entry = {
20
- ...(r.ReportingArea && { reportingArea: r.ReportingArea.trim() }),
21
- ...(r.StateCode && { stateCode: r.StateCode }),
22
- ...(r.Latitude !== undefined && { latitude: r.Latitude }),
23
- ...(r.Longitude !== undefined && { longitude: r.Longitude }),
24
- ...(r.DateObserved && { dateObserved: r.DateObserved.trim() }),
25
- ...(r.HourObserved !== undefined && { hourObserved: r.HourObserved }),
26
- ...(r.LocalTimeZone && { localTimeZone: r.LocalTimeZone }),
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
- grouped.set(key, entry);
68
+ areas.set(key, area);
30
69
  }
31
- if (r.ParameterName && r.AQI !== undefined) {
32
- entry.readings.push({
33
- parameterName: r.ParameterName,
34
- aqi: r.AQI,
35
- ...(r.Category?.Number !== undefined && { categoryNumber: r.Category.Number }),
36
- ...(r.Category?.Name && { categoryName: r.Category.Name }),
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 Array.from(grouped.values());
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
- if (val !== undefined && val !== '') {
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 fetchWithCache(url, cacheKey, ctx) {
77
- // Try cache first
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.data;
174
+ return cached.body;
82
175
  }
83
- const data = await withRetry(async () => {
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
- const parsed = JSON.parse(text);
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
- // Store in cache
100
- await ctx.state.set(cacheKey, { data, expiresAt: Date.now() + CACHE_TTL_MS });
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":"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"}
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
- /** Raw AirNow observation/forecast record from the JSON API. */
6
- export interface RawAirNowRecord {
7
- AQI?: number;
8
- Category?: {
9
- Number?: number;
10
- Name?: string;
11
- };
12
- DateObserved?: string;
13
- HourObserved?: number;
14
- Latitude?: number;
15
- LocalTimeZone?: string;
16
- Longitude?: number;
17
- ParameterName?: string;
18
- ReportingArea?: string;
19
- StateCode?: string;
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
- aqi: number;
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 AirNow observation/forecast result. */
29
- export interface AirQualityResult {
61
+ /** Normalized readings for one reporting area (and, for observations, one observed hour). */
62
+ export interface AirQualityArea {
30
63
  dateObserved?: string;
31
- hourObserved?: number;
32
- latitude?: number;
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;;;GAGG;AAEH,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
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;;;GAGG"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/airnow/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}