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

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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +442 -214
  3. package/dist/apiKey.d.ts +12 -0
  4. package/dist/apiKey.d.ts.map +1 -0
  5. package/dist/apiKey.js +18 -0
  6. package/dist/apiKey.js.map +1 -0
  7. package/dist/blsSeries.d.ts.map +1 -1
  8. package/dist/blsSeries.js +2 -4
  9. package/dist/blsSeries.js.map +1 -1
  10. package/dist/btsTransportationStats.d.ts.map +1 -1
  11. package/dist/btsTransportationStats.js +3 -5
  12. package/dist/btsTransportationStats.js.map +1 -1
  13. package/dist/cdcCountyObesity.d.ts.map +1 -1
  14. package/dist/cdcCountyObesity.js +3 -5
  15. package/dist/cdcCountyObesity.js.map +1 -1
  16. package/dist/cdcSocrataCatalogue.d.ts.map +1 -1
  17. package/dist/cdcSocrataCatalogue.js +3 -5
  18. package/dist/cdcSocrataCatalogue.js.map +1 -1
  19. package/dist/cfpbConsumerComplaints.d.ts +2 -0
  20. package/dist/cfpbConsumerComplaints.d.ts.map +1 -1
  21. package/dist/cfpbConsumerComplaints.js +12 -12
  22. package/dist/cfpbConsumerComplaints.js.map +1 -1
  23. package/dist/clinicalTrialsStudies.d.ts.map +1 -1
  24. package/dist/clinicalTrialsStudies.js +3 -5
  25. package/dist/clinicalTrialsStudies.js.map +1 -1
  26. package/dist/cpscProductRecalls.d.ts.map +1 -1
  27. package/dist/cpscProductRecalls.js +2 -4
  28. package/dist/cpscProductRecalls.js.map +1 -1
  29. package/dist/epaEnvirofactsFacilities.d.ts.map +1 -1
  30. package/dist/epaEnvirofactsFacilities.js +2 -4
  31. package/dist/epaEnvirofactsFacilities.js.map +1 -1
  32. package/dist/errors.d.ts +12 -2
  33. package/dist/errors.d.ts.map +1 -1
  34. package/dist/errors.js +8 -4
  35. package/dist/errors.js.map +1 -1
  36. package/dist/estimatetaxIncomeTax.d.ts +140 -0
  37. package/dist/estimatetaxIncomeTax.d.ts.map +1 -0
  38. package/dist/estimatetaxIncomeTax.js +225 -0
  39. package/dist/estimatetaxIncomeTax.js.map +1 -0
  40. package/dist/fdicBankDirectory.d.ts.map +1 -1
  41. package/dist/fdicBankDirectory.js +2 -4
  42. package/dist/fdicBankDirectory.js.map +1 -1
  43. package/dist/femaDisasterDeclarations.d.ts.map +1 -1
  44. package/dist/femaDisasterDeclarations.js +2 -4
  45. package/dist/femaDisasterDeclarations.js.map +1 -1
  46. package/dist/healthdataSocrataCatalogue.d.ts.map +1 -1
  47. package/dist/healthdataSocrataCatalogue.js +3 -5
  48. package/dist/healthdataSocrataCatalogue.js.map +1 -1
  49. package/dist/http.d.ts +36 -0
  50. package/dist/http.d.ts.map +1 -0
  51. package/dist/http.js +85 -0
  52. package/dist/http.js.map +1 -0
  53. package/dist/index.d.ts +17 -1
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +9 -1
  56. package/dist/index.js.map +1 -1
  57. package/dist/ncbiPubmedSearch.d.ts.map +1 -1
  58. package/dist/ncbiPubmedSearch.js +2 -4
  59. package/dist/ncbiPubmedSearch.js.map +1 -1
  60. package/dist/nceiAnnualTemperature.d.ts.map +1 -1
  61. package/dist/nceiAnnualTemperature.js +3 -5
  62. package/dist/nceiAnnualTemperature.js.map +1 -1
  63. package/dist/noaaSeaLevel.d.ts.map +1 -1
  64. package/dist/noaaSeaLevel.js +2 -4
  65. package/dist/noaaSeaLevel.js.map +1 -1
  66. package/dist/nwsPointForecast.d.ts +1 -1
  67. package/dist/nwsPointForecast.d.ts.map +1 -1
  68. package/dist/nwsPointForecast.js +7 -6
  69. package/dist/nwsPointForecast.js.map +1 -1
  70. package/dist/nycOpenDataCatalogue.d.ts +97 -0
  71. package/dist/nycOpenDataCatalogue.d.ts.map +1 -0
  72. package/dist/nycOpenDataCatalogue.js +151 -0
  73. package/dist/nycOpenDataCatalogue.js.map +1 -0
  74. package/dist/openFdaFoodRecalls.d.ts.map +1 -1
  75. package/dist/openFdaFoodRecalls.js +2 -4
  76. package/dist/openFdaFoodRecalls.js.map +1 -1
  77. package/dist/registry.d.ts.map +1 -1
  78. package/dist/registry.js +8 -2
  79. package/dist/registry.js.map +1 -1
  80. package/dist/secEdgarFilings.d.ts +1 -1
  81. package/dist/secEdgarFilings.d.ts.map +1 -1
  82. package/dist/secEdgarFilings.js +7 -6
  83. package/dist/secEdgarFilings.js.map +1 -1
  84. package/dist/treasuryAvgInterestRate.d.ts.map +1 -1
  85. package/dist/treasuryAvgInterestRate.js +2 -4
  86. package/dist/treasuryAvgInterestRate.js.map +1 -1
  87. package/dist/treasuryDebtToPenny.d.ts.map +1 -1
  88. package/dist/treasuryDebtToPenny.js +2 -4
  89. package/dist/treasuryDebtToPenny.js.map +1 -1
  90. package/dist/usaspendingAgencies.d.ts.map +1 -1
  91. package/dist/usaspendingAgencies.js +3 -5
  92. package/dist/usaspendingAgencies.js.map +1 -1
  93. package/dist/usgsEarthquakes.d.ts.map +1 -1
  94. package/dist/usgsEarthquakes.js +2 -4
  95. package/dist/usgsEarthquakes.js.map +1 -1
  96. package/dist/usgsPeakStreamflow.d.ts.map +1 -1
  97. package/dist/usgsPeakStreamflow.js +3 -5
  98. package/dist/usgsPeakStreamflow.js.map +1 -1
  99. package/package.json +4 -3
package/README.md CHANGED
@@ -1,30 +1,67 @@
1
1
  # @usa-open-data-connectors/usa-sources
2
2
 
3
- Uniform TypeScript adapters for US public data sources. Keyless first, with a
4
- strict parser and a committed fixture behind every adapter so a build never
5
- depends on a live API.
6
-
7
- The HTTP API and the CLI read this package's registry (`US_DATA_SOURCES`), so a
8
- new adapter is exposed on `/api/sources`, `/api/sources/{id}/probe`, and
9
- `/api/sources/{id}/data` as soon as it is registered.
10
-
11
- ## Sources
12
-
13
- | id | publisher | auth | what it reads |
14
- | ---------------------------- | --------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------------------------- |
15
- | `bls-unemployment-rate` | Bureau of Labor Statistics | none | The national unemployment rate, monthly, seasonally adjusted |
16
- | `usgs-hawaii-earthquakes` | US Geological Survey | none | Earthquakes of magnitude 2.5 and above near the Hawaiian islands |
17
- | `cdc-county-obesity` | Centers for Disease Control (CDC) | none | The share of adults with obesity in each US county, from CDC PLACES |
18
- | `ncei-annual-temperature` | NOAA National Centers for Environmental Information | none | Calendar-year average temperature for the contiguous United States, since 1895 |
19
- | `noaa-sea-level` | NOAA Center for Operational Oceanographic Products and Services | none | Monthly mean sea level at a tide gauge, folded into calendar-year averages, since 1856 at The Battery |
20
- | `treasury-avg-interest-rate` | US Department of the Treasury | none | Average interest rate on the interest-bearing federal debt outstanding, monthly since 2001 |
21
- | `fema-disaster-declarations` | Federal Emergency Management Agency | none | Every disaster declaration FEMA has published, one row per declaration, from 1953 to the newest one |
22
- | `openfda-food-recalls` | US Food and Drug Administration | none | Every food recall FDA has published as an enforcement report, from June 2012 to the newest publication date |
23
- | `usgs-peak-streamflow` | US Geological Survey | none | The annual peak-flow record at a USGS stream gauge, one row per water year, since 1844 at the Mississippi River at St. Louis |
24
- | `cpsc-product-recalls` | US Consumer Product Safety Commission | none | Every consumer product recall CPSC has published, one row per recall, from 2014 to the newest one |
25
- | `cfpb-consumer-complaints` | Consumer Financial Protection Bureau | none | Every consumer complaint sent to a company since December 2011, counted by year, with the products and companies named most |
26
-
27
- ## Usage
3
+ Uniform TypeScript adapters for US public data sources, with live probes and offline fixtures.
4
+
5
+ ## What this package does
6
+
7
+ - Exposes 24 source adapters behind one interface.
8
+ - Fetches live data from each source and parses it into plain objects.
9
+ - Loads a committed fixture when the network is unavailable.
10
+ - Publishes the `US_DATA_SOURCES` registry for the API and the CLI.
11
+ - Exports named functions for each source, with that source's query parameters.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install @usa-open-data-connectors/usa-sources
17
+ ```
18
+
19
+ ## Quick start
20
+
21
+ ```ts
22
+ import { probeAllUsDataSources } from '@usa-open-data-connectors/usa-sources';
23
+
24
+ const probes = await probeAllUsDataSources();
25
+ console.log(probes.map((probe) => `${probe.id}: ${probe.ok ? 'ok' : probe.status}`).join('\n'));
26
+ ```
27
+
28
+ ## Adapters
29
+
30
+ | Adapter | Publisher | What it reads |
31
+ | --- | --- | --- |
32
+ | `bls-unemployment-rate` | Bureau of Labor Statistics | Monthly national unemployment rate, seasonally adjusted, from the Current Population Survey. |
33
+ | `usgs-hawaii-earthquakes` | US Geological Survey | Earthquakes of magnitude 2.5 and above near the Hawaiian islands. |
34
+ | `cdc-county-obesity` | Centers for Disease Control and Prevention | Model-based share of adults with obesity in each US county, from CDC PLACES. |
35
+ | `ncei-annual-temperature` | NOAA National Centers for Environmental Information | Calendar-year average temperature for the contiguous United States since 1895. |
36
+ | `noaa-sea-level` | NOAA Center for Operational Oceanographic Products and Services | Monthly mean sea level at a tide gauge, folded into calendar-year averages with a fitted trend. |
37
+ | `treasury-avg-interest-rate` | US Department of the Treasury | Average interest rate on the interest-bearing federal debt outstanding, monthly since 2001. |
38
+ | `fema-disaster-declarations` | Federal Emergency Management Agency | Every disaster declaration FEMA has published, from 1953 to the newest. |
39
+ | `openfda-food-recalls` | US Food and Drug Administration | Every food recall the FDA publishes as an enforcement report, from June 2012. |
40
+ | `usgs-peak-streamflow` | US Geological Survey | Annual peak-flow record at a USGS stream gauge, from 1844 at the Mississippi River at St. Louis. |
41
+ | `cpsc-product-recalls` | US Consumer Product Safety Commission | Every consumer product recall CPSC has published, from 2014 to the newest. |
42
+ | `cfpb-consumer-complaints` | Consumer Financial Protection Bureau | Every consumer complaint sent to a company, counted by year, with the top products and companies. |
43
+ | `treasury-debt-to-penny` | US Department of the Treasury | Daily total public debt outstanding, in dollars, from the debt to the penny file. |
44
+ | `sec-edgar-filings` | US Securities and Exchange Commission | An issuer's recent filing history from SEC EDGAR, with the form type and the newest date. |
45
+ | `fdic-bank-directory` | Federal Deposit Insurance Corporation | Insured banks in the FDIC institution directory, with the city and state of each main office. |
46
+ | `clinicaltrials-studies` | ClinicalTrials.gov | Registered clinical studies, with the status, phase, lead sponsor, and enrollment. |
47
+ | `nws-point-forecast` | National Weather Service | The forecast grid point for a latitude and longitude, with the URLs for the daily and hourly forecast. |
48
+ | `usaspending-agencies` | USAspending | Top-tier federal agencies, with budget authority, obligations, and outlays for the active quarter. |
49
+ | `epa-envirofacts-facilities` | Environmental Protection Agency | Toxics Release Inventory facilities, with county, address, and open or closed status. |
50
+ | `ncbi-pubmed-search` | National Center for Biotechnology Information | PubMed literature search results, with the match count and a page of PubMed ids. |
51
+ | `cdc-socrata-catalogue` | Centers for Disease Control and Prevention | The CDC Socrata catalogue, one row per dataset, chart, or story. |
52
+ | `healthdata-socrata-catalogue` | HealthData.gov | The HealthData.gov Socrata catalogue, one row per dataset, chart, or story. |
53
+ | `bts-transportation-stats` | Bureau of Transportation Statistics | The BTS Socrata catalogue, one row per dataset, chart, or story. |
54
+ | `estimatetax-income-tax` | EstimateTax | US federal, state, and local income tax for one salary, with the official document behind every figure. |
55
+ | `nyc-open-data-catalogue` | New York City Open Data | The NYC Socrata catalogue, one row per published dataset or map. |
56
+
57
+ The HTTP API and the CLI read this package's registry. A new adapter is exposed on
58
+ `/api/sources`, `/api/sources/{id}/probe`, and `/api/sources/{id}/data` as soon as it is registered.
59
+
60
+ ### Usage examples
61
+
62
+ The examples below hit the live sources and need no key. Each one uses the exported functions for that adapter.
63
+
64
+ `bls-unemployment-rate`:
28
65
 
29
66
  ```ts
30
67
  import { fetchBlsSeries, US_UNEMPLOYMENT_SERIES_ID } from '@usa-open-data-connectors/usa-sources';
@@ -36,6 +73,8 @@ const rate = await fetchBlsSeries(US_UNEMPLOYMENT_SERIES_ID, {
36
73
  console.log(rate.latest.year, rate.latest.period, rate.latest.value);
37
74
  ```
38
75
 
76
+ `usgs-hawaii-earthquakes`:
77
+
39
78
  ```ts
40
79
  import {
41
80
  buildUsgsEarthquakeCatalogue,
@@ -54,6 +93,8 @@ const catalogue = buildUsgsEarthquakeCatalogue(earthquakes);
54
93
  console.log(catalogue.count, catalogue.strongest.place, catalogue.strongest.magnitude);
55
94
  ```
56
95
 
96
+ `cdc-county-obesity`:
97
+
57
98
  ```ts
58
99
  import {
59
100
  buildCdcCountyObesitySet,
@@ -64,6 +105,8 @@ const set = buildCdcCountyObesitySet(await fetchCdcCountyObesity());
64
105
  console.log(set.countyCount, set.lowest.countyName, set.highest.percent, set.national.percent);
65
106
  ```
66
107
 
108
+ `ncei-annual-temperature`:
109
+
67
110
  ```ts
68
111
  import {
69
112
  buildNceiAnnualTemperatureSeries,
@@ -77,6 +120,8 @@ const series = await fetchNceiAnnualTemperature({
77
120
  console.log(series.yearCount, series.warmest.year, series.coldest.valueFahrenheit);
78
121
  ```
79
122
 
123
+ `noaa-sea-level`:
124
+
80
125
  ```ts
81
126
  import { buildNoaaSeaLevelSeries, fetchNoaaSeaLevel } from '@usa-open-data-connectors/usa-sources';
82
127
 
@@ -89,6 +134,8 @@ console.log(
89
134
  );
90
135
  ```
91
136
 
137
+ `treasury-avg-interest-rate`:
138
+
92
139
  ```ts
93
140
  import { fetchTreasuryAvgInterestRates } from '@usa-open-data-connectors/usa-sources';
94
141
 
@@ -96,6 +143,142 @@ const rates = await fetchTreasuryAvgInterestRates();
96
143
  console.log(rates.monthCount, rates.lastMonth.averageInterestRatePercent, rates.lowest.recordDate);
97
144
  ```
98
145
 
146
+ `treasury-debt-to-penny`:
147
+
148
+ ```ts
149
+ import {
150
+ buildTreasuryDebtToPennyPage,
151
+ fetchTreasuryDebtToPenny,
152
+ } from '@usa-open-data-connectors/usa-sources';
153
+
154
+ const page = buildTreasuryDebtToPennyPage(
155
+ (await fetchTreasuryDebtToPenny({ rowLimit: 10_000 })).days
156
+ );
157
+ console.log(
158
+ page.dayCount,
159
+ page.firstDay.recordDate,
160
+ page.lastDay.recordDate,
161
+ page.lastDay.totalPublicDebtOutstandingUsd
162
+ );
163
+ ```
164
+
165
+ `fdic-bank-directory`:
166
+
167
+ ```ts
168
+ import {
169
+ buildFdicBankDirectoryUrl,
170
+ parseFdicBankDirectoryPayload,
171
+ } from '@usa-open-data-connectors/usa-sources';
172
+
173
+ const url = new URL(
174
+ buildFdicBankDirectoryUrl({ rowLimit: 10_000, fields: 'ID,NAME,CITY,STALP' })
175
+ );
176
+ url.searchParams.set('filters', 'ACTIVE:1');
177
+ const response = await fetch(url);
178
+ if (!response.ok) {
179
+ throw new Error(`HTTP ${response.status} from ${url.toString()}`);
180
+ }
181
+ const directory = parseFdicBankDirectoryPayload(await response.json());
182
+ console.log(directory.bankCount, directory.totalBanks, directory.states[0]);
183
+ ```
184
+
185
+ The public API is `FDIC_BANK_API_BASE`, `FDIC_BANK_ROW_LIMIT`,
186
+ `FDIC_BANK_FIELDS`, `buildFdicBankDirectoryUrl`,
187
+ `parseFdicBankDirectoryPayload`, `fetchFdicBankDirectory`, and
188
+ `fdicBankDirectoryAdapter`, with the `FdicBank`, `FdicBankStateCount`, and
189
+ `FdicBankDirectory` types. The parser validates the response with a zod schema:
190
+ `meta.total`, `totals.count`, `data[].data.{ID,NAME,CITY,STALP}`, and
191
+ `errors`. A row missing any of the four fields throws `UsSourceParseError`; a
192
+ body with an `errors` array throws `UsSourceApiError`.
193
+
194
+ `sec-edgar-filings`:
195
+
196
+ ```ts
197
+ import {
198
+ fetchSecEdgarFilings,
199
+ SEC_EDGAR_APPLE_CIK,
200
+ } from '@usa-open-data-connectors/usa-sources';
201
+
202
+ const company = await fetchSecEdgarFilings({ cik: SEC_EDGAR_APPLE_CIK });
203
+ console.log(
204
+ company.name,
205
+ company.filingCount,
206
+ company.newestFilingDate,
207
+ company.formCounts[0]
208
+ );
209
+ ```
210
+
211
+ The public API is `SEC_EDGAR_SUBMISSIONS_API_BASE`, `SEC_EDGAR_APPLE_CIK`,
212
+ `SEC_EDGAR_USER_AGENT`, `buildSecEdgarSubmissionsUrl`,
213
+ `parseSecEdgarFilingsPayload`, `fetchSecEdgarFilings`, and
214
+ `secEdgarFilingsAdapter`, with the `SecEdgarFiling`, `SecEdgarFormCount`, and
215
+ `SecEdgarCompanyFilings` types. The parser validates the response with a zod
216
+ schema over `cik`, `name`, `tickers`, `exchanges`, `fiscalYearEnd`, and
217
+ `filings.recent`. The `recent` block sends parallel arrays for the accession
218
+ number, filing date, report date, form, size, XBRL flags, primary document,
219
+ and description. The parser zips them into one row per filing and throws
220
+ `UsSourceParseError` when the arrays do not line up or a filing date is not
221
+ `YYYY-MM-DD`.
222
+
223
+ `epa-envirofacts-facilities`:
224
+
225
+ ```ts
226
+ import {
227
+ buildEpaEnvirofactsFacilitiesUrl,
228
+ parseEpaEnvirofactsFacilitiesPayload,
229
+ } from '@usa-open-data-connectors/usa-sources';
230
+
231
+ const url = buildEpaEnvirofactsFacilitiesUrl({ stateAbbr: 'CA', rowWindow: '0:9999' });
232
+ const response = await fetch(url);
233
+ if (!response.ok) {
234
+ throw new Error(`HTTP ${response.status} from ${url}`);
235
+ }
236
+ const page = parseEpaEnvirofactsFacilitiesPayload(await response.json());
237
+ console.log(page.facilityCount, page.openCount, page.counties[0]);
238
+ ```
239
+
240
+ The public API is `EPA_ENVIROFACTS_API_BASE`, `EPA_ENVIROFACTS_TABLE`,
241
+ `EPA_ENVIROFACTS_STATE_COLUMN`, `EPA_ENVIROFACTS_STATE_ABBR`,
242
+ `EPA_ENVIROFACTS_ROW_WINDOW`, `buildEpaEnvirofactsFacilitiesUrl`,
243
+ `parseEpaEnvirofactsFacilitiesPayload`, `fetchEpaEnvirofactsFacilities`, and
244
+ `epaEnvirofactsFacilitiesAdapter`, with the `EpaEnvirofactsFacility`,
245
+ `EpaEnvirofactsCountyCount`, and `EpaEnvirofactsFacilityPage` types. The
246
+ parser validates a bare JSON array against a zod schema over
247
+ `tri_facility_id`, `facility_name`, `state_abbr`, and the optional
248
+ `street_address`, `city_name`, `county_name`, `zip_code`, `region`,
249
+ `fac_closed_ind`, and `parent_co_name` fields. A rejected query answers with
250
+ `{error}` rather than an array, which throws `UsSourceApiError`.
251
+
252
+ `cdc-socrata-catalogue`:
253
+
254
+ ```ts
255
+ import {
256
+ buildCdcSocrataCatalogueUrl,
257
+ parseCdcSocrataCataloguePayload,
258
+ } from '@usa-open-data-connectors/usa-sources';
259
+
260
+ const url = buildCdcSocrataCatalogueUrl({ limit: 10_000 });
261
+ const response = await fetch(url);
262
+ if (!response.ok) {
263
+ throw new Error(`HTTP ${response.status} from ${url}`);
264
+ }
265
+ const catalogue = parseCdcSocrataCataloguePayload(await response.json());
266
+ console.log(catalogue.entryCount, catalogue.datasetCount, catalogue.categories[0]);
267
+ ```
268
+
269
+ The public API is `CDC_SOCRATA_CATALOGUE_API_BASE`,
270
+ `CDC_SOCRATA_CATALOGUE_LIMIT`, `buildCdcSocrataCatalogueUrl`,
271
+ `parseCdcSocrataCataloguePayload`, `fetchCdcSocrataCatalogue`, and
272
+ `cdcSocrataCatalogueAdapter`, with the `CdcSocrataCatalogueEntry`,
273
+ `CdcSocrataCategoryCount`, and `CdcSocrataCatalogue` types. The parser
274
+ validates a bare JSON array against a zod schema over `id`, `name`,
275
+ `assetType`, `category`, `viewType`, `createdAt`, `viewLastModified`,
276
+ `viewCount`, and `downloadCount`. A row missing `id`, `name`, or `createdAt`
277
+ throws `UsSourceParseError`. A response with no rows also throws, because an
278
+ empty catalogue is not a usable answer.
279
+
280
+ `fema-disaster-declarations`:
281
+
99
282
  ```ts
100
283
  import { fetchFemaDeclarations } from '@usa-open-data-connectors/usa-sources';
101
284
 
@@ -107,6 +290,8 @@ console.log(
107
290
  );
108
291
  ```
109
292
 
293
+ `usgs-peak-streamflow`:
294
+
110
295
  ```ts
111
296
  import {
112
297
  buildUsgsPeakStreamflowSeries,
@@ -122,6 +307,8 @@ console.log(
122
307
  );
123
308
  ```
124
309
 
310
+ `cpsc-product-recalls`:
311
+
125
312
  ```ts
126
313
  import { fetchCpscProductRecalls } from '@usa-open-data-connectors/usa-sources';
127
314
 
@@ -134,6 +321,8 @@ console.log(
134
321
  );
135
322
  ```
136
323
 
324
+ `cfpb-consumer-complaints`:
325
+
137
326
  ```ts
138
327
  import { fetchCfpConsumerComplaints } from '@usa-open-data-connectors/usa-sources';
139
328
 
@@ -146,194 +335,233 @@ console.log(
146
335
  );
147
336
  ```
148
337
 
149
- ## Notes on the BLS API
150
-
151
- - The public API refuses a request spanning more than ten years, so
152
- `fetchBlsSeries` splits a longer range into windows: two decades cost three
153
- calls once the range runs into the current year.
154
- - Keyless access allows 25 series queries a day. A registered key raises that
155
- to 500, and the adapter takes `apiKey` for the day that matters.
156
- - The agency uses `-` for a month it could not publish, and `M13` for an
157
- annual average. Both are dropped, so one year holds twelve monthly values.
158
-
159
- ## Notes on the USGS earthquake catalogue
160
-
161
- - The event query answers a window of any length, one request per window. The
162
- box, the magnitude floor, and the dates come from the caller.
163
- - The feed mixes tectonic earthquakes with quarry blasts, explosions, and ice
164
- quakes, and a row can arrive without a magnitude. The parser keeps only
165
- tectonic earthquakes that carry a magnitude and a depth.
166
- - The catalogue answers newest first. `parseUsgsEarthquakes` returns oldest
167
- first, so a caller reading index 0 gets the start of the window.
168
-
169
- ## Notes on the CDC PLACES county release
170
-
171
- - The resource id is per release. `cdc-county-obesity` reads the 2025
172
- release, whose estimates come from the 2023 Behavioral Risk Factor
173
- Surveillance System (BRFSS) and the Census Bureau's 2023 county population
174
- estimates. A new release gets a new id, so a caller pinned to this one
175
- keeps reading the numbers it was written against.
176
- - Every row is a model-based estimate, not a count. The release publishes
177
- each measure twice, as crude and as age-adjusted prevalence; the adapter
178
- reads the crude rows, because age-adjusted ones answer a different question.
179
- - A county the model could not estimate arrives with no value, and the
180
- release carries no obesity rows at all for Kentucky and Pennsylvania. Rows
181
- without a value are dropped, and the national row is kept apart from the
182
- counties rather than counted as one.
183
-
184
- ## Notes on the Climate at a Glance download
185
-
186
- - The download is a CSV, not JSON: two comment lines, a header, then one row
187
- per year as `YYYYMM,value` in degrees Fahrenheit. `parseNceiAnnualTemperatureCsv`
188
- skips the comments and the header, and stops on any other line shape rather
189
- than dropping it.
190
- - The URL carries a window length and a window end month. Twelve months ending
191
- in December is the calendar year, which is the number a story about a year
192
- can compare. A shorter window ending in December would be that month alone,
193
- and the parser refuses a file with more than one row per year.
194
- - The current year joins the file only once December has closed it, so the
195
- newest row is the last complete calendar year. The 2026 download is dated
196
- 2026-09-26 and ends at 2025.
197
-
198
- ## Notes on the CO-OPS sea level record
199
-
200
- - One request covers the whole window: the `monthly_mean` product answers one
201
- row per month rather than one row per reading, so 170 years cost one call
202
- and about 460 KB. The endpoint is keyless.
203
- - Values are metres against the station's MSL datum, the average of hourly
204
- heights over the 1983-2001 National Tidal Datum Epoch. Reading the whole
205
- record against one fixed epoch is what makes two years comparable.
206
- - The API answers a row for every month in the window and leaves the value
207
- blank when it has none, which is how the early gaps arrive (1920 holds
208
- seven months, 1861 and 1879-1892 hold none). Blank rows are dropped, and a
209
- year's average is taken over the months it does have, so `monthCount` says
210
- how much of the year a value covers.
211
- - Errors come back in the body with HTTP 200, as
212
- `{"error":{"message":"..."}}`, so the parser checks for that before reading
213
- the data. A wrong product name answers with plain text instead, which fails
214
- the shape check.
215
-
216
- ## Notes on the Treasury average interest rate
217
-
218
- - One keyless request covers the whole run. The dataset answers a row per
219
- month per security type, and the filter pins the response to
220
- `security_type_desc:eq:Interest-bearing Debt`, which is the portfolio total
221
- the bureau's own monthly statement is built from. The other rows, the rates
222
- on bills, notes, bonds, and non-marketable debt, never reach the parser.
223
- - The rate is the average across everything outstanding, not the rate on
224
- anything issued today. That is why it moves slowly: a new bill reprices a
225
- fraction of the portfolio, and a bond issued in 2019 still pays its own
226
- coupon.
227
- - A page holds at most 10,000 rows and the run is around 300, so the adapter
228
- asks for the whole series in one request and does not page. A run longer
229
- than the cap would be a change worth making here rather than a silent
230
- truncation, so the row limit is a named constant.
231
- - Each row is dated to the last day of its month. A month the agency could
232
- not compute would arrive without a value, and those rows are dropped.
233
- - A request the API rejects answers with an HTTP error and a body of
234
- `{"error":"Invalid Query Param","message":"..."}`, which the parser reports
235
- as an API error rather than a shape error.
236
-
237
- ## Notes on the USGS peak-flow record
238
-
239
- - The adapter reads the `peaks` collection on the USGS Water Data OGC API
240
- (`https://api.waterdata.usgs.gov/ogcapi/v0`), not the older
241
- `waterservices.usgs.gov` endpoints. The older statistics service answers
242
- `503 Service unavailable` to roughly half of scripted requests, which is
243
- enough to fail a build; the OGC API answered every request in the same
244
- window. The daily-values service on the older host still works and is worth
245
- using when a daily series is what a story needs.
246
- - Every number in a row arrives as a string, including the peak itself, so
247
- the parser converts once and keeps the number. `value` blank means the
248
- agency filed no peak for that year, and the row is dropped rather than
249
- read as zero.
250
- - The rows are dated by **water year**, which starts on 1 October. A peak on
251
- 1955-10-08 is filed under water year 1956, so `waterYear` and
252
- `calendarYear` are separate fields and are not always the same number. The
253
- record is sorted and charted by water year, because that is the unit the
254
- agency files and the unit a flood season belongs to.
255
- - The record has holes. At the default gauge there is no row for the 17
256
- water years from 1845 to 1861, and the series lists them in
257
- `missingWaterYears` rather than drawing them as zero, because a year with
258
- no filing is not a year the river ran dry.
259
- - A gauge with several peaks in one water year is filed once, with the
260
- largest. The adapter does not de-duplicate, so a response carrying two rows
261
- for one water year would be a change in the source worth seeing.
262
- - Rows carry the agency's qualifiers, for example `UNKNOWNREGULATION` where
263
- the agency could not tell whether works upstream changed the flow, or
264
- `MAXDAILYMEAN` where the peak is a daily mean rather than an instantaneous
265
- reading. They are kept on each year rather than dropped, so the copy can be
266
- honest about them.
267
- - One keyless request covers the whole record. The default gauge answers 165
268
- water years in one page of about 126 KB.
269
-
270
- ## Notes on the CPSC recall service
271
-
272
- - The adapter reads
273
- `https://www.saferproducts.gov/RestWebServices/Recall`, the same service
274
- behind the public SaferProducts.gov search. It is keyless.
275
- - One request covers one calendar year. A range wider than a year fails: the
276
- service answers HTTP 200 with a single row whose title reads
277
- `Error retrieving Recalls: The underlying provider failed on Open.` The
278
- adapter reads a year at a time for that reason, and the parser throws on
279
- that row rather than counting it as a recall.
280
- - A year of rows is heavy as JSON. 2025 is 420 recalls in about 1.3 MB, so the
281
- default window of 2014 to the current year costs about 11 MB across 13
282
- requests. A year with no recall keeps a zero row in `years` rather than
283
- disappearing from the chart.
284
- - The newest year in the window is the year the agency is still filling, so
285
- `busiestCompleteYear` and `quietestCompleteYear` are picked from the years
286
- before it. `completeYearCount` says how many those were.
287
- - `ManufacturerCountries` names every country a recall touches, so a recall
288
- can list several and the country counts add to more than the recall count.
289
- In the 2014 to 2026 window China appears on 2,312 of 3,986 recalls, the
290
- United States on 753, and 122 countries appear at least once.
291
- - `RemedyOptions` is nearly an enum (Refund, Repair, Replace, Dispose, New
292
- Instructions, Label, Inspect) with two junk rows in it: one carries `R` and
293
- one carries a whole paragraph of consumer instructions. The adapter keeps
294
- whatever the agency sends and sorts the counts, so the tail of the list
295
- shows the mess rather than hiding it.
296
- - The service holds recalls from the 1970s. The adapter starts at 2014, which
297
- keeps the window to 13 requests and to the years a reading of the current
298
- trend needs.
299
- - The committed snapshot (`cpsc-product-recalls-2026-10-02.json`) holds the
300
- folded counts, not the raw years, because a year of raw rows is about a
301
- megabyte and the counts are a few kilobytes. The second fixture
302
- (`cpsc-recall-rows-2014-2026-sampled-2026-10-02.json`) holds the first three
303
- recalls of every year in the window, which is what the registry test answers
304
- each per-year request with.
305
-
306
- ## Notes on the Consumer Complaint Database
307
-
308
- - The adapter reads
309
- `https://www.consumerfinance.gov/data-research/consumer-complaints/search/api/v1/`,
310
- the search endpoint behind the public Consumer Complaint Database. It is
311
- keyless. The bureau answers a scripted request with 403 and a request with a
312
- declared user agent with 200.
313
- - The search API computes its term aggregations on every request, which takes
314
- the body from about fifteen kilobytes to about four hundred. Adding
315
- `no_aggs=true` drops them, so the adapter asks for `size=0&no_aggs=true` and
316
- reads `hits.total.value` when it only needs a count, and makes one extra
317
- request without `no_aggs` for the product and company tallies.
318
- - The API answers one question per request and has no date sort, so the window
319
- costs one request per year plus one for the tallies plus one or two to find
320
- the newest day. `findNewestCfpComplaintDate` walks back from today and stops
321
- at the first day with a complaint.
322
- - The first year in the window, 2011, holds only December: the bureau
323
- published its first complaints on 1 December 2011. It is kept rather than
324
- dropped, and the page's data note says the first bar covers one month.
325
- - The product tally splits credit reporting across more than one label because
326
- the bureau changed its product taxonomy over the years. The adapter keeps
327
- whatever the API sends rather than merging labels, so a reader of
328
- `topProducts` should not assume the labels are stable.
329
- - The committed snapshot (`cfpb-consumer-complaints-2026-10-03.json`) holds
330
- the folded counts, not the eighteen million raw rows. The registry test
331
- answers each per-year request, the newest-day probe, and the tally request
332
- from that one fixture.
333
-
334
- ## Checks
338
+ `estimatetax-income-tax`:
335
339
 
336
- ```sh
337
- npm run test --workspace @usa-open-data-connectors/usa-sources
338
- npm run test:smoke --workspace @usa-open-data-connectors/usa-sources # hits the live API
340
+ ```ts
341
+ import { fetchEstimateTaxCalculation } from '@usa-open-data-connectors/usa-sources';
342
+
343
+ const tax = await fetchEstimateTaxCalculation({
344
+ query: { income: 85_000, filingStatus: 'single', state: 'NY', city: 'new-york-city' },
345
+ });
346
+ console.log(tax.totals.totalTax, tax.totals.effectiveRate, tax.sources.federal.url);
347
+ ```
348
+
349
+ `ncbi-pubmed-search`:
350
+
351
+ ```ts
352
+ import { fetchNcbiPubmedSearch } from '@usa-open-data-connectors/usa-sources';
353
+
354
+ const result = await fetchNcbiPubmedSearch({
355
+ term: '"long COVID"[tiab] AND 2025[edat]',
356
+ retmax: 0,
357
+ });
358
+ console.log(result.totalCount, result.queryTranslation);
339
359
  ```
360
+
361
+ The public API is `NCBI_EUTILS_API_BASE`, `NCBI_PUBMED_ESEARCH_PATH`,
362
+ `NCBI_PUBMED_DEFAULT_TERM`, `NCBI_PUBMED_DEFAULT_RETMAX`, `buildNcbiPubmedSearchUrl`,
363
+ `parseNcbiPubmedSearchPayload`, `fetchNcbiPubmedSearch`, and
364
+ `ncbiPubmedSearchAdapter`, with the `NcbiPubmedSearchResult` and
365
+ `NcbiPubmedTranslation` types. The parser validates the response with a zod
366
+ schema over `header` and `esearchresult`, where `esearchresult` can carry
367
+ `count`, `retmax`, `retstart`, `idlist`, `querytranslation`, `translationset`,
368
+ `error`, and `warninglist`. The counts arrive as strings and are converted once.
369
+ An `esearchresult.error` is thrown as `UsSourceApiError` even though the HTTP
370
+ status is 200.
371
+
372
+ `clinicaltrials-studies`:
373
+
374
+ ```ts
375
+ import {
376
+ buildClinicalTrialsStudiesUrl,
377
+ parseClinicalTrialsStudiesPayload,
378
+ } from '@usa-open-data-connectors/usa-sources';
379
+
380
+ const url = new URL(buildClinicalTrialsStudiesUrl({ pageSize: 1000 }));
381
+ url.searchParams.set('query.cond', 'long COVID');
382
+ url.searchParams.set(
383
+ 'fields',
384
+ 'NCTId,BriefTitle,OverallStatus,StartDate,StudyType,Phase,Condition,LeadSponsorName,EnrollmentCount,EnrollmentType,LastUpdatePostDate'
385
+ );
386
+ const response = await fetch(url);
387
+ if (!response.ok) {
388
+ throw new Error(`HTTP ${response.status} from ${url.toString()}`);
389
+ }
390
+ const page = parseClinicalTrialsStudiesPayload(await response.json());
391
+ console.log(page.studyCount, page.statusCounts[0], page.nextPageToken);
392
+ ```
393
+
394
+ The public API is `CLINICALTRIALS_STUDIES_API_BASE`,
395
+ `CLINICALTRIALS_STUDIES_PAGE_SIZE`, `CLINICALTRIALS_STUDIES_SOURCE_ID`,
396
+ `buildClinicalTrialsStudiesUrl`, `parseClinicalTrialsStudiesPayload`,
397
+ `fetchClinicalTrialsStudies`, and `clinicalTrialsStudiesAdapter`, with the
398
+ `ClinicalTrialStudy`, `ClinicalTrialStatusCount`, and `ClinicalTrialStudyPage`
399
+ types. The parser validates the response with a zod schema over `studies` and
400
+ `nextPageToken`. Each study carries an optional `protocolSection` with
401
+ `identificationModule` (`nctId`, `briefTitle`), `statusModule`
402
+ (`overallStatus`, `startDateStruct`, `lastUpdatePostDateStruct`),
403
+ `sponsorCollaboratorsModule` (`leadSponsor.name`), `conditionsModule`
404
+ (`conditions`), and `designModule` (`studyType`, `phases`, `enrollmentInfo`). A
405
+ study without a registry id or a brief title throws `UsSourceParseError`, and an
406
+ empty page also throws.
407
+
408
+ The adapter reads one page of the register and keeps the id, title, status,
409
+ study type, phases, conditions, lead sponsor, enrollment, start date, and last
410
+ update date. The default page size is ten. `buildClinicalTrialsStudiesUrl` takes
411
+ only `pageSize`, so a filtered story builds the URL and adds `query.cond`,
412
+ `query.term`, or `filter.*` itself before calling the parser. The full protocol
413
+ record is large: 737 long COVID studies were 12.2 MB without a field projection
414
+ and 426 KB with the eleven fields above on 2026-10-20. The `fields` parameter
415
+ accepts those short field names, so a caller does not need to ask for the whole
416
+ record.
417
+
418
+ Three shape notes. A start date can be day precision (`2024-12-10`) or month
419
+ precision (`2026-05`), and the adapter keeps the string as the registry wrote
420
+ it. A start date can be planned rather than actual, so a recent year can still
421
+ gain studies. `query.cond` is the registry's own condition search rather than a
422
+ filter over the `conditions` array; a long COVID search returned 737 studies on
423
+ 2026-10-20 while only 315 of them carried a condition string containing "long
424
+ COVID", because keywords and related terms also match. The response has no
425
+ total count unless the request adds `countTotal=true`, so count `studies` for
426
+ the page or ask for the total separately.
427
+
428
+ ## Notes and limits
429
+
430
+ - `bls-unemployment-rate`: the public API refuses a window longer than ten years. `fetchBlsSeries` splits a longer range into windows, so two decades cost three calls once the range reaches the current year.
431
+ - `bls-unemployment-rate`: keyless access allows 25 series queries a day. A registered key allows 500, and the adapter takes `apiKey` for the day that matters.
432
+ - `bls-unemployment-rate`: the agency uses `-` for a month it could not publish and `M13` for an annual average. The parser drops both, so one year holds twelve monthly values.
433
+ - `usgs-hawaii-earthquakes`: the event query answers a window of any length, one request per window. The caller sets the box, the magnitude floor, and the dates.
434
+ - `usgs-hawaii-earthquakes`: the feed mixes tectonic earthquakes with quarry blasts, explosions, and ice quakes. A row can arrive without a magnitude. The parser keeps only tectonic earthquakes that carry a magnitude and a depth.
435
+ - `usgs-hawaii-earthquakes`: the catalogue answers newest first. `parseUsgsEarthquakes` returns oldest first, so a caller reading index 0 gets the start of the window.
436
+ - `cdc-county-obesity`: the resource id is per release. This adapter reads the 2025 release. Its estimates come from the 2023 Behavioral Risk Factor Surveillance System (BRFSS) and the Census Bureau 2023 county population estimates. A new release gets a new id, so a caller pinned to this one keeps reading the numbers it was written against.
437
+ - `cdc-county-obesity`: every row is a model-based estimate, not a count. The release publishes each measure as crude prevalence and as age-adjusted prevalence. The adapter reads the crude rows, because age-adjusted rows answer a different question.
438
+ - `cdc-county-obesity`: a county the model could not estimate arrives with no value. The release carries no obesity rows for Kentucky and Pennsylvania. The parser drops rows without a value and keeps the national row apart from the counties.
439
+ - `ncei-annual-temperature`: the download is a CSV, not JSON. It holds two comment lines, a header, then one row per year as `YYYYMM,value` in degrees Fahrenheit. `parseNceiAnnualTemperatureCsv` skips the comments and the header, and stops on any other line shape rather than dropping it.
440
+ - `ncei-annual-temperature`: the URL carries a window length and a window end month. Twelve months ending in December is the calendar year. The parser refuses a file with more than one row per year.
441
+ - `ncei-annual-temperature`: the current year joins the file only after December closes it, so the newest row is the last complete calendar year. The 2026 download is dated 2026-09-26 and ends at 2025.
442
+ - `noaa-sea-level`: one request covers the whole window. The `monthly_mean` product answers one row per month rather than one row per reading, so 170 years cost one call and about 460 KB. The endpoint is keyless.
443
+ - `noaa-sea-level`: values are metres against the station Mean Sea Level (MSL) datum, the average of hourly heights over the 1983-2001 National Tidal Datum Epoch. Reading the whole record against one fixed epoch is what makes two years comparable.
444
+ - `noaa-sea-level`: the API answers a row for every month in the window and leaves the value blank when it has none. Blank rows are dropped. A year's average is taken over the months it has, so `monthCount` says how much of the year a value covers.
445
+ - `noaa-sea-level`: errors come back in the body with HTTP 200, as `{"error":{"message":"..."}}`. The parser checks for that before reading the data. A wrong product name answers with plain text instead, which fails the shape check.
446
+ - `treasury-avg-interest-rate`: one keyless request covers the whole run. The dataset answers a row per month per security type. The filter pins the response to `security_type_desc:eq:Interest-bearing Debt`. That is the portfolio total the bureau's monthly statement is built from. The other rows never reach the parser. They hold the rates on bills, notes, bonds, and non-marketable debt.
447
+ - `treasury-avg-interest-rate`: the rate is the average across everything outstanding, not the rate on anything issued today. That is why it moves slowly. A new bill reprices a fraction of the portfolio, and a bond issued in 2019 still pays its own coupon.
448
+ - `treasury-avg-interest-rate`: a page holds at most 10,000 rows and the run is around 300. The adapter asks for the whole series in one request and does not page. The row limit is a named constant.
449
+ - `treasury-avg-interest-rate`: each row is dated to the last day of its month. A month the agency could not compute arrives without a value, and the parser drops those rows.
450
+ - `treasury-avg-interest-rate`: a request the API rejects answers with an HTTP error and a body of `{"error":"Invalid Query Param","message":"..."}`. The parser reports an API error rather than a shape error.
451
+ - `treasury-debt-to-penny`: one keyless request covers the whole run if the row limit is raised. The file starts on 1993-04-01 and held 8,414 business-day rows on 2026-10-15, about 2.8 MB. The default `TREASURY_DEBT_TO_PENNY_ROW_LIMIT` is 10 and the API returns oldest first, so the default page is the first ten days of 1993 rather than recent debt. Pass `{ rowLimit: 10_000 }` to read the whole series in one request.
452
+ - `treasury-debt-to-penny`: the API caps `page[size]` at 10,000 rows and the run still fits under that cap. A caller asking for more gets the cap's worth, so check `dayCount` against the API's `meta.count` when the run grows past it.
453
+ - `treasury-debt-to-penny`: every value arrives as a string, and a figure the agency could not report arrives as the literal text `"null"`. The adapter reads that as null and drops a row with no total. The first row, 1993-04-01, has a total but no public or intragovernmental split.
454
+ - `treasury-debt-to-penny`: `tot_pub_debt_out_amt` equals `debt_held_public_amt` plus `intragov_hold_amt` on the rows that carry all three. Keep the split fields when the story compares public debt with intragovernmental holdings, and use the total field for the headline.
455
+ - `treasury-debt-to-penny`: rows cover business days only. A weekend or federal holiday has no row, so a daily series has gaps by design. Use the `record_date` rather than an index when aligning debt with another daily series.
456
+ - `treasury-debt-to-penny`: `sort=-record_date` works on the API but the URL builder does not set it. The helper reads the oldest-first default, which keeps a fixture and a live response in the same order. A rejected request answers with an HTTP error and a body of `{"error":"...","message":"..."}`.
457
+ - `usgs-peak-streamflow`: the adapter reads the `peaks` collection on the USGS Water Data OGC API. It does not read the older `waterservices.usgs.gov` endpoints. The older statistics service answers `503 Service unavailable` to about half of scripted requests, which is enough to fail a build. The daily-values service on the older host still works when a daily series is what a story needs.
458
+ - `usgs-peak-streamflow`: every number in a row arrives as a string, including the peak itself. The parser converts once and keeps the number. A blank `value` means the agency filed no peak for that year. The parser drops that row.
459
+ - `usgs-peak-streamflow`: the rows are dated by water year, which starts on 1 October. A peak on 1955-10-08 is filed under water year 1956, so `waterYear` and `calendarYear` are separate fields and are not always the same number.
460
+ - `usgs-peak-streamflow`: the record has holes. At the default gauge there is no row for the 17 water years from 1845 to 1861. The series lists them in `missingWaterYears` rather than drawing them as zero.
461
+ - `usgs-peak-streamflow`: a gauge with several peaks in one water year is filed once, with the largest. The adapter does not de-duplicate, so two rows for one water year would be a change in the source worth seeing.
462
+ - `usgs-peak-streamflow`: rows carry the agency's qualifiers, for example `UNKNOWNREGULATION` or `MAXDAILYMEAN`. The parser keeps them on each year.
463
+ - `usgs-peak-streamflow`: one keyless request covers the whole record. The default gauge answers 165 water years in one page of about 126 KB.
464
+ - `cpsc-product-recalls`: the adapter reads the same service behind the public SaferProducts.gov search. It is keyless.
465
+ - `cpsc-product-recalls`: one request covers one calendar year, and a wider range fails. The service answers HTTP 200 with a single row. Its title reads `Error retrieving Recalls: The underlying provider failed on Open.` The adapter reads a year at a time for that reason. The parser throws on that row rather than counting it as a recall.
466
+ - `cpsc-product-recalls`: a year of rows is heavy as JSON. 2025 is 420 recalls in about 1.3 MB, so the default window of 2014 to the current year costs about 11 MB across 13 requests. A year with no recall keeps a zero row in `years`.
467
+ - `cpsc-product-recalls`: the newest year is the year the agency is still filling, so `busiestCompleteYear` and `quietestCompleteYear` are picked from the years before it. `completeYearCount` says how many those were.
468
+ - `cpsc-product-recalls`: `ManufacturerCountries` names every country a recall touches, so a recall can list several countries. The country counts add to more than the recall count. In the 2014 to 2026 window China appears on 2,312 of 3,986 recalls. The United States appears on 753, and 122 countries appear at least once.
469
+ - `cpsc-product-recalls`: `RemedyOptions` is nearly an enum (Refund, Repair, Replace, Dispose, New Instructions, Label, Inspect) with two junk rows. One carries `R` and one carries a whole paragraph of consumer instructions. The adapter keeps whatever the agency sends and sorts the counts.
470
+ - `cpsc-product-recalls`: the service holds recalls from the 1970s. The adapter starts at 2014, which keeps the window to 13 requests.
471
+ - `cfpb-consumer-complaints`: the adapter reads the search endpoint behind the public Consumer Complaint Database. It is keyless. The bureau answers a scripted request with 403 and a request with a declared user agent with 200.
472
+ - `cfpb-consumer-complaints`: the search API computes its term aggregations on every request, which takes the body from about fifteen kilobytes to about four hundred. The adapter asks for `size=0&no_aggs=true` and reads `hits.total.value` when it only needs a count. It makes one extra request without `no_aggs` for the product and company tallies.
473
+ - `cfpb-consumer-complaints`: the API answers one question per request and has no date sort. The window costs one request per year plus one for the tallies plus one or two to find the newest day. `findNewestCfpComplaintDate` walks back from today. It stops at the first day with a complaint.
474
+ - `cfpb-consumer-complaints`: the first year in the window, 2011, holds only December. The bureau published its first complaints on 1 December 2011. The adapter keeps the year, and the page's data note says the first bar covers one month.
475
+ - `cfpb-consumer-complaints`: the product tally splits credit reporting across more than one label because the bureau changed its product taxonomy over the years. The adapter keeps whatever the API sends, so the labels in `topProducts` are not stable.
476
+ - `cfpb-consumer-complaints`: the committed snapshot holds the folded counts, not the eighteen million raw rows.
477
+ - `fdic-bank-directory`: the default request asks for ten rows and sets no active filter. The full file held 27,835 records on 2026-10-16, including historical institutions the FDIC marks inactive, so the first page is not a current bank count. Add `filters=ACTIVE:1` to the URL to read the active directory, which held 4,219 institutions that day.
478
+ - `fdic-bank-directory`: the filtered count arrives in both `meta.total` and `totals.count`. Both fields describe the filtered result, not the size of the unfiltered file, so `totalBanks` is the active count when the request carries the active filter.
479
+ - `fdic-bank-directory`: `banks.data.fdic.gov` answers with a redirect to `api.fdic.gov`. `fetch` follows it. Both hosts answer 200 to a scripted request, and neither needs a key.
480
+ - `fdic-bank-directory`: `FDIC_BANK_FIELDS` asks for `NAME,CITY,STALP`, but the parser requires `ID` as well. The service adds `ID` to the response even when it is not in `fields`, but a caller should ask for `ID,NAME,CITY,STALP` so the request and the parser agree without relying on that behaviour.
481
+ - `fdic-bank-directory`: each row sits inside a `data` wrapper with a separate `score` field. The parser reads `row.data`, not the wrapper.
482
+ - `fdic-bank-directory`: the active rows cover 55 state-level codes, not 50. DC, Puerto Rico, Guam, Micronesia, and the US Virgin Islands each hold active banks, so a caller that labels the counts as states will overstate the map.
483
+ - `fdic-bank-directory`: the directory counts institutions at the state of the main office. It is not a branch count, and a bank with branches across several states appears once.
484
+ - `sec-edgar-filings`: the SEC asks every caller to identify itself with a User-Agent header. The adapter sends `SEC_EDGAR_USER_AGENT`, which carries a placeholder contact address. A deployment should override it with its own application name and address before the request leaves a shared host.
485
+ - `sec-edgar-filings`: the submissions file carries the last 1,000 filings, not the issuer's full history. Apple's block ran from 18 September 2015 to 16 October 2026 on 2026-10-19. A caller that needs older rows has to read the filing archive rather than this endpoint.
486
+ - `sec-edgar-filings`: the default CIK is Apple's, `0000320193`. `fetchSecEdgarFilings({ cik })` accepts one to ten digits and zero-pads the value, so `320193` and `0000320193` ask for the same issuer.
487
+ - `sec-edgar-filings`: the `recent` block is a set of parallel arrays, not one object per filing. A column the form does not carry arrives as an empty string. The parser turns a blank report date or document description into null, and stops rather than guessing when a column length differs from the accession list.
488
+ - `sec-edgar-filings`: a filing's `size` is in bytes and describes the submission file. It is not the size of the main document, and it is not a count of pages.
489
+ - `epa-envirofacts-facilities`: the state filter takes the value directly after the column name, as `TRI_FACILITY/STATE_ABBR/CA/rows/0:9999/JSON`. Adding an `equals` segment is accepted but ignored, and the service then returns the first rows of the whole table instead of the state asked for.
490
+ - `epa-envirofacts-facilities`: the default row window is the first ten rows. `fetchEpaEnvirofactsFacilities` takes a state but not a row window, so a caller that needs the whole state builds the URL with `buildEpaEnvirofactsFacilitiesUrl({ stateAbbr, rowWindow })` and parses the response with `parseEpaEnvirofactsFacilitiesPayload`.
491
+ - `epa-envirofacts-facilities`: the table holds facility records, not release amounts. It is the Toxics Release Inventory facility register, so a row says where a site is and whether the EPA marks it closed (`fac_closed_ind` of `1`), not how much it released in a year.
492
+ - `epa-envirofacts-facilities`: every key arrives in lower case, and the parent company is sometimes the literal text `NA`. The parser reads that as null rather than a company name.
493
+ - `epa-envirofacts-facilities`: a state response can be large. California held 5,110 facilities on 2026-10-19, about 7.7 MB with the full record and about 1 MB after trimming to the ten fields the parser keeps.
494
+ - `estimatetax-income-tax`: one keyless request answers the federal, state, and local income tax for one salary. The endpoint is limited to 60 requests a minute per address.
495
+ - `estimatetax-income-tax`: the response is a calculation from the publisher's own engine, not a row from an official statistical release. Every layer cites the IRS or state document it was read from, so the source and the date are visible in the response.
496
+ - `estimatetax-income-tax`: the engine assumes the standard deduction unless `preTaxDeductions` is supplied. It does not model itemised deductions, capital gains, or credits, and the response carries that disclaimer.
497
+ - `estimatetax-income-tax`: the default query is the $85,000 single filer in New York City recorded in the fixture. A federal-only query sets `state` to null.
498
+ - `ncbi-pubmed-search`: the default term is `asthma` and the default page is ten ids. Pass `term` for another query and `retmax: 0` when only the match count is needed; the count still arrives.
499
+ - `ncbi-pubmed-search`: `[dp]` means Date - Publication, and PubMed can hold both an electronic and a print date for one record. Year buckets built with `[dp]` can overlap, so their counts can add to more than the unfiltered total. Use `[edat]` (Date - Entry) when each record must fall in exactly one year.
500
+ - `ncbi-pubmed-search`: keyless NCBI allows about three requests a second. The adapter makes one request per call and does not add a delay, so a caller reading six years must space the calls itself.
501
+ - `ncbi-pubmed-search`: a query field tag such as `[tiab]` restricts the match to the title and abstract. Without a tag, PubMed maps the words through MeSH and searches every indexed field, which answers a wider and different count. `queryTranslation` shows what PubMed actually ran.
502
+ - `ncbi-pubmed-search`: a bad query answers HTTP 200 with `esearchresult.error` instead of a result block. The parser turns that into `UsSourceApiError`, so a caller does not count an error as zero papers.
503
+ - `cdc-socrata-catalogue`: the default request asks for ten rows. The discovery API answers the whole catalogue when the limit is high enough, and the CDC catalogue held 1,477 entries on 2026-10-19. Call `buildCdcSocrataCatalogueUrl({ limit: 10_000 })` or pass `limit` to `fetchCdcSocrataCatalogue` when you need every entry. A limit below the catalogue size truncates the response without an error.
504
+ - `cdc-socrata-catalogue`: each row is a catalogue entry, not a dataset record. `assetType` can be `dataset`, `file`, `href`, `filter`, `chart`, `story`, or `map`. A category can hold one type or a mix, so count the asset type before you describe a category as a dataset collection.
505
+ - `cdc-socrata-catalogue`: an entry without a category has `null` in the response. The adapter keeps the null on the entry and counts it under `Uncategorised` in `categories`. That bucket is an adapter label, not a category the CDC assigned.
506
+ - `cdc-socrata-catalogue`: the full response is large because each row carries portal metadata. The 1,477-row response was about 7.8 MB on 2026-10-19. The parser keeps nine fields, and a fixture trimmed to those fields was about 490 KB.
507
+ - `cdc-socrata-catalogue`: the discovery API can carry rights and metadata fields, but this parser does not keep them. Check the portal entry before you reuse an item.
508
+ - `nyc-open-data-catalogue`: the discovery API answers a bare JSON array of catalogue entries, not dataset rows. An entry can arrive without a category, which the adapter counts under `Uncategorised`.
509
+ - `nyc-open-data-catalogue`: the page captured on 2026-10-19 mixes datasets and maps. The adapter counts both, and keeps the view and download totals for the page it read.
510
+ - Unit tests use the committed fixtures and run offline. Run them with `npm run test --workspace @usa-open-data-connectors/usa-sources`.
511
+ - Smoke tests hit the live APIs and need `RUN_SMOKE=1`. Run them with `npm run test:smoke --workspace @usa-open-data-connectors/usa-sources`.
512
+
513
+ ## Data sources and licences
514
+
515
+ The data comes from US federal agencies and the open data catalogue services they publish on.
516
+
517
+ | Publisher | Source URL |
518
+ | --- | --- |
519
+ | Bureau of Labor Statistics | `https://api.bls.gov/publicAPI/v2/timeseries/data` |
520
+ | US Geological Survey | `https://earthquake.usgs.gov/fdsnws/event/1/query` and `https://api.waterdata.usgs.gov/ogcapi/v0/collections/peak/items` |
521
+ | Centers for Disease Control and Prevention | `https://data.cdc.gov/resource/swc5-untb.json` and `https://data.cdc.gov/api/views.json` |
522
+ | NOAA National Centers for Environmental Information | `https://www.ncei.noaa.gov/access/monitoring/climate-at-a-glance` |
523
+ | NOAA Center for Operational Oceanographic Products and Services | `https://api.tidesandcurrents.noaa.gov/api/prod/datagetter` |
524
+ | US Department of the Treasury | `https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v2/accounting/od/avg_interest_rates` and `https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v2/accounting/od/debt_to_penny` |
525
+ | Federal Emergency Management Agency | `https://www.fema.gov/api/open/v1/FemaWebDisasterDeclarations` |
526
+ | US Food and Drug Administration | `https://api.fda.gov/food/enforcement.json` |
527
+ | US Consumer Product Safety Commission | `https://www.saferproducts.gov/RestWebServices/Recall` |
528
+ | Consumer Financial Protection Bureau | `https://www.consumerfinance.gov/data-research/consumer-complaints/search/api/v1/` |
529
+ | US Securities and Exchange Commission | `https://data.sec.gov/submissions/CIK0000320193.json` |
530
+ | Federal Deposit Insurance Corporation | `https://banks.data.fdic.gov/api/institutions` |
531
+ | ClinicalTrials.gov | `https://clinicaltrials.gov/api/v2/studies` |
532
+ | National Weather Service | `https://api.weather.gov/points/38.8894,-77.0352` |
533
+ | USAspending | `https://api.usaspending.gov/api/v2/references/toptier_agencies/` |
534
+ | Environmental Protection Agency | `https://data.epa.gov/efservice/TRI_FACILITY/STATE_ABBR/RI/rows/0:9/JSON` |
535
+ | National Center for Biotechnology Information | `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi` |
536
+ | Bureau of Transportation Statistics | `https://data.bts.gov/api/views.json` |
537
+ | EstimateTax | `https://estimatetax.net/api/v1/calculate` |
538
+ | HealthData.gov | `https://healthdata.gov/api/views.json` |
539
+ | New York City Open Data | `https://data.cityofnewyork.us/api/views.json` |
540
+
541
+ Each publisher sets its own terms for its data. The Socrata discovery responses can carry rights and
542
+ metadata fields, but the catalogue adapters in this package keep a subset of the entry fields and do not
543
+ retain those terms. Check the licence on the portal entry before you reuse an item. A catalogue can list
544
+ entries under terms such as these:
545
+
546
+ - USA.gov public-domain label: `http://www.usa.gov/publicdomain/label/1.0/`
547
+ - USA.gov government works: `https://www.usa.gov/government-works`
548
+ - Open Database License (ODbL): `http://opendefinition.org/licenses/odc-odbl/`
549
+ - Public Domain Dedication and License (PDDL): `http://opendatacommons.org/licenses/pddl/1.0/`
550
+
551
+ Check the licence field on each entry before you reuse it. This section is not legal advice. The package
552
+ licence covers the code, not the data.
553
+
554
+ EstimateTax is a private publisher rather than a federal agency. Its API terms ask for attribution
555
+ and the source document alongside any figure you display, and the calculation response cites the
556
+ official document behind each tax figure. No open licence is stated for the API output, so credit
557
+ EstimateTax and the cited authority when you republish a calculation.
558
+
559
+ ## Package licence
560
+
561
+ MIT. See LICENSE.
562
+
563
+ ## Links
564
+
565
+ - npm: https://www.npmjs.com/package/@usa-open-data-connectors/usa-sources
566
+ - source: https://github.com/olitreadwell/usa-open-data-connectors/tree/main/packages/usa-sources
567
+ - docs: ../../docs/CONNECTOR_DISCOVERY.md