@cyanheads/epa-mcp-server 0.1.1
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/CLAUDE.md +405 -0
- package/Dockerfile +98 -0
- package/README.md +322 -0
- package/changelog/0.1.x/0.1.0.md +22 -0
- package/changelog/0.1.x/0.1.1.md +18 -0
- package/changelog/template.md +119 -0
- package/dist/config/server-config.d.ts +15 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +38 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/facility.resource.d.ts +9 -0
- package/dist/mcp-server/resources/definitions/facility.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/facility.resource.js +24 -0
- package/dist/mcp-server/resources/definitions/facility.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/index.d.ts +12 -0
- package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/index.js +10 -0
- package/dist/mcp-server/resources/definitions/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/superfund-site.resource.d.ts +9 -0
- package/dist/mcp-server/resources/definitions/superfund-site.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/superfund-site.resource.js +26 -0
- package/dist/mcp-server/resources/definitions/superfund-site.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.d.ts +46 -0
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.js +193 -0
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-facility.tool.d.ts +61 -0
- package/dist/mcp-server/tools/definitions/get-facility.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-facility.tool.js +193 -0
- package/dist/mcp-server/tools/definitions/get-facility.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.d.ts +26 -0
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.js +92 -0
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/index.d.ts +305 -0
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/index.js +31 -0
- package/dist/mcp-server/tools/definitions/index.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-facilities.tool.d.ts +58 -0
- package/dist/mcp-server/tools/definitions/search-facilities.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/search-facilities.tool.js +182 -0
- package/dist/mcp-server/tools/definitions/search-facilities.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-superfund.tool.d.ts +49 -0
- package/dist/mcp-server/tools/definitions/search-superfund.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/search-superfund.tool.js +155 -0
- package/dist/mcp-server/tools/definitions/search-superfund.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.d.ts +29 -0
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.js +113 -0
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-violations.tool.d.ts +48 -0
- package/dist/mcp-server/tools/definitions/search-violations.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/search-violations.tool.js +156 -0
- package/dist/mcp-server/tools/definitions/search-violations.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.d.ts +38 -0
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.js +147 -0
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.js.map +1 -0
- package/dist/services/airnow/airnow-service.d.ts +45 -0
- package/dist/services/airnow/airnow-service.d.ts.map +1 -0
- package/dist/services/airnow/airnow-service.js +160 -0
- package/dist/services/airnow/airnow-service.js.map +1 -0
- package/dist/services/airnow/types.d.ts +39 -0
- package/dist/services/airnow/types.d.ts.map +1 -0
- package/dist/services/airnow/types.js +6 -0
- package/dist/services/airnow/types.js.map +1 -0
- package/dist/services/dmap/dmap-service.d.ts +60 -0
- package/dist/services/dmap/dmap-service.d.ts.map +1 -0
- package/dist/services/dmap/dmap-service.js +320 -0
- package/dist/services/dmap/dmap-service.js.map +1 -0
- package/dist/services/dmap/types.d.ts +108 -0
- package/dist/services/dmap/types.d.ts.map +1 -0
- package/dist/services/dmap/types.js +6 -0
- package/dist/services/dmap/types.js.map +1 -0
- package/dist/services/echo/echo-service.d.ts +57 -0
- package/dist/services/echo/echo-service.d.ts.map +1 -0
- package/dist/services/echo/echo-service.js +295 -0
- package/dist/services/echo/echo-service.js.map +1 -0
- package/dist/services/echo/types.d.ts +198 -0
- package/dist/services/echo/types.d.ts.map +1 -0
- package/dist/services/echo/types.js +6 -0
- package/dist/services/echo/types.js.map +1 -0
- package/package.json +80 -0
- package/server.json +111 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tool for searching EPA enforcement cases by location, program, or date range.
|
|
3
|
+
* @module mcp-server/tools/definitions/search-violations.tool
|
|
4
|
+
*/
|
|
5
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { getEchoService } from '../../../services/echo/echo-service.js';
|
|
8
|
+
export const searchViolationsTool = tool('epa_search_violations', {
|
|
9
|
+
title: 'Search EPA Enforcement Cases',
|
|
10
|
+
description: "Search EPA civil and criminal enforcement cases by state, regulatory program, or date range. Returns case identifier, regulated facility name and Registry ID, programs involved, penalty assessed, settlement date, and case type. Designed for area-level violation discovery — for a single known facility's enforcement history use epa_get_facility instead. At least one of state or zip_code is required.",
|
|
11
|
+
annotations: { readOnlyHint: true, openWorldHint: true, idempotentHint: true },
|
|
12
|
+
input: z.object({
|
|
13
|
+
state: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('2-letter US state abbreviation to filter cases (e.g. "TX", "CA")'),
|
|
17
|
+
zip_code: z.string().optional().describe('5-digit ZIP code to filter cases near that area'),
|
|
18
|
+
program: z
|
|
19
|
+
.enum(['CAA', 'CWA', 'RCRA', 'SDWA', 'CERCLA', 'FIFRA', 'TSCA'])
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Filter by regulatory program: CAA=Clean Air Act, CWA=Clean Water Act, RCRA=hazardous waste, SDWA=drinking water, CERCLA=Superfund, FIFRA=pesticides, TSCA=toxic substances'),
|
|
22
|
+
case_type: z
|
|
23
|
+
.enum(['civil', 'criminal', 'all'])
|
|
24
|
+
.default('all')
|
|
25
|
+
.describe('Filter by case type: civil (regulatory violations), criminal (willful violations), or all'),
|
|
26
|
+
date_filed_start: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Start date for case filing date range (ISO 8601: YYYY-MM-DD)'),
|
|
30
|
+
date_filed_end: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('End date for case filing date range (ISO 8601: YYYY-MM-DD)'),
|
|
34
|
+
limit: z
|
|
35
|
+
.number()
|
|
36
|
+
.int()
|
|
37
|
+
.min(1)
|
|
38
|
+
.max(100)
|
|
39
|
+
.default(50)
|
|
40
|
+
.describe('Maximum number of cases to return (1–100)'),
|
|
41
|
+
}),
|
|
42
|
+
output: z.object({
|
|
43
|
+
cases: z
|
|
44
|
+
.array(z
|
|
45
|
+
.object({
|
|
46
|
+
caseId: z.string().optional().describe('ECHO enforcement case identifier'),
|
|
47
|
+
caseName: z.string().optional().describe('Case name or docket number'),
|
|
48
|
+
facilityName: z.string().optional().describe('Regulated facility name'),
|
|
49
|
+
registryId: z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('EPA Registry ID — use with epa_get_facility for full compliance profile'),
|
|
53
|
+
programsViolated: z
|
|
54
|
+
.string()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe('Regulatory programs cited in the case'),
|
|
57
|
+
caseType: z.string().optional().describe('Case type (civil or criminal)'),
|
|
58
|
+
penaltyAssessedInDollars: z
|
|
59
|
+
.number()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Total penalty assessed in dollars'),
|
|
62
|
+
settlementDate: z.string().optional().describe('Settlement or resolution date'),
|
|
63
|
+
filedDate: z.string().optional().describe('Date the enforcement case was filed'),
|
|
64
|
+
state: z.string().optional().describe('State where the facility is located'),
|
|
65
|
+
})
|
|
66
|
+
.describe('EPA enforcement case record with facility, penalty, and program details'))
|
|
67
|
+
.describe('Matching enforcement cases'),
|
|
68
|
+
totalCount: z.number().describe('Total cases matched before the limit was applied'),
|
|
69
|
+
message: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('Recovery hint when no cases are found — echoes filters and suggests broadening. Absent when cases are returned.'),
|
|
73
|
+
}),
|
|
74
|
+
errors: [
|
|
75
|
+
{
|
|
76
|
+
reason: 'no_geographic_filter',
|
|
77
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
78
|
+
when: 'Neither state nor zip_code was provided.',
|
|
79
|
+
recovery: 'Provide at least one of state or zip_code to scope the enforcement case search.',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
async handler(input, ctx) {
|
|
83
|
+
if (!input.state?.trim() && !input.zip_code?.trim()) {
|
|
84
|
+
throw ctx.fail('no_geographic_filter', 'At least one of state or zip_code is required to search enforcement cases.', {
|
|
85
|
+
...ctx.recoveryFor('no_geographic_filter'),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
ctx.log.info('epa_search_violations', {
|
|
89
|
+
state: input.state,
|
|
90
|
+
program: input.program,
|
|
91
|
+
caseType: input.case_type,
|
|
92
|
+
});
|
|
93
|
+
const state = input.state?.trim();
|
|
94
|
+
const zipCode = input.zip_code?.trim();
|
|
95
|
+
const dateFiledStart = input.date_filed_start?.trim();
|
|
96
|
+
const dateFiledEnd = input.date_filed_end?.trim();
|
|
97
|
+
const { cases, totalCount } = await getEchoService().searchViolations({
|
|
98
|
+
...(state && { state }),
|
|
99
|
+
...(zipCode && { zipCode }),
|
|
100
|
+
...(input.program && { program: input.program }),
|
|
101
|
+
...(input.case_type && { caseType: input.case_type }),
|
|
102
|
+
...(dateFiledStart && { dateFiledStart }),
|
|
103
|
+
...(dateFiledEnd && { dateFiledEnd }),
|
|
104
|
+
limit: input.limit,
|
|
105
|
+
}, ctx);
|
|
106
|
+
ctx.log.info('epa_search_violations completed', { count: cases.length, totalCount });
|
|
107
|
+
if (cases.length === 0) {
|
|
108
|
+
const parts = [];
|
|
109
|
+
if (input.state)
|
|
110
|
+
parts.push(`state="${input.state}"`);
|
|
111
|
+
if (input.zip_code)
|
|
112
|
+
parts.push(`zip_code="${input.zip_code}"`);
|
|
113
|
+
if (input.program)
|
|
114
|
+
parts.push(`program="${input.program}"`);
|
|
115
|
+
if (input.case_type && input.case_type !== 'all')
|
|
116
|
+
parts.push(`case_type="${input.case_type}"`);
|
|
117
|
+
return {
|
|
118
|
+
cases: [],
|
|
119
|
+
totalCount: 0,
|
|
120
|
+
message: `No enforcement cases matched: ${parts.join(', ')}. Try removing program or date filters, or expanding the geographic area.`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return { cases, totalCount };
|
|
124
|
+
},
|
|
125
|
+
format: (result) => {
|
|
126
|
+
const lines = [];
|
|
127
|
+
lines.push(`## EPA Enforcement Cases`);
|
|
128
|
+
lines.push(`**Total Found:** ${result.totalCount} | **Returned:** ${result.cases.length}`);
|
|
129
|
+
if (result.message)
|
|
130
|
+
lines.push(`\n> ${result.message}`);
|
|
131
|
+
for (const c of result.cases) {
|
|
132
|
+
lines.push(`\n### ${c.caseName ?? c.caseId ?? 'Unnamed Case'}`);
|
|
133
|
+
if (c.caseId)
|
|
134
|
+
lines.push(`**Case ID:** ${c.caseId}`);
|
|
135
|
+
if (c.facilityName)
|
|
136
|
+
lines.push(`**Facility:** ${c.facilityName}`);
|
|
137
|
+
if (c.registryId)
|
|
138
|
+
lines.push(`**Registry ID:** ${c.registryId}`);
|
|
139
|
+
if (c.state)
|
|
140
|
+
lines.push(`**State:** ${c.state}`);
|
|
141
|
+
if (c.programsViolated)
|
|
142
|
+
lines.push(`**Programs:** ${c.programsViolated}`);
|
|
143
|
+
if (c.caseType)
|
|
144
|
+
lines.push(`**Type:** ${c.caseType}`);
|
|
145
|
+
if (c.penaltyAssessedInDollars !== undefined) {
|
|
146
|
+
lines.push(`**Penalty:** $${c.penaltyAssessedInDollars.toLocaleString()}`);
|
|
147
|
+
}
|
|
148
|
+
if (c.filedDate)
|
|
149
|
+
lines.push(`**Filed:** ${c.filedDate}`);
|
|
150
|
+
if (c.settlementDate)
|
|
151
|
+
lines.push(`**Settlement:** ${c.settlementDate}`);
|
|
152
|
+
}
|
|
153
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
//# sourceMappingURL=search-violations.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-violations.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-violations.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tool for searching drinking water systems (SDWIS) by state or ZIP code.
|
|
3
|
+
* @module mcp-server/tools/definitions/search-water-systems.tool
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
export declare const searchWaterSystemsTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
|
+
state: z.ZodOptional<z.ZodString>;
|
|
9
|
+
zip_code: z.ZodOptional<z.ZodString>;
|
|
10
|
+
has_violation: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
pws_type: z.ZodOptional<z.ZodEnum<{
|
|
12
|
+
community: "community";
|
|
13
|
+
"non-transient": "non-transient";
|
|
14
|
+
transient: "transient";
|
|
15
|
+
}>>;
|
|
16
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
17
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
18
|
+
systems: z.ZodArray<z.ZodObject<{
|
|
19
|
+
pwsid: z.ZodString;
|
|
20
|
+
name: z.ZodString;
|
|
21
|
+
state: z.ZodOptional<z.ZodString>;
|
|
22
|
+
city: z.ZodOptional<z.ZodString>;
|
|
23
|
+
zip: z.ZodOptional<z.ZodString>;
|
|
24
|
+
type: z.ZodOptional<z.ZodString>;
|
|
25
|
+
populationServed: z.ZodOptional<z.ZodNumber>;
|
|
26
|
+
primarySourceCode: z.ZodOptional<z.ZodString>;
|
|
27
|
+
hasViolation: z.ZodOptional<z.ZodBoolean>;
|
|
28
|
+
isActive: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
}, z.core.$strip>>;
|
|
30
|
+
totalCount: z.ZodNumber;
|
|
31
|
+
message: z.ZodOptional<z.ZodString>;
|
|
32
|
+
}, z.core.$strip>, readonly [{
|
|
33
|
+
readonly reason: "no_geographic_filter";
|
|
34
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
35
|
+
readonly when: "Neither state nor zip_code was provided.";
|
|
36
|
+
readonly recovery: "Provide at least one of state or zip_code to search drinking water systems.";
|
|
37
|
+
}]>;
|
|
38
|
+
//# sourceMappingURL=search-water-systems.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-water-systems.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-water-systems.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGjE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyJjC,CAAC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tool for searching drinking water systems (SDWIS) by state or ZIP code.
|
|
3
|
+
* @module mcp-server/tools/definitions/search-water-systems.tool
|
|
4
|
+
*/
|
|
5
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { getDmapService } from '../../../services/dmap/dmap-service.js';
|
|
8
|
+
export const searchWaterSystemsTool = tool('epa_search_water_systems', {
|
|
9
|
+
title: 'Search Drinking Water Systems',
|
|
10
|
+
description: 'Search drinking water systems (SDWIS) by state or ZIP code. Returns system name, PWSID, population served, primary water source, and active violation status. Use to identify community water systems with current or recent compliance violations. At least one of state or zip_code is required.',
|
|
11
|
+
annotations: { readOnlyHint: true, openWorldHint: true, idempotentHint: true },
|
|
12
|
+
input: z.object({
|
|
13
|
+
state: z.string().optional().describe('2-letter US state abbreviation (e.g. "WA", "CA")'),
|
|
14
|
+
zip_code: z.string().optional().describe('5-digit ZIP code to search within'),
|
|
15
|
+
has_violation: z
|
|
16
|
+
.boolean()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('When true, return only systems with active violations'),
|
|
19
|
+
pws_type: z
|
|
20
|
+
.enum(['community', 'non-transient', 'transient'])
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Filter by public water system type: community=permanent residences, non-transient=regular non-residents (schools, businesses), transient=occasional users (campgrounds)'),
|
|
23
|
+
limit: z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.min(1)
|
|
27
|
+
.max(200)
|
|
28
|
+
.default(50)
|
|
29
|
+
.describe('Maximum number of systems to return (1–200)'),
|
|
30
|
+
}),
|
|
31
|
+
output: z.object({
|
|
32
|
+
systems: z
|
|
33
|
+
.array(z
|
|
34
|
+
.object({
|
|
35
|
+
pwsid: z.string().describe('Public Water System ID (PWSID) — unique system identifier'),
|
|
36
|
+
name: z.string().describe('Water system name'),
|
|
37
|
+
state: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('2-letter state abbreviation (primacy agency code)'),
|
|
41
|
+
city: z.string().optional().describe('City served by this water system'),
|
|
42
|
+
zip: z.string().optional().describe('ZIP code'),
|
|
43
|
+
type: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('PWS type code: CWS=Community, NTNCWS=Non-transient non-community, TNCWS=Transient non-community'),
|
|
47
|
+
populationServed: z
|
|
48
|
+
.number()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe('Estimated population served by this water system'),
|
|
51
|
+
primarySourceCode: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('Primary water source code (e.g. GW=groundwater, SW=surface water)'),
|
|
55
|
+
hasViolation: z
|
|
56
|
+
.boolean()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('Whether this system has an active compliance violation on record'),
|
|
59
|
+
isActive: z
|
|
60
|
+
.boolean()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('Whether this water system is currently active'),
|
|
63
|
+
})
|
|
64
|
+
.describe('Public water system record with PWSID, population served, and violation status'))
|
|
65
|
+
.describe('Matching public water systems'),
|
|
66
|
+
totalCount: z.number().describe('Number of systems returned'),
|
|
67
|
+
message: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('Recovery hint when no systems are found. Absent when systems are returned.'),
|
|
71
|
+
}),
|
|
72
|
+
errors: [
|
|
73
|
+
{
|
|
74
|
+
reason: 'no_geographic_filter',
|
|
75
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
76
|
+
when: 'Neither state nor zip_code was provided.',
|
|
77
|
+
recovery: 'Provide at least one of state or zip_code to search drinking water systems.',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
async handler(input, ctx) {
|
|
81
|
+
if (!input.state?.trim() && !input.zip_code?.trim()) {
|
|
82
|
+
throw ctx.fail('no_geographic_filter', 'At least one of state or zip_code is required.', {
|
|
83
|
+
...ctx.recoveryFor('no_geographic_filter'),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
ctx.log.info('epa_search_water_systems', {
|
|
87
|
+
state: input.state,
|
|
88
|
+
zip: input.zip_code,
|
|
89
|
+
hasViolation: input.has_violation,
|
|
90
|
+
type: input.pws_type,
|
|
91
|
+
});
|
|
92
|
+
const state = input.state?.trim();
|
|
93
|
+
const zipCode = input.zip_code?.trim();
|
|
94
|
+
const systems = await getDmapService().searchWaterSystems({
|
|
95
|
+
...(state && { state }),
|
|
96
|
+
...(zipCode && { zipCode }),
|
|
97
|
+
...(input.has_violation && { hasViolation: input.has_violation }),
|
|
98
|
+
...(input.pws_type && { pwsType: input.pws_type }),
|
|
99
|
+
limit: input.limit,
|
|
100
|
+
}, ctx);
|
|
101
|
+
ctx.log.info('epa_search_water_systems completed', { count: systems.length });
|
|
102
|
+
if (systems.length === 0) {
|
|
103
|
+
const parts = [];
|
|
104
|
+
if (input.state)
|
|
105
|
+
parts.push(`state="${input.state}"`);
|
|
106
|
+
if (input.zip_code)
|
|
107
|
+
parts.push(`zip_code="${input.zip_code}"`);
|
|
108
|
+
if (input.has_violation)
|
|
109
|
+
parts.push('has_violation=true');
|
|
110
|
+
if (input.pws_type)
|
|
111
|
+
parts.push(`pws_type="${input.pws_type}"`);
|
|
112
|
+
return {
|
|
113
|
+
systems: [],
|
|
114
|
+
totalCount: 0,
|
|
115
|
+
message: `No water systems found matching: ${parts.join(', ')}. Try removing the violation or type filter, or checking the state abbreviation.`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return { systems, totalCount: systems.length };
|
|
119
|
+
},
|
|
120
|
+
format: (result) => {
|
|
121
|
+
const lines = [];
|
|
122
|
+
lines.push(`## Drinking Water Systems`);
|
|
123
|
+
lines.push(`**Found:** ${result.totalCount}`);
|
|
124
|
+
if (result.message)
|
|
125
|
+
lines.push(`\n> ${result.message}`);
|
|
126
|
+
for (const s of result.systems) {
|
|
127
|
+
const violationFlag = s.hasViolation === true ? ' ⚠️ VIOLATION' : '';
|
|
128
|
+
lines.push(`\n### ${s.name}${violationFlag}`);
|
|
129
|
+
lines.push(`**PWSID:** ${s.pwsid}`);
|
|
130
|
+
const location = [s.city, s.state, s.zip].filter(Boolean).join(', ');
|
|
131
|
+
if (location)
|
|
132
|
+
lines.push(`**Location:** ${location}`);
|
|
133
|
+
if (s.type)
|
|
134
|
+
lines.push(`**Type:** ${s.type}`);
|
|
135
|
+
if (s.populationServed !== undefined)
|
|
136
|
+
lines.push(`**Population Served:** ${s.populationServed.toLocaleString()}`);
|
|
137
|
+
if (s.primarySourceCode)
|
|
138
|
+
lines.push(`**Primary Source:** ${s.primarySourceCode}`);
|
|
139
|
+
if (s.hasViolation !== undefined)
|
|
140
|
+
lines.push(`**Active Violation:** ${s.hasViolation ? 'Yes' : 'No'}`);
|
|
141
|
+
if (s.isActive !== undefined)
|
|
142
|
+
lines.push(`**Active:** ${s.isActive ? 'Yes' : 'No'}`);
|
|
143
|
+
}
|
|
144
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=search-water-systems.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-water-systems.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-water-systems.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
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.
|
|
5
|
+
* @module services/airnow/airnow-service
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@cyanheads/mcp-ts-core';
|
|
8
|
+
import type { AppConfig } from '@cyanheads/mcp-ts-core/config';
|
|
9
|
+
import type { StorageService } from '@cyanheads/mcp-ts-core/storage';
|
|
10
|
+
import type { AirQualityResult } from './types.js';
|
|
11
|
+
export declare class AirNowService {
|
|
12
|
+
private readonly baseUrl;
|
|
13
|
+
private readonly apiKey;
|
|
14
|
+
constructor(_config: AppConfig, _storage: StorageService);
|
|
15
|
+
private buildUrl;
|
|
16
|
+
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[]>;
|
|
42
|
+
}
|
|
43
|
+
export declare function initAirNowService(config: AppConfig, storage: StorageService): void;
|
|
44
|
+
export declare function getAirNowService(): AirNowService;
|
|
45
|
+
//# sourceMappingURL=airnow-service.d.ts.map
|
|
@@ -0,0 +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;AAqCpE,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc;IAMxD,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,QAAQ;YASF,cAAc;IAwC5B,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;IAS9B,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;IAa9B,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;IAa9B,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;CAiB/B;AAMD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAElF;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAKhD"}
|
|
@@ -0,0 +1,160 @@
|
|
|
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.
|
|
5
|
+
* @module services/airnow/airnow-service
|
|
6
|
+
*/
|
|
7
|
+
import { serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
import { httpErrorFromResponse, withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
9
|
+
import { getServerConfig } from '../../config/server-config.js';
|
|
10
|
+
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
|
+
if (!grouped.has(key)) {
|
|
18
|
+
grouped.set(key, {
|
|
19
|
+
...(r.ReportingArea && { reportingArea: r.ReportingArea.trim() }),
|
|
20
|
+
...(r.StateCode && { stateCode: r.StateCode }),
|
|
21
|
+
...(r.Latitude !== undefined && { latitude: r.Latitude }),
|
|
22
|
+
...(r.Longitude !== undefined && { longitude: r.Longitude }),
|
|
23
|
+
...(r.DateObserved && { dateObserved: r.DateObserved.trim() }),
|
|
24
|
+
...(r.HourObserved !== undefined && { hourObserved: r.HourObserved }),
|
|
25
|
+
...(r.LocalTimeZone && { localTimeZone: r.LocalTimeZone }),
|
|
26
|
+
readings: [],
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const entry = grouped.get(key);
|
|
30
|
+
if (r.ParameterName && r.AQI !== undefined) {
|
|
31
|
+
entry.readings.push({
|
|
32
|
+
parameterName: r.ParameterName,
|
|
33
|
+
aqi: r.AQI,
|
|
34
|
+
...(r.Category?.Number !== undefined && { categoryNumber: r.Category.Number }),
|
|
35
|
+
...(r.Category?.Name && { categoryName: r.Category.Name }),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return Array.from(grouped.values());
|
|
40
|
+
}
|
|
41
|
+
export class AirNowService {
|
|
42
|
+
baseUrl;
|
|
43
|
+
apiKey;
|
|
44
|
+
constructor(_config, _storage) {
|
|
45
|
+
const serverConfig = getServerConfig();
|
|
46
|
+
this.baseUrl = serverConfig.airNowBaseUrl;
|
|
47
|
+
this.apiKey = serverConfig.airNowApiKey;
|
|
48
|
+
}
|
|
49
|
+
buildUrl(path, params) {
|
|
50
|
+
const url = new URL(`${this.baseUrl}/${path}`);
|
|
51
|
+
url.searchParams.set('format', 'application/json');
|
|
52
|
+
url.searchParams.set('API_KEY', this.apiKey);
|
|
53
|
+
for (const [key, val] of Object.entries(params)) {
|
|
54
|
+
if (val !== undefined && val !== '') {
|
|
55
|
+
url.searchParams.set(key, String(val));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return url.toString();
|
|
59
|
+
}
|
|
60
|
+
cacheKey(kind, params) {
|
|
61
|
+
const stable = Object.entries(params)
|
|
62
|
+
.filter(([, v]) => v !== undefined)
|
|
63
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
64
|
+
.map(([k, v]) => `${k}-${v}`)
|
|
65
|
+
.join('_');
|
|
66
|
+
return `airnow:${kind}:${stable}`;
|
|
67
|
+
}
|
|
68
|
+
async fetchWithCache(url, cacheKey, ctx) {
|
|
69
|
+
// Try cache first
|
|
70
|
+
const cached = await ctx.state.get(cacheKey);
|
|
71
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
72
|
+
ctx.log.debug('AirNow cache hit', { cacheKey });
|
|
73
|
+
return cached.data;
|
|
74
|
+
}
|
|
75
|
+
const data = await withRetry(async () => {
|
|
76
|
+
const response = await fetch(url, { signal: ctx.signal });
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw await httpErrorFromResponse(response, { service: 'AirNow', data: { url } });
|
|
79
|
+
}
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
82
|
+
throw serviceUnavailable('AirNow API returned HTML instead of JSON — likely rate-limited.', { url });
|
|
83
|
+
}
|
|
84
|
+
const parsed = JSON.parse(text);
|
|
85
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
86
|
+
}, {
|
|
87
|
+
operation: 'AirNowService.fetch',
|
|
88
|
+
baseDelayMs: 2000,
|
|
89
|
+
signal: ctx.signal,
|
|
90
|
+
});
|
|
91
|
+
// Store in cache
|
|
92
|
+
await ctx.state.set(cacheKey, { data, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
93
|
+
return data;
|
|
94
|
+
}
|
|
95
|
+
/** Get current air quality by ZIP code. */
|
|
96
|
+
async getCurrentByZip(params, ctx) {
|
|
97
|
+
const qparams = { zipCode: params.zipCode, distance: params.distanceMiles ?? 25 };
|
|
98
|
+
const url = this.buildUrl('observation/zipCode/current/', qparams);
|
|
99
|
+
const ck = this.cacheKey('current:zip', qparams);
|
|
100
|
+
ctx.log.debug('AirNow current by ZIP', { zipCode: params.zipCode });
|
|
101
|
+
const records = await this.fetchWithCache(url, ck, ctx);
|
|
102
|
+
return normalizeRecords(records);
|
|
103
|
+
}
|
|
104
|
+
/** Get current air quality by lat/lng. */
|
|
105
|
+
async getCurrentByLatLng(params, ctx) {
|
|
106
|
+
const qparams = {
|
|
107
|
+
latitude: params.latitude,
|
|
108
|
+
longitude: params.longitude,
|
|
109
|
+
distance: params.distanceMiles ?? 25,
|
|
110
|
+
};
|
|
111
|
+
const url = this.buildUrl('observation/latLong/current/', qparams);
|
|
112
|
+
const ck = this.cacheKey('current:latlng', qparams);
|
|
113
|
+
ctx.log.debug('AirNow current by lat/lng', { lat: params.latitude, lng: params.longitude });
|
|
114
|
+
const records = await this.fetchWithCache(url, ck, ctx);
|
|
115
|
+
return normalizeRecords(records);
|
|
116
|
+
}
|
|
117
|
+
/** Get air quality forecast by ZIP code. */
|
|
118
|
+
async getForecastByZip(params, ctx) {
|
|
119
|
+
const qparams = {
|
|
120
|
+
zipCode: params.zipCode,
|
|
121
|
+
date: params.date,
|
|
122
|
+
distance: params.distanceMiles ?? 25,
|
|
123
|
+
};
|
|
124
|
+
const url = this.buildUrl('forecast/zipCode/', qparams);
|
|
125
|
+
const ck = this.cacheKey('forecast:zip', qparams);
|
|
126
|
+
ctx.log.debug('AirNow forecast by ZIP', { zipCode: params.zipCode, date: params.date });
|
|
127
|
+
const records = await this.fetchWithCache(url, ck, ctx);
|
|
128
|
+
return normalizeRecords(records);
|
|
129
|
+
}
|
|
130
|
+
/** Get air quality forecast by lat/lng. */
|
|
131
|
+
async getForecastByLatLng(params, ctx) {
|
|
132
|
+
const qparams = {
|
|
133
|
+
latitude: params.latitude,
|
|
134
|
+
longitude: params.longitude,
|
|
135
|
+
date: params.date,
|
|
136
|
+
distance: params.distanceMiles ?? 25,
|
|
137
|
+
};
|
|
138
|
+
const url = this.buildUrl('forecast/latLong/', qparams);
|
|
139
|
+
const ck = this.cacheKey('forecast:latlng', qparams);
|
|
140
|
+
ctx.log.debug('AirNow forecast by lat/lng', {
|
|
141
|
+
lat: params.latitude,
|
|
142
|
+
lng: params.longitude,
|
|
143
|
+
date: params.date,
|
|
144
|
+
});
|
|
145
|
+
const records = await this.fetchWithCache(url, ck, ctx);
|
|
146
|
+
return normalizeRecords(records);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// --- Init/accessor pattern ---
|
|
150
|
+
let _service;
|
|
151
|
+
export function initAirNowService(config, storage) {
|
|
152
|
+
_service = new AirNowService(config, storage);
|
|
153
|
+
}
|
|
154
|
+
export function getAirNowService() {
|
|
155
|
+
if (!_service) {
|
|
156
|
+
throw new Error('AirNowService not initialized — call initAirNowService() in setup()');
|
|
157
|
+
}
|
|
158
|
+
return _service;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=airnow-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"airnow-service.js","sourceRoot":"","sources":["../../../src/services/airnow/airnow-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview AirNow API response types and normalized domain types.
|
|
3
|
+
* @module services/airnow/types
|
|
4
|
+
*/
|
|
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;
|
|
20
|
+
}
|
|
21
|
+
/** Normalized AQI reading for a single pollutant. */
|
|
22
|
+
export interface AqiReading {
|
|
23
|
+
aqi: number;
|
|
24
|
+
categoryName?: string;
|
|
25
|
+
categoryNumber?: number;
|
|
26
|
+
parameterName: string;
|
|
27
|
+
}
|
|
28
|
+
/** Normalized AirNow observation/forecast result. */
|
|
29
|
+
export interface AirQualityResult {
|
|
30
|
+
dateObserved?: string;
|
|
31
|
+
hourObserved?: number;
|
|
32
|
+
latitude?: number;
|
|
33
|
+
localTimeZone?: string;
|
|
34
|
+
longitude?: number;
|
|
35
|
+
readings: AqiReading[];
|
|
36
|
+
reportingArea?: string;
|
|
37
|
+
stateCode?: string;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/airnow/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|