@usa-open-data-connectors/usa-sources 0.1.1 → 0.1.3

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 (52) hide show
  1. package/dist/btsTransportationStats.d.ts +87 -0
  2. package/dist/btsTransportationStats.d.ts.map +1 -0
  3. package/dist/btsTransportationStats.js +132 -0
  4. package/dist/btsTransportationStats.js.map +1 -0
  5. package/dist/cdcSocrataCatalogue.d.ts +87 -0
  6. package/dist/cdcSocrataCatalogue.d.ts.map +1 -0
  7. package/dist/cdcSocrataCatalogue.js +132 -0
  8. package/dist/cdcSocrataCatalogue.js.map +1 -0
  9. package/dist/clinicalTrialsStudies.d.ts +92 -0
  10. package/dist/clinicalTrialsStudies.d.ts.map +1 -0
  11. package/dist/clinicalTrialsStudies.js +149 -0
  12. package/dist/clinicalTrialsStudies.js.map +1 -0
  13. package/dist/epaEnvirofactsFacilities.d.ts +94 -0
  14. package/dist/epaEnvirofactsFacilities.d.ts.map +1 -0
  15. package/dist/epaEnvirofactsFacilities.js +142 -0
  16. package/dist/epaEnvirofactsFacilities.js.map +1 -0
  17. package/dist/fdicBankDirectory.d.ts +78 -0
  18. package/dist/fdicBankDirectory.d.ts.map +1 -0
  19. package/dist/fdicBankDirectory.js +124 -0
  20. package/dist/fdicBankDirectory.js.map +1 -0
  21. package/dist/healthdataSocrataCatalogue.d.ts +87 -0
  22. package/dist/healthdataSocrataCatalogue.d.ts.map +1 -0
  23. package/dist/healthdataSocrataCatalogue.js +132 -0
  24. package/dist/healthdataSocrataCatalogue.js.map +1 -0
  25. package/dist/index.d.ts +44 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +22 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/ncbiPubmedSearch.d.ts +71 -0
  30. package/dist/ncbiPubmedSearch.d.ts.map +1 -0
  31. package/dist/ncbiPubmedSearch.js +128 -0
  32. package/dist/ncbiPubmedSearch.js.map +1 -0
  33. package/dist/nwsPointForecast.d.ts +99 -0
  34. package/dist/nwsPointForecast.d.ts.map +1 -0
  35. package/dist/nwsPointForecast.js +146 -0
  36. package/dist/nwsPointForecast.js.map +1 -0
  37. package/dist/registry.d.ts.map +1 -1
  38. package/dist/registry.js +22 -0
  39. package/dist/registry.js.map +1 -1
  40. package/dist/secEdgarFilings.d.ts +103 -0
  41. package/dist/secEdgarFilings.d.ts.map +1 -0
  42. package/dist/secEdgarFilings.js +214 -0
  43. package/dist/secEdgarFilings.js.map +1 -0
  44. package/dist/treasuryDebtToPenny.d.ts +90 -0
  45. package/dist/treasuryDebtToPenny.d.ts.map +1 -0
  46. package/dist/treasuryDebtToPenny.js +170 -0
  47. package/dist/treasuryDebtToPenny.js.map +1 -0
  48. package/dist/usaspendingAgencies.d.ts +78 -0
  49. package/dist/usaspendingAgencies.d.ts.map +1 -0
  50. package/dist/usaspendingAgencies.js +121 -0
  51. package/dist/usaspendingAgencies.js.map +1 -0
  52. package/package.json +10 -2
@@ -0,0 +1,149 @@
1
+ import { z } from 'zod';
2
+ import { UsSourceApiError, UsSourceParseError } from './errors.js';
3
+ import { readFixtureJson } from './fixtures.js';
4
+ /** Adapter id for the ClinicalTrials.gov studies source. */
5
+ export const CLINICALTRIALS_STUDIES_SOURCE_ID = 'clinicaltrials-studies';
6
+ /** Base URL for the ClinicalTrials.gov studies API. */
7
+ export const CLINICALTRIALS_STUDIES_API_BASE = 'https://clinicaltrials.gov/api/v2/studies';
8
+ /** Page size for one request. */
9
+ export const CLINICALTRIALS_STUDIES_PAGE_SIZE = 10;
10
+ /** Committed snapshot, so a build works when the ClinicalTrials host is unreachable. */
11
+ const CLINICALTRIALS_STUDIES_FIXTURE_FILENAME = 'clinicaltrials-studies-2026-10-05.json';
12
+ const CLINICALTRIALS_STUDY_SCHEMA = z.object({
13
+ protocolSection: z
14
+ .object({
15
+ identificationModule: z
16
+ .object({ nctId: z.string().optional(), briefTitle: z.string().optional() })
17
+ .optional(),
18
+ statusModule: z
19
+ .object({
20
+ overallStatus: z.string().optional(),
21
+ startDateStruct: z.object({ date: z.string().optional() }).optional(),
22
+ lastUpdatePostDateStruct: z.object({ date: z.string().optional() }).optional(),
23
+ })
24
+ .optional(),
25
+ sponsorCollaboratorsModule: z
26
+ .object({ leadSponsor: z.object({ name: z.string().optional() }).optional() })
27
+ .optional(),
28
+ conditionsModule: z.object({ conditions: z.array(z.string()).optional() }).optional(),
29
+ designModule: z
30
+ .object({
31
+ studyType: z.string().optional(),
32
+ phases: z.array(z.string()).optional(),
33
+ enrollmentInfo: z
34
+ .object({ count: z.number().optional(), type: z.string().optional() })
35
+ .optional(),
36
+ })
37
+ .optional(),
38
+ })
39
+ .optional(),
40
+ });
41
+ const CLINICALTRIALS_PAYLOAD_SCHEMA = z.object({
42
+ studies: z.array(CLINICALTRIALS_STUDY_SCHEMA).optional(),
43
+ nextPageToken: z.string().optional(),
44
+ });
45
+ /**
46
+ * Builds the request URL for one page of studies.
47
+ *
48
+ * The endpoint is keyless. The default page size is small on purpose: one
49
+ * study carries a deep protocol record, so a caller reading many pages should
50
+ * pass the page token from the previous response.
51
+ *
52
+ * @param options - an optional page size
53
+ * @returns the full request URL
54
+ */
55
+ export function buildClinicalTrialsStudiesUrl(options) {
56
+ const params = new URLSearchParams({
57
+ pageSize: String(options?.pageSize ?? CLINICALTRIALS_STUDIES_PAGE_SIZE),
58
+ });
59
+ return `${CLINICALTRIALS_STUDIES_API_BASE}?${params.toString()}`;
60
+ }
61
+ /** Counts studies per recruitment status, most studies first, ties by name. */
62
+ function countClinicalTrialStatuses(studies) {
63
+ const counts = new Map();
64
+ for (const study of studies) {
65
+ counts.set(study.overallStatus, (counts.get(study.overallStatus) ?? 0) + 1);
66
+ }
67
+ return [...counts.entries()]
68
+ .sort(([leftStatus, leftCount], [rightStatus, rightCount]) => leftCount === rightCount ? leftStatus.localeCompare(rightStatus) : rightCount - leftCount)
69
+ .map(([status, studyCount]) => ({ status, studyCount }));
70
+ }
71
+ /**
72
+ * Parses one page of studies into the fields a story reads.
73
+ *
74
+ * A study without a registry id or a brief title is a changed shape and stops
75
+ * the parse. Optional protocol blocks are read when present and left null
76
+ * when the registry did not carry them.
77
+ *
78
+ * @param payload - the JSON body from the ClinicalTrials.gov studies API
79
+ * @returns the studies in the page, the status counts, and the next page token
80
+ */
81
+ export function parseClinicalTrialsStudiesPayload(payload) {
82
+ const parsed = CLINICALTRIALS_PAYLOAD_SCHEMA.safeParse(payload);
83
+ if (!parsed.success) {
84
+ throw new UsSourceParseError(CLINICALTRIALS_STUDIES_SOURCE_ID, parsed.error.message);
85
+ }
86
+ const rows = parsed.data.studies ?? [];
87
+ if (rows.length === 0) {
88
+ throw new UsSourceParseError(CLINICALTRIALS_STUDIES_SOURCE_ID, 'The response carried no studies');
89
+ }
90
+ const studies = rows.map((row) => {
91
+ const protocol = row.protocolSection;
92
+ const nctId = protocol?.identificationModule?.nctId;
93
+ const briefTitle = protocol?.identificationModule?.briefTitle;
94
+ if (nctId === undefined || briefTitle === undefined) {
95
+ throw new UsSourceParseError(CLINICALTRIALS_STUDIES_SOURCE_ID, 'A study arrived without its registry id or brief title');
96
+ }
97
+ const status = protocol?.statusModule;
98
+ const design = protocol?.designModule;
99
+ return {
100
+ nctId,
101
+ briefTitle,
102
+ overallStatus: status?.overallStatus ?? '',
103
+ studyType: design?.studyType ?? null,
104
+ phases: design?.phases ?? [],
105
+ conditions: protocol?.conditionsModule?.conditions ?? [],
106
+ leadSponsor: protocol?.sponsorCollaboratorsModule?.leadSponsor?.name ?? null,
107
+ enrollmentCount: design?.enrollmentInfo?.count ?? null,
108
+ enrollmentType: design?.enrollmentInfo?.type ?? null,
109
+ startDate: status?.startDateStruct?.date ?? null,
110
+ lastUpdatePostDate: status?.lastUpdatePostDateStruct?.date ?? null,
111
+ };
112
+ });
113
+ return {
114
+ studies,
115
+ studyCount: studies.length,
116
+ statusCounts: countClinicalTrialStatuses(studies),
117
+ nextPageToken: parsed.data.nextPageToken ?? null,
118
+ };
119
+ }
120
+ /**
121
+ * Reads one page of registered clinical studies.
122
+ *
123
+ * One keyless request covers the page. The response also carries a token for
124
+ * the next page of the same query, which the adapter keeps on the page it
125
+ * returns.
126
+ *
127
+ * @param options - an optional page size and fetch stub
128
+ * @returns the studies in the page, the status counts, and the next page token
129
+ */
130
+ export async function fetchClinicalTrialsStudies(options) {
131
+ const fetchImpl = options?.fetchImpl ?? globalThis.fetch;
132
+ const url = buildClinicalTrialsStudiesUrl(options?.pageSize === undefined ? {} : { pageSize: options.pageSize });
133
+ const response = await fetchImpl(url);
134
+ if (!response.ok) {
135
+ throw new UsSourceApiError(CLINICALTRIALS_STUDIES_SOURCE_ID, `HTTP ${response.status} reading the studies`);
136
+ }
137
+ return parseClinicalTrialsStudiesPayload(await response.json());
138
+ }
139
+ /** ClinicalTrials.gov registered studies, one row per study, keyless. */
140
+ export const clinicalTrialsStudiesAdapter = {
141
+ id: CLINICALTRIALS_STUDIES_SOURCE_ID,
142
+ name: 'ClinicalTrials.gov studies',
143
+ auth: 'none',
144
+ description: 'Registered clinical studies from ClinicalTrials.gov, one row per study, with the status, phase, lead sponsor, and enrollment.',
145
+ fetchLive: async (options) => fetchClinicalTrialsStudies(options?.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
146
+ parse: (payload) => parseClinicalTrialsStudiesPayload(payload),
147
+ loadFixture: () => parseClinicalTrialsStudiesPayload(readFixtureJson(CLINICALTRIALS_STUDIES_FIXTURE_FILENAME)),
148
+ };
149
+ //# sourceMappingURL=clinicalTrialsStudies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clinicalTrialsStudies.js","sourceRoot":"","sources":["../src/clinicalTrialsStudies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import type { UsDataAdapter } from './types.js';
2
+ /** Adapter id for the EPA Envirofacts facility source. */
3
+ export declare const EPA_ENVIROFACTS_FACILITIES_SOURCE_ID = "epa-envirofacts-facilities";
4
+ /** One Toxics Release Inventory facility from the Envirofacts service. */
5
+ export interface EpaEnvirofactsFacility {
6
+ /** The EPA's own TRI facility id. */
7
+ facilityId: string;
8
+ /** Facility name as the register holds it. */
9
+ facilityName: string;
10
+ /** Street address of the site. */
11
+ streetAddress: string;
12
+ /** City of the site. */
13
+ city: string;
14
+ /** County the site sits in, spelled the way the service spells it. */
15
+ countyName: string;
16
+ /** Two-letter state code the service reports for the site. */
17
+ stateAbbr: string;
18
+ /** Postal code of the site. */
19
+ postalCode: string;
20
+ /** EPA region number as the service reports it, for example "1". */
21
+ region: string;
22
+ /** True when the service marks the facility as closed. */
23
+ closed: boolean;
24
+ /** Parent company name, or null when the service reports "NA" or leaves it out. */
25
+ parentCompanyName: string | null;
26
+ }
27
+ /** One county and how many facilities in the page sit there. */
28
+ export interface EpaEnvirofactsCountyCount {
29
+ countyName: string;
30
+ facilityCount: number;
31
+ }
32
+ /** One page of facilities, folded for a story. */
33
+ export interface EpaEnvirofactsFacilityPage {
34
+ facilities: EpaEnvirofactsFacility[];
35
+ facilityCount: number;
36
+ /** Facilities the service does not mark as closed. */
37
+ openCount: number;
38
+ /** Facilities the service marks as closed. */
39
+ closedCount: number;
40
+ /** Counties the facilities in the page sit in, most facilities first. */
41
+ counties: EpaEnvirofactsCountyCount[];
42
+ }
43
+ /** Base URL for the EPA Envirofacts service. */
44
+ export declare const EPA_ENVIROFACTS_API_BASE = "https://data.epa.gov/efservice";
45
+ /** Envirofacts table that holds the Toxics Release Inventory facility records. */
46
+ export declare const EPA_ENVIROFACTS_TABLE = "TRI_FACILITY";
47
+ /** Envirofacts column the state filter reads. */
48
+ export declare const EPA_ENVIROFACTS_STATE_COLUMN = "STATE_ABBR";
49
+ /** State the default URL asks for. */
50
+ export declare const EPA_ENVIROFACTS_STATE_ABBR = "RI";
51
+ /** Row window one request reads, the first ten rows. */
52
+ export declare const EPA_ENVIROFACTS_ROW_WINDOW = "0:9";
53
+ /**
54
+ * Builds the request URL for one page of Toxics Release Inventory facilities.
55
+ *
56
+ * The endpoint is keyless, and the state segment does narrow the rows: the
57
+ * value sits directly after the column name, with no `equals` operator.
58
+ * Envirofacts accepts an `equals` segment without complaining but then
59
+ * returns the table's first rows instead of the ones asked for.
60
+ *
61
+ * @param options - an optional state code and row window
62
+ * @returns the full request URL
63
+ */
64
+ export declare function buildEpaEnvirofactsFacilitiesUrl(options?: {
65
+ stateAbbr?: string;
66
+ rowWindow?: string;
67
+ }): string;
68
+ /**
69
+ * Parses one response into the facilities in the page and its rollups.
70
+ *
71
+ * A row without a facility id, name, or state code fails the schema and stops
72
+ * the parse. An empty array is treated as a changed source, because the
73
+ * default query always returns rows.
74
+ *
75
+ * @param payload - the JSON body from the Envirofacts service
76
+ * @returns the facilities in the page, the open and closed counts, and the counties
77
+ */
78
+ export declare function parseEpaEnvirofactsFacilitiesPayload(payload: unknown): EpaEnvirofactsFacilityPage;
79
+ /**
80
+ * Reads one page of Toxics Release Inventory facilities from Envirofacts.
81
+ *
82
+ * One keyless request covers the page. The state segment in the URL narrows
83
+ * the rows, so the page holds facilities from the state that was asked for.
84
+ *
85
+ * @param options - an optional state code, row window, and fetch stub
86
+ * @returns the facilities in the page and its rollups
87
+ */
88
+ export declare function fetchEpaEnvirofactsFacilities(options?: {
89
+ stateAbbr?: string;
90
+ fetchImpl?: typeof globalThis.fetch;
91
+ }): Promise<EpaEnvirofactsFacilityPage>;
92
+ /** EPA Envirofacts Toxics Release Inventory facilities, keyless. */
93
+ export declare const epaEnvirofactsFacilitiesAdapter: UsDataAdapter<EpaEnvirofactsFacilityPage>;
94
+ //# sourceMappingURL=epaEnvirofactsFacilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"epaEnvirofactsFacilities.d.ts","sourceRoot":"","sources":["../src/epaEnvirofactsFacilities.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,0DAA0D;AAC1D,eAAO,MAAM,oCAAoC,+BAA+B,CAAC;AAEjF,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,mFAAmF;IACnF,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,gEAAgE;AAChE,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,kDAAkD;AAClD,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,sBAAsB,EAAE,CAAC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,QAAQ,EAAE,yBAAyB,EAAE,CAAC;CACvC;AAED,gDAAgD;AAChD,eAAO,MAAM,wBAAwB,mCAAmC,CAAC;AAEzE,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,iBAAiB,CAAC;AAEpD,iDAAiD;AACjD,eAAO,MAAM,4BAA4B,eAAe,CAAC;AAEzD,sCAAsC;AACtC,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,wDAAwD;AACxD,eAAO,MAAM,0BAA0B,QAAQ,CAAC;AAmDhD;;;;;;;;;;GAUG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,CAAC,EAAE;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,MAAM,CAIT;AAED;;;;;;;;;GASG;AACH,wBAAgB,oCAAoC,CAAC,OAAO,EAAE,OAAO,GAAG,0BAA0B,CAwCjG;AAED;;;;;;;;GAQG;AACH,wBAAsB,6BAA6B,CAAC,OAAO,CAAC,EAAE;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACrC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAatC;AAED,oEAAoE;AACpE,eAAO,MAAM,+BAA+B,EAAE,aAAa,CAAC,0BAA0B,CAarF,CAAC"}
@@ -0,0 +1,142 @@
1
+ import { z } from 'zod';
2
+ import { UsSourceApiError, UsSourceParseError } from './errors.js';
3
+ import { readFixtureJson } from './fixtures.js';
4
+ /** Adapter id for the EPA Envirofacts facility source. */
5
+ export const EPA_ENVIROFACTS_FACILITIES_SOURCE_ID = 'epa-envirofacts-facilities';
6
+ /** Base URL for the EPA Envirofacts service. */
7
+ export const EPA_ENVIROFACTS_API_BASE = 'https://data.epa.gov/efservice';
8
+ /** Envirofacts table that holds the Toxics Release Inventory facility records. */
9
+ export const EPA_ENVIROFACTS_TABLE = 'TRI_FACILITY';
10
+ /** Envirofacts column the state filter reads. */
11
+ export const EPA_ENVIROFACTS_STATE_COLUMN = 'STATE_ABBR';
12
+ /** State the default URL asks for. */
13
+ export const EPA_ENVIROFACTS_STATE_ABBR = 'RI';
14
+ /** Row window one request reads, the first ten rows. */
15
+ export const EPA_ENVIROFACTS_ROW_WINDOW = '0:9';
16
+ /** Committed snapshot, so a build works when the Envirofacts host is unreachable. */
17
+ const EPA_ENVIROFACTS_FIXTURE_FILENAME = 'epa-envirofacts-facilities-2026-10-05.json';
18
+ // The service answers with a bare JSON array, and a rejected query answers
19
+ // with a body of {error}. The filter segment takes the value on its own: an
20
+ // "equals" operator is accepted in the URL but never narrows the rows, so the
21
+ // URL builder below does not use one.
22
+ const EPA_ENVIROFACTS_FACILITY_SCHEMA = z.object({
23
+ tri_facility_id: z.string(),
24
+ facility_name: z.string(),
25
+ street_address: z.string().nullable().optional(),
26
+ city_name: z.string().nullable().optional(),
27
+ county_name: z.string().nullable().optional(),
28
+ state_abbr: z.string(),
29
+ zip_code: z.string().nullable().optional(),
30
+ region: z.string().nullable().optional(),
31
+ fac_closed_ind: z.string().nullable().optional(),
32
+ parent_co_name: z.string().nullable().optional(),
33
+ });
34
+ const EPA_ENVIROFACTS_PAYLOAD_SCHEMA = z.union([
35
+ z.array(EPA_ENVIROFACTS_FACILITY_SCHEMA),
36
+ z.object({ error: z.string().optional() }),
37
+ ]);
38
+ /** Reads the parent company, treating the register's "NA" as absent. */
39
+ function readEpaParentCompany(value) {
40
+ if (value === null || value === undefined) {
41
+ return null;
42
+ }
43
+ const trimmed = value.trim();
44
+ return trimmed === '' || trimmed === 'NA' ? null : trimmed;
45
+ }
46
+ /** Counts facilities per county, most facilities first, then by name. */
47
+ function countEpaEnvirofactsCounties(facilities) {
48
+ const counts = new Map();
49
+ for (const facility of facilities) {
50
+ counts.set(facility.countyName, (counts.get(facility.countyName) ?? 0) + 1);
51
+ }
52
+ return [...counts.entries()]
53
+ .sort(([leftCounty, leftCount], [rightCounty, rightCount]) => leftCount === rightCount ? leftCounty.localeCompare(rightCounty) : rightCount - leftCount)
54
+ .map(([countyName, facilityCount]) => ({ countyName, facilityCount }));
55
+ }
56
+ /**
57
+ * Builds the request URL for one page of Toxics Release Inventory facilities.
58
+ *
59
+ * The endpoint is keyless, and the state segment does narrow the rows: the
60
+ * value sits directly after the column name, with no `equals` operator.
61
+ * Envirofacts accepts an `equals` segment without complaining but then
62
+ * returns the table's first rows instead of the ones asked for.
63
+ *
64
+ * @param options - an optional state code and row window
65
+ * @returns the full request URL
66
+ */
67
+ export function buildEpaEnvirofactsFacilitiesUrl(options) {
68
+ const stateAbbr = options?.stateAbbr ?? EPA_ENVIROFACTS_STATE_ABBR;
69
+ const rowWindow = options?.rowWindow ?? EPA_ENVIROFACTS_ROW_WINDOW;
70
+ return `${EPA_ENVIROFACTS_API_BASE}/${EPA_ENVIROFACTS_TABLE}/${EPA_ENVIROFACTS_STATE_COLUMN}/${stateAbbr}/rows/${rowWindow}/JSON`;
71
+ }
72
+ /**
73
+ * Parses one response into the facilities in the page and its rollups.
74
+ *
75
+ * A row without a facility id, name, or state code fails the schema and stops
76
+ * the parse. An empty array is treated as a changed source, because the
77
+ * default query always returns rows.
78
+ *
79
+ * @param payload - the JSON body from the Envirofacts service
80
+ * @returns the facilities in the page, the open and closed counts, and the counties
81
+ */
82
+ export function parseEpaEnvirofactsFacilitiesPayload(payload) {
83
+ const parsed = EPA_ENVIROFACTS_PAYLOAD_SCHEMA.safeParse(payload);
84
+ if (!parsed.success) {
85
+ throw new UsSourceParseError(EPA_ENVIROFACTS_FACILITIES_SOURCE_ID, parsed.error.message);
86
+ }
87
+ if (!Array.isArray(parsed.data)) {
88
+ throw new UsSourceApiError(EPA_ENVIROFACTS_FACILITIES_SOURCE_ID, parsed.data.error ?? 'The service rejected the query');
89
+ }
90
+ if (parsed.data.length === 0) {
91
+ throw new UsSourceParseError(EPA_ENVIROFACTS_FACILITIES_SOURCE_ID, 'The response carried no rows');
92
+ }
93
+ const facilities = parsed.data.map((row) => ({
94
+ facilityId: row.tri_facility_id,
95
+ facilityName: row.facility_name,
96
+ streetAddress: row.street_address ?? '',
97
+ city: row.city_name ?? '',
98
+ countyName: row.county_name ?? '',
99
+ stateAbbr: row.state_abbr,
100
+ postalCode: row.zip_code ?? '',
101
+ region: row.region ?? '',
102
+ closed: row.fac_closed_ind === '1',
103
+ parentCompanyName: readEpaParentCompany(row.parent_co_name),
104
+ }));
105
+ const closedCount = facilities.filter((facility) => facility.closed).length;
106
+ return {
107
+ facilities,
108
+ facilityCount: facilities.length,
109
+ openCount: facilities.length - closedCount,
110
+ closedCount,
111
+ counties: countEpaEnvirofactsCounties(facilities),
112
+ };
113
+ }
114
+ /**
115
+ * Reads one page of Toxics Release Inventory facilities from Envirofacts.
116
+ *
117
+ * One keyless request covers the page. The state segment in the URL narrows
118
+ * the rows, so the page holds facilities from the state that was asked for.
119
+ *
120
+ * @param options - an optional state code, row window, and fetch stub
121
+ * @returns the facilities in the page and its rollups
122
+ */
123
+ export async function fetchEpaEnvirofactsFacilities(options) {
124
+ const fetchImpl = options?.fetchImpl ?? globalThis.fetch;
125
+ const url = buildEpaEnvirofactsFacilitiesUrl(options?.stateAbbr === undefined ? {} : { stateAbbr: options.stateAbbr });
126
+ const response = await fetchImpl(url);
127
+ if (!response.ok) {
128
+ throw new UsSourceApiError(EPA_ENVIROFACTS_FACILITIES_SOURCE_ID, `HTTP ${response.status} reading the facilities`);
129
+ }
130
+ return parseEpaEnvirofactsFacilitiesPayload(await response.json());
131
+ }
132
+ /** EPA Envirofacts Toxics Release Inventory facilities, keyless. */
133
+ export const epaEnvirofactsFacilitiesAdapter = {
134
+ id: EPA_ENVIROFACTS_FACILITIES_SOURCE_ID,
135
+ name: 'EPA Envirofacts facilities',
136
+ auth: 'none',
137
+ description: 'Toxics Release Inventory facilities from the EPA Envirofacts service, one row per site, with its county, address, and open or closed status.',
138
+ fetchLive: async (options) => fetchEpaEnvirofactsFacilities(options?.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
139
+ parse: (payload) => parseEpaEnvirofactsFacilitiesPayload(payload),
140
+ loadFixture: () => parseEpaEnvirofactsFacilitiesPayload(readFixtureJson(EPA_ENVIROFACTS_FIXTURE_FILENAME)),
141
+ };
142
+ //# sourceMappingURL=epaEnvirofactsFacilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"epaEnvirofactsFacilities.js","sourceRoot":"","sources":["../src/epaEnvirofactsFacilities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import type { UsDataAdapter } from './types.js';
2
+ /** Adapter id for the FDIC bank directory source. */
3
+ export declare const FDIC_BANK_DIRECTORY_SOURCE_ID = "fdic-bank-directory";
4
+ /** One bank in the FDIC's institution directory. */
5
+ export interface FdicBank {
6
+ /** The FDIC's own certificate number for the bank. */
7
+ id: string;
8
+ /** The bank's legal name. */
9
+ name: string;
10
+ /** City of the bank's main office. */
11
+ city: string;
12
+ /** Two-letter state code of the main office. */
13
+ stateAbbr: string;
14
+ }
15
+ /** One state and how many banks in the page sit there. */
16
+ export interface FdicBankStateCount {
17
+ stateAbbr: string;
18
+ bankCount: number;
19
+ }
20
+ /** One page of the bank directory, folded for a story. */
21
+ export interface FdicBankDirectory {
22
+ /** One entry per bank in the page. */
23
+ banks: FdicBank[];
24
+ bankCount: number;
25
+ /** How many banks the FDIC holds in total, not just in this page. */
26
+ totalBanks: number;
27
+ /** States the banks in the page sit in, most banks first. */
28
+ states: FdicBankStateCount[];
29
+ }
30
+ /** Base URL for the FDIC institutions API. */
31
+ export declare const FDIC_BANK_API_BASE = "https://banks.data.fdic.gov/api/institutions";
32
+ /** Row cap for one request. */
33
+ export declare const FDIC_BANK_ROW_LIMIT = 10;
34
+ /** Columns the adapter asks for. The directory carries many more. */
35
+ export declare const FDIC_BANK_FIELDS = "NAME,CITY,STALP";
36
+ /**
37
+ * Builds the request URL for one page of the bank directory.
38
+ *
39
+ * The endpoint is keyless. The `fields` parameter trims the response to the
40
+ * columns a story reads, which keeps each row small. The service answers the
41
+ * banks.data.fdic.gov host with a redirect to api.fdic.gov, which fetch
42
+ * follows.
43
+ *
44
+ * @param options - an optional row cap and column list
45
+ * @returns the full request URL
46
+ */
47
+ export declare function buildFdicBankDirectoryUrl(options?: {
48
+ rowLimit?: number;
49
+ fields?: string;
50
+ }): string;
51
+ /**
52
+ * Parses one response into the banks in the page and the state counts.
53
+ *
54
+ * A row without an id, name, city, or state is a changed shape and stops the
55
+ * parse. The API's own total is used for `totalBanks`, falling back to the
56
+ * page size when the envelope omits it.
57
+ *
58
+ * @param payload - the JSON body from the FDIC institutions API
59
+ * @returns the banks in the page, the total the FDIC reports, and state counts
60
+ */
61
+ export declare function parseFdicBankDirectoryPayload(payload: unknown): FdicBankDirectory;
62
+ /**
63
+ * Reads one page of the FDIC's bank directory.
64
+ *
65
+ * One keyless request covers the page. The service trims each row to the
66
+ * columns asked for, so a story gets names, cities, and states without the
67
+ * full institution record.
68
+ *
69
+ * @param options - an optional row cap and fetch stub
70
+ * @returns the banks in the page, the FDIC total, and the state counts
71
+ */
72
+ export declare function fetchFdicBankDirectory(options?: {
73
+ rowLimit?: number;
74
+ fetchImpl?: typeof globalThis.fetch;
75
+ }): Promise<FdicBankDirectory>;
76
+ /** FDIC bank directory, one row per insured institution, keyless. */
77
+ export declare const fdicBankDirectoryAdapter: UsDataAdapter<FdicBankDirectory>;
78
+ //# sourceMappingURL=fdicBankDirectory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fdicBankDirectory.d.ts","sourceRoot":"","sources":["../src/fdicBankDirectory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,qDAAqD;AACrD,eAAO,MAAM,6BAA6B,wBAAwB,CAAC;AAEnE,oDAAoD;AACpD,MAAM,WAAW,QAAQ;IACvB,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,sCAAsC;IACtC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,MAAM,EAAE,kBAAkB,EAAE,CAAC;CAC9B;AAED,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,iDAAiD,CAAC;AAEjF,+BAA+B;AAC/B,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAuBlD;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAMT;AAeD;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,iBAAiB,CAiCjF;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,CAAC,EAAE;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACrC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAa7B;AAED,qEAAqE;AACrE,eAAO,MAAM,wBAAwB,EAAE,aAAa,CAAC,iBAAiB,CAYrE,CAAC"}
@@ -0,0 +1,124 @@
1
+ import { z } from 'zod';
2
+ import { UsSourceApiError, UsSourceParseError } from './errors.js';
3
+ import { readFixtureJson } from './fixtures.js';
4
+ /** Adapter id for the FDIC bank directory source. */
5
+ export const FDIC_BANK_DIRECTORY_SOURCE_ID = 'fdic-bank-directory';
6
+ /** Base URL for the FDIC institutions API. */
7
+ export const FDIC_BANK_API_BASE = 'https://banks.data.fdic.gov/api/institutions';
8
+ /** Row cap for one request. */
9
+ export const FDIC_BANK_ROW_LIMIT = 10;
10
+ /** Columns the adapter asks for. The directory carries many more. */
11
+ export const FDIC_BANK_FIELDS = 'NAME,CITY,STALP';
12
+ /** Committed snapshot, so a build works when the FDIC host is unreachable. */
13
+ const FDIC_BANK_FIXTURE_FILENAME = 'fdic-bank-directory-2026-10-05.json';
14
+ // The API wraps each institution in a result object with its own score, and
15
+ // answers a rejected request with an HTTP error and a body of {errors}.
16
+ const FDIC_BANK_ROW_SCHEMA = z.object({
17
+ data: z.object({
18
+ ID: z.string().optional(),
19
+ NAME: z.string().optional(),
20
+ CITY: z.string().optional(),
21
+ STALP: z.string().optional(),
22
+ }),
23
+ });
24
+ const FDIC_BANK_PAYLOAD_SCHEMA = z.object({
25
+ meta: z.object({ total: z.number().optional() }).optional(),
26
+ totals: z.object({ count: z.number().optional() }).optional(),
27
+ data: z.array(FDIC_BANK_ROW_SCHEMA).optional(),
28
+ errors: z.array(z.unknown()).optional(),
29
+ });
30
+ /**
31
+ * Builds the request URL for one page of the bank directory.
32
+ *
33
+ * The endpoint is keyless. The `fields` parameter trims the response to the
34
+ * columns a story reads, which keeps each row small. The service answers the
35
+ * banks.data.fdic.gov host with a redirect to api.fdic.gov, which fetch
36
+ * follows.
37
+ *
38
+ * @param options - an optional row cap and column list
39
+ * @returns the full request URL
40
+ */
41
+ export function buildFdicBankDirectoryUrl(options) {
42
+ const params = new URLSearchParams({
43
+ limit: String(options?.rowLimit ?? FDIC_BANK_ROW_LIMIT),
44
+ fields: options?.fields ?? FDIC_BANK_FIELDS,
45
+ });
46
+ return `${FDIC_BANK_API_BASE}?${params.toString()}`;
47
+ }
48
+ /** Counts banks per state in the order they were seen. */
49
+ function countFdicBankStates(banks) {
50
+ const counts = new Map();
51
+ for (const bank of banks) {
52
+ counts.set(bank.stateAbbr, (counts.get(bank.stateAbbr) ?? 0) + 1);
53
+ }
54
+ return [...counts.entries()]
55
+ .sort(([leftState, leftCount], [rightState, rightCount]) => leftCount === rightCount ? leftState.localeCompare(rightState) : rightCount - leftCount)
56
+ .map(([stateAbbr, bankCount]) => ({ stateAbbr, bankCount }));
57
+ }
58
+ /**
59
+ * Parses one response into the banks in the page and the state counts.
60
+ *
61
+ * A row without an id, name, city, or state is a changed shape and stops the
62
+ * parse. The API's own total is used for `totalBanks`, falling back to the
63
+ * page size when the envelope omits it.
64
+ *
65
+ * @param payload - the JSON body from the FDIC institutions API
66
+ * @returns the banks in the page, the total the FDIC reports, and state counts
67
+ */
68
+ export function parseFdicBankDirectoryPayload(payload) {
69
+ const parsed = FDIC_BANK_PAYLOAD_SCHEMA.safeParse(payload);
70
+ if (!parsed.success) {
71
+ throw new UsSourceParseError(FDIC_BANK_DIRECTORY_SOURCE_ID, parsed.error.message);
72
+ }
73
+ if (parsed.data.errors !== undefined && parsed.data.errors.length > 0) {
74
+ throw new UsSourceApiError(FDIC_BANK_DIRECTORY_SOURCE_ID, 'The API rejected the request');
75
+ }
76
+ const rows = parsed.data.data ?? [];
77
+ if (rows.length === 0) {
78
+ throw new UsSourceParseError(FDIC_BANK_DIRECTORY_SOURCE_ID, 'The response carried no rows');
79
+ }
80
+ const banks = rows.map((row) => {
81
+ const { ID, NAME, CITY, STALP } = row.data;
82
+ if (ID === undefined || NAME === undefined || CITY === undefined || STALP === undefined) {
83
+ throw new UsSourceParseError(FDIC_BANK_DIRECTORY_SOURCE_ID, 'A bank row arrived without its id, name, city, or state');
84
+ }
85
+ return { id: ID, name: NAME, city: CITY, stateAbbr: STALP };
86
+ });
87
+ const totalBanks = parsed.data.totals?.count ?? parsed.data.meta?.total ?? banks.length;
88
+ return {
89
+ banks,
90
+ bankCount: banks.length,
91
+ totalBanks,
92
+ states: countFdicBankStates(banks),
93
+ };
94
+ }
95
+ /**
96
+ * Reads one page of the FDIC's bank directory.
97
+ *
98
+ * One keyless request covers the page. The service trims each row to the
99
+ * columns asked for, so a story gets names, cities, and states without the
100
+ * full institution record.
101
+ *
102
+ * @param options - an optional row cap and fetch stub
103
+ * @returns the banks in the page, the FDIC total, and the state counts
104
+ */
105
+ export async function fetchFdicBankDirectory(options) {
106
+ const fetchImpl = options?.fetchImpl ?? globalThis.fetch;
107
+ const url = buildFdicBankDirectoryUrl(options?.rowLimit === undefined ? {} : { rowLimit: options.rowLimit });
108
+ const response = await fetchImpl(url);
109
+ if (!response.ok) {
110
+ throw new UsSourceApiError(FDIC_BANK_DIRECTORY_SOURCE_ID, `HTTP ${response.status} reading the bank directory`);
111
+ }
112
+ return parseFdicBankDirectoryPayload(await response.json());
113
+ }
114
+ /** FDIC bank directory, one row per insured institution, keyless. */
115
+ export const fdicBankDirectoryAdapter = {
116
+ id: FDIC_BANK_DIRECTORY_SOURCE_ID,
117
+ name: 'FDIC bank directory',
118
+ auth: 'none',
119
+ description: 'Insured banks in the FDIC institution directory, one row per bank, with the city and state of its main office.',
120
+ fetchLive: async (options) => fetchFdicBankDirectory(options?.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
121
+ parse: (payload) => parseFdicBankDirectoryPayload(payload),
122
+ loadFixture: () => parseFdicBankDirectoryPayload(readFixtureJson(FDIC_BANK_FIXTURE_FILENAME)),
123
+ };
124
+ //# sourceMappingURL=fdicBankDirectory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fdicBankDirectory.js","sourceRoot":"","sources":["../src/fdicBankDirectory.ts"],"names":[],"mappings":"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"}