@usa-open-data-connectors/usa-sources 0.1.4 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -5
- package/dist/callookCallsigns.d.ts +119 -0
- package/dist/callookCallsigns.d.ts.map +1 -0
- package/dist/callookCallsigns.js +264 -0
- package/dist/callookCallsigns.js.map +1 -0
- package/dist/collegeScorecardInstitutions.d.ts +133 -0
- package/dist/collegeScorecardInstitutions.d.ts.map +1 -0
- package/dist/collegeScorecardInstitutions.js +339 -0
- package/dist/collegeScorecardInstitutions.js.map +1 -0
- package/dist/http.d.ts +5 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +33 -25
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/ncbiPubmedSearch.d.ts +4 -0
- package/dist/ncbiPubmedSearch.d.ts.map +1 -1
- package/dist/ncbiPubmedSearch.js +9 -1
- package/dist/ncbiPubmedSearch.js.map +1 -1
- package/dist/nextBusTransitVehicles.d.ts +102 -0
- package/dist/nextBusTransitVehicles.d.ts.map +1 -0
- package/dist/nextBusTransitVehicles.js +182 -0
- package/dist/nextBusTransitVehicles.js.map +1 -0
- package/dist/nwsPointForecast.d.ts.map +1 -1
- package/dist/nwsPointForecast.js +13 -2
- package/dist/nwsPointForecast.js.map +1 -1
- package/dist/publicDataHubHospital.d.ts +116 -0
- package/dist/publicDataHubHospital.d.ts.map +1 -0
- package/dist/publicDataHubHospital.js +175 -0
- package/dist/publicDataHubHospital.js.map +1 -0
- package/dist/registry.d.ts +2 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +21 -3
- package/dist/registry.js.map +1 -1
- package/dist/secEdgarFilings.d.ts.map +1 -1
- package/dist/secEdgarFilings.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@ Uniform TypeScript adapters for US public data sources, with live probes and off
|
|
|
4
4
|
|
|
5
5
|
## What this package does
|
|
6
6
|
|
|
7
|
-
- Exposes
|
|
7
|
+
- Exposes 28 source adapters behind one interface.
|
|
8
8
|
- Fetches live data from each source and parses it into plain objects.
|
|
9
|
-
- Loads
|
|
9
|
+
- Loads committed fixtures in tests only; a live fetch never falls back.
|
|
10
10
|
- Publishes the `US_DATA_SOURCES` registry for the API and the CLI.
|
|
11
11
|
- Exports named functions for each source, with that source's query parameters.
|
|
12
12
|
|
|
@@ -53,6 +53,10 @@ console.log(probes.map((probe) => `${probe.id}: ${probe.ok ? 'ok' : probe.status
|
|
|
53
53
|
| `bts-transportation-stats` | Bureau of Transportation Statistics | The BTS Socrata catalogue, one row per dataset, chart, or story. |
|
|
54
54
|
| `estimatetax-income-tax` | EstimateTax | US federal, state, and local income tax for one salary, with the official document behind every figure. |
|
|
55
55
|
| `nyc-open-data-catalogue` | New York City Open Data | The NYC Socrata catalogue, one row per published dataset or map. |
|
|
56
|
+
| `callook-callsigns` | Callook.info | US amateur radio callsign records from the FCC licence file, with the licence class, location, and expiry. |
|
|
57
|
+
| `college-scorecard-institutions` | College Scorecard, US Department of Education | Institution-level enrollment, cost, completion, debt, and earnings ten years after entry for US colleges. |
|
|
58
|
+
| `publicdatahub-hospital-quality` | PublicDataHub, from the Centers for Medicare & Medicaid Services | Medicare-certified hospital quality measures, with national ranks and provenance. |
|
|
59
|
+
| `nextbus-transit-vehicles` | Portland Streetcar, through the NextBus XML feed | Live positions, headings, speeds, and report ages for the streetcars on the NS line. |
|
|
56
60
|
|
|
57
61
|
The HTTP API and the CLI read this package's registry. A new adapter is exposed on
|
|
58
62
|
`/api/sources`, `/api/sources/{id}/probe`, and `/api/sources/{id}/data` as soon as it is registered.
|
|
@@ -277,6 +281,53 @@ validates a bare JSON array against a zod schema over `id`, `name`,
|
|
|
277
281
|
throws `UsSourceParseError`. A response with no rows also throws, because an
|
|
278
282
|
empty catalogue is not a usable answer.
|
|
279
283
|
|
|
284
|
+
`bts-transportation-stats`:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import {
|
|
288
|
+
buildBtsTransportationStatsUrl,
|
|
289
|
+
parseBtsTransportationStatsPayload,
|
|
290
|
+
} from '@usa-open-data-connectors/usa-sources';
|
|
291
|
+
|
|
292
|
+
const url = buildBtsTransportationStatsUrl({ limit: 10_000 });
|
|
293
|
+
const response = await fetch(url);
|
|
294
|
+
if (!response.ok) {
|
|
295
|
+
throw new Error(`HTTP ${response.status} from ${url}`);
|
|
296
|
+
}
|
|
297
|
+
const catalogue = parseBtsTransportationStatsPayload(await response.json());
|
|
298
|
+
console.log(catalogue.entryCount, catalogue.datasetCount, catalogue.categories[0]);
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The public API is `BTS_TRANSPORTATION_STATS_API_BASE`,
|
|
302
|
+
`BTS_TRANSPORTATION_STATS_LIMIT`, `buildBtsTransportationStatsUrl`,
|
|
303
|
+
`parseBtsTransportationStatsPayload`, `fetchBtsTransportationStats`, and
|
|
304
|
+
`btsTransportationStatsAdapter`, with the `BtsCatalogueEntry`,
|
|
305
|
+
`BtsCatalogueCategoryCount`, and `BtsTransportationCatalogue` types. The parser
|
|
306
|
+
validates a bare JSON array against a zod schema over `id`, `name`,
|
|
307
|
+
`assetType`, `category`, `viewType`, `createdAt`, `viewLastModified`,
|
|
308
|
+
`viewCount`, and `downloadCount`. A row missing `id`, `name`, or `createdAt`
|
|
309
|
+
throws `UsSourceParseError`. A response with no rows also throws, because an
|
|
310
|
+
empty catalogue is not a usable answer.
|
|
311
|
+
|
|
312
|
+
`nextbus-transit-vehicles`:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import { fetchNextBusVehicles } from '@usa-open-data-connectors/usa-sources';
|
|
316
|
+
|
|
317
|
+
const snapshot = await fetchNextBusVehicles();
|
|
318
|
+
console.log(snapshot.vehicleCount, snapshot.vehicles[0]);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The public API is `NEXTBUS_XML_FEED_BASE`, `NEXTBUS_PORTLAND_STREETCAR_AGENCY_TAG`,
|
|
322
|
+
`NEXTBUS_PORTLAND_STREETCAR_ROUTE_TAG`, `NEXTBUS_PORTLAND_STREETCAR_QUERY`,
|
|
323
|
+
`buildNextBusVehicleUrl`, `parseNextBusVehiclePayload`, `fetchNextBusVehicles`,
|
|
324
|
+
and `nextBusTransitVehiclesAdapter`, with the `NextBusVehicle`,
|
|
325
|
+
`NextBusVehicleQuery`, and `NextBusVehicleSnapshot` types. The parser reads
|
|
326
|
+
the XML attributes as strings and validates every field with zod. An error
|
|
327
|
+
body, a missing timestamp, or a position outside the map throws
|
|
328
|
+
`UsSourceParseError`. A route with no vehicles is valid and returns an empty
|
|
329
|
+
`vehicles` list.
|
|
330
|
+
|
|
280
331
|
`fema-disaster-declarations`:
|
|
281
332
|
|
|
282
333
|
```ts
|
|
@@ -425,6 +476,65 @@ COVID", because keywords and related terms also match. The response has no
|
|
|
425
476
|
total count unless the request adds `countTotal=true`, so count `studies` for
|
|
426
477
|
the page or ask for the total separately.
|
|
427
478
|
|
|
479
|
+
`nws-point-forecast`:
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
import { fetchNwsPointForecast } from '@usa-open-data-connectors/usa-sources';
|
|
483
|
+
|
|
484
|
+
const point = await fetchNwsPointForecast({ latitude: 47.601, longitude: -122.33 });
|
|
485
|
+
console.log(point.gridId, point.gridX, point.gridY, point.forecastUrl);
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
The public API is `NWS_API_BASE`, `NWS_POINT_FORECAST_PATH`,
|
|
489
|
+
`NWS_POINT_FORECAST_SOURCE_ID`, `NWS_USER_AGENT`, `NWS_DEFAULT_LATITUDE`,
|
|
490
|
+
`NWS_DEFAULT_LONGITUDE`, `buildNwsPointUrl`, `parseNwsPointForecastPayload`,
|
|
491
|
+
`fetchNwsPointForecast`, and `nwsPointForecastAdapter`, with the
|
|
492
|
+
`NwsForecastPoint` type. The parser validates the response with a zod schema
|
|
493
|
+
over `type`, `geometry.coordinates`, and a `properties` object carrying
|
|
494
|
+
`city`, `state`, `gridId`, `gridX`, `gridY`, `forecastOffice`, `forecast`,
|
|
495
|
+
`forecastHourly`, `forecastGridData`, `observationStations`, `timeZone`,
|
|
496
|
+
`radarStation`, `county`, `fireWeatherZone`, and the nested
|
|
497
|
+
`relativeLocation.properties` place label. A response that is not a GeoJSON
|
|
498
|
+
feature, has no coordinate pair, or has no grid id, grid cell, forecast URL, or
|
|
499
|
+
forecast office throws `UsSourceParseError`.
|
|
500
|
+
|
|
501
|
+
Three shape notes. The point lookup returns the forecast grid and the URLs that
|
|
502
|
+
lead to the forecast, not the forecast periods themselves; a caller that needs
|
|
503
|
+
temperatures reads `forecastUrl` or `forecastHourlyUrl` next. Grid cell numbers
|
|
504
|
+
are local to an office, so `gridX` 100 in one office and `gridX` 100 in another
|
|
505
|
+
point at different places; always keep `gridId` next to the cell. The place
|
|
506
|
+
label is the nearest place the service knows, not the caller's own name: a
|
|
507
|
+
lookup for Tacoma came back as "Seattle" on 2026-10-20, and a lookup for
|
|
508
|
+
Tacoma Old City Hall landed in office SEW at cell 125,68.
|
|
509
|
+
|
|
510
|
+
`callook-callsigns`:
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
import { fetchCallookCallsigns } from '@usa-open-data-connectors/usa-sources';
|
|
514
|
+
|
|
515
|
+
const directory = await fetchCallookCallsigns({ callsigns: ['W1AW'] });
|
|
516
|
+
console.log(directory.callsigns[0]?.callsign, directory.soonestExpiry?.expiryDate);
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Callook answers one callsign per request. The default fetch reads W1ABC through
|
|
520
|
+
W0ABC, one callsign for each US call district. Pass `callsigns` to read a
|
|
521
|
+
different set. The parser keeps a lookup with status `INVALID` or `UPDATING` as
|
|
522
|
+
a record with no licence fields, and counts only `VALID` records in the folded
|
|
523
|
+
totals.
|
|
524
|
+
|
|
525
|
+
`college-scorecard-institutions`:
|
|
526
|
+
|
|
527
|
+
```ts
|
|
528
|
+
import { fetchCollegeScorecardInstitutions } from '@usa-open-data-connectors/usa-sources';
|
|
529
|
+
|
|
530
|
+
const snapshot = await fetchCollegeScorecardInstitutions();
|
|
531
|
+
console.log(snapshot.institutionCount, snapshot.highestEarningState?.stateAbbr);
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
The adapter reads the keyless institution-level bulk download, not the keyed
|
|
535
|
+
College Scorecard API. The archive is a zip that holds one wide CSV, and a live
|
|
536
|
+
fetch takes about 20 seconds on a fast connection.
|
|
537
|
+
|
|
428
538
|
## Notes and limits
|
|
429
539
|
|
|
430
540
|
- `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.
|
|
@@ -448,7 +558,7 @@ the page or ask for the total separately.
|
|
|
448
558
|
- `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
559
|
- `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
560
|
- `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,
|
|
561
|
+
- `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,415 business-day rows on 2026-10-16, 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
562
|
- `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
563
|
- `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
564
|
- `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.
|
|
@@ -500,16 +610,38 @@ the page or ask for the total separately.
|
|
|
500
610
|
- `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
611
|
- `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
612
|
- `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,
|
|
613
|
+
- `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,478 entries on 2026-10-20. 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
614
|
- `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
615
|
- `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,
|
|
616
|
+
- `cdc-socrata-catalogue`: the full response is large because each row carries portal metadata. The 1,478-row response was about 7.9 MB on 2026-10-20. The parser keeps nine fields, and a fixture trimmed to those fields was about 491 KB.
|
|
507
617
|
- `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.
|
|
618
|
+
- `bts-transportation-stats`: the default request asks for ten rows. The story that consumed this adapter needed the whole catalogue, so it calls `buildBtsTransportationStatsUrl({ limit: 10_000 })` and parses the full response with `parseBtsTransportationStatsPayload`. A lower limit truncates the catalogue without an error.
|
|
619
|
+
- `bts-transportation-stats`: each row is a catalogue entry, not a dataset record. `assetType` can be `story`, `dataset`, `chart`, `measure`, `filter`, `href`, `map`, or `file`. The story and dataset counts are different questions, and the 2026-10-20 catalogue held 177 stories against 153 datasets.
|
|
620
|
+
- `bts-transportation-stats`: `viewCount` and `downloadCount` are the portal's own counters for an item page and its files. They are not unique people. Stories carry views and a zero download count because Socrata does not give a story a download counter, while 268 entries reported no downloads at all on 2026-10-20.
|
|
621
|
+
- `bts-transportation-stats`: the response is large because every entry carries portal metadata. The 589-row response was about 2.2 MB on 2026-10-20. The parser keeps nine fields, and a fixture trimmed to those fields was about 172 KB.
|
|
622
|
+
- `bts-transportation-stats`: 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 bureau assigned.
|
|
508
623
|
- `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
624
|
- `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
625
|
- Unit tests use the committed fixtures and run offline. Run them with `npm run test --workspace @usa-open-data-connectors/usa-sources`.
|
|
511
626
|
- 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
627
|
|
|
628
|
+
- `nws-point-forecast`: one keyless request covers one coordinate. The service asks every caller to send a User-Agent that identifies the application, and `fetchNwsPointForecast` sends `NWS_USER_AGENT` on every call.
|
|
629
|
+
- `nws-point-forecast`: the response is a GeoJSON feature whose `properties` hold the grid and the links. The forecast itself is a second request to `forecastUrl` (daily periods) or `forecastHourlyUrl` (hourly periods).
|
|
630
|
+
- `nws-point-forecast`: `gridX` and `gridY` are numbered inside one forecast office. The same pair in two offices is not the same place, and there is no national grid coordinate in the response.
|
|
631
|
+
- `nws-point-forecast`: `relativeLocation.properties` names the nearest place the service knows, which can be a neighboring city. A Tacoma lookup returned "Seattle", so a caller that needs its own label should keep it outside the response.
|
|
632
|
+
- `nws-point-forecast`: one point response is about 3.5 KB. Twenty-five lookups cost twenty-five requests and about 88 KB of committed JSON when saved as a snapshot.
|
|
633
|
+
- `callook-callsigns`: the service answers one callsign per request and has no bulk list endpoint. The default ten-district sample costs ten requests. The service asks for no key, and its API reference says the data comes from the FCC licence file, refreshed daily.
|
|
634
|
+
- `callook-callsigns`: a lookup returns `INVALID` for a callsign with no current licence. The adapter keeps that answer as a record with status `INVALID` rather than treating it as an HTTP failure. A callsign with a current licence has status `VALID` and carries the licence fields.
|
|
635
|
+
- `callook-callsigns`: coordinates and the grid square are calculated by Callook from the mailing address, not taken from the FCC file. A record can have an empty location. The adapter keeps the coordinates as null in that case and does not invent a place.
|
|
636
|
+
- `college-scorecard-institutions`: the bulk archive is 24 MB and the CSV inside it is 102 MB. The adapter raises its request timeout to two minutes. The CSV has 3,379 columns, so the adapter reads only the outcome fields it exposes.
|
|
637
|
+
- `college-scorecard-institutions`: `NA` and `PS` mean the Department of Education did not publish a figure. The adapter keeps those as null, so a chart does not turn a suppressed value into a zero.
|
|
638
|
+
- `college-scorecard-institutions`: the state summary is a median of the institution medians, not a weighted student average. It answers "what does a typical college in this state report", not "what does a typical student earn".
|
|
639
|
+
- `college-scorecard-institutions`: the earnings field is `MD_EARN_WNE_P10`, the median for graduates working and not enrolled ten years after entry. It is not a first-year salary.
|
|
640
|
+
- `nextbus-transit-vehicles`: the adapter reads one agency route, the Portland Streetcar NS line (`portland-sc`, route `193`). The feed also serves other agencies, including non-US ones, so the adapter does not claim to cover every agency on the feed.
|
|
641
|
+
- `nextbus-transit-vehicles`: `t=0` asks for every report the feed still holds for the route. The feed returns the current set of streetcars, not a history, so the snapshot describes one moment and is replaced on the next request.
|
|
642
|
+
- `nextbus-transit-vehicles`: a route with no service returns a snapshot with zero vehicles and a valid timestamp. That is a real answer, not a failure, so the parser keeps it.
|
|
643
|
+
- `nextbus-transit-vehicles`: the feed carries a copyright notice for the transit agency and Umo IQ, and states no open licence. Check the agency's terms before republishing the positions.
|
|
644
|
+
|
|
513
645
|
## Data sources and licences
|
|
514
646
|
|
|
515
647
|
The data comes from US federal agencies and the open data catalogue services they publish on.
|
|
@@ -537,6 +669,10 @@ The data comes from US federal agencies and the open data catalogue services the
|
|
|
537
669
|
| EstimateTax | `https://estimatetax.net/api/v1/calculate` |
|
|
538
670
|
| HealthData.gov | `https://healthdata.gov/api/views.json` |
|
|
539
671
|
| New York City Open Data | `https://data.cityofnewyork.us/api/views.json` |
|
|
672
|
+
| Callook.info | `https://callook.info/[callsign]/json` |
|
|
673
|
+
| College Scorecard, US Department of Education | `https://ed-public-download.scorecard.network/downloads/Most-Recent-Cohorts-Institution_10152026.zip` |
|
|
674
|
+
| PublicDataHub, from the Centers for Medicare & Medicaid Services | `https://publicdatahub.org/api/export/hospital/{slug}.json` |
|
|
675
|
+
| Portland Streetcar, through the NextBus XML feed | `https://retro.umoiq.com/service/publicXMLFeed?command=vehicleLocations&a=portland-sc&r=193&t=0` |
|
|
540
676
|
|
|
541
677
|
Each publisher sets its own terms for its data. The Socrata discovery responses can carry rights and
|
|
542
678
|
metadata fields, but the catalogue adapters in this package keep a subset of the entry fields and do not
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { UsDataAdapter } from './types.js';
|
|
2
|
+
/** Adapter id for the Callook.info amateur radio callsign source. */
|
|
3
|
+
export declare const CALLOOK_CALLSIGNS_SOURCE_ID = "callook-callsigns";
|
|
4
|
+
/** Base URL for the Callook.info callsign lookup service. */
|
|
5
|
+
export declare const CALLOOK_API_BASE = "https://callook.info";
|
|
6
|
+
/**
|
|
7
|
+
* One callsign for each of the ten US amateur radio call districts.
|
|
8
|
+
*
|
|
9
|
+
* Callook answers one callsign per request rather than exposing a bulk list.
|
|
10
|
+
* The W1ABC through W0ABC callsigns give the same lookup a ten-district
|
|
11
|
+
* sample, so the adapter can show how the licence file varies across the
|
|
12
|
+
* country instead of reporting a single record.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CALLOOK_DISTRICT_CALLSIGNS: readonly string[];
|
|
15
|
+
/** The three states Callook uses when a lookup has no licence record. */
|
|
16
|
+
export type CallookLookupStatus = 'VALID' | 'INVALID' | 'UPDATING';
|
|
17
|
+
/** One callsign lookup, with the licence fields flattened into plain names. */
|
|
18
|
+
export interface CallookCallsign {
|
|
19
|
+
/** Whether Callook found a current licence for the callsign. */
|
|
20
|
+
status: CallookLookupStatus;
|
|
21
|
+
/** Current callsign, or an empty string when the lookup found no licence. */
|
|
22
|
+
callsign: string;
|
|
23
|
+
/** Licence type, e.g. "CLUB" or "PERSON", or an empty string when none. */
|
|
24
|
+
licenseType: string;
|
|
25
|
+
/** Operator class, e.g. "GENERAL" or "EXTRA", or an empty string when none. */
|
|
26
|
+
operatorClass: string;
|
|
27
|
+
/** Previous callsign held by the same licensee, or an empty string. */
|
|
28
|
+
previousCallsign: string;
|
|
29
|
+
/** Previous operator class held by the same licensee, or an empty string. */
|
|
30
|
+
previousOperatorClass: string;
|
|
31
|
+
/** Name of the licensee, or the club the licence belongs to. */
|
|
32
|
+
licenseeName: string;
|
|
33
|
+
/** Callsign of the club trustee, or an empty string for a personal licence. */
|
|
34
|
+
trusteeCallsign: string;
|
|
35
|
+
/** Name of the club trustee, or an empty string for a personal licence. */
|
|
36
|
+
trusteeName: string;
|
|
37
|
+
/** First line of the mailing address on the FCC record. */
|
|
38
|
+
addressLine1: string;
|
|
39
|
+
/** Second line of the mailing address, usually city, state, and ZIP code. */
|
|
40
|
+
addressLine2: string;
|
|
41
|
+
/** Attention line on the mailing address, or an empty string. */
|
|
42
|
+
attention: string;
|
|
43
|
+
/** Approximate latitude Callook calculated from the address, or null. */
|
|
44
|
+
latitude: number | null;
|
|
45
|
+
/** Approximate longitude Callook calculated from the address, or null. */
|
|
46
|
+
longitude: number | null;
|
|
47
|
+
/** Maidenhead grid square Callook calculated from the coordinates, or empty. */
|
|
48
|
+
gridsquare: string;
|
|
49
|
+
/** Licence grant date as YYYY-MM-DD, or null when Callook reports none. */
|
|
50
|
+
grantDate: string | null;
|
|
51
|
+
/** Licence expiry date as YYYY-MM-DD, or null when Callook reports none. */
|
|
52
|
+
expiryDate: string | null;
|
|
53
|
+
/** Date of the last FCC action on the licence, or null. */
|
|
54
|
+
lastActionDate: string | null;
|
|
55
|
+
/** FCC Registration Number, or an empty string. */
|
|
56
|
+
frn: string;
|
|
57
|
+
/** Link to the licence in the FCC ULS search, or an empty string. */
|
|
58
|
+
ulsUrl: string;
|
|
59
|
+
/** Two-letter state parsed from the mailing address, or an empty string. */
|
|
60
|
+
stateAbbr: string;
|
|
61
|
+
/** US call district digit taken from the callsign, or an empty string. */
|
|
62
|
+
callDistrict: string;
|
|
63
|
+
}
|
|
64
|
+
/** One label and how many records carry it. */
|
|
65
|
+
export interface CallookLabelCount {
|
|
66
|
+
label: string;
|
|
67
|
+
count: number;
|
|
68
|
+
}
|
|
69
|
+
/** A batch of callsign lookups, folded for a story. */
|
|
70
|
+
export interface CallookCallsignDirectory {
|
|
71
|
+
/** Every lookup in the batch, valid or not, in the order it was requested. */
|
|
72
|
+
callsigns: CallookCallsign[];
|
|
73
|
+
callsignCount: number;
|
|
74
|
+
validCount: number;
|
|
75
|
+
invalidCount: number;
|
|
76
|
+
updatingCount: number;
|
|
77
|
+
/** Licence types, most records first, ties broken by label. */
|
|
78
|
+
licenseTypeCounts: CallookLabelCount[];
|
|
79
|
+
/** Operator classes, most records first, ties broken by label. */
|
|
80
|
+
operatorClassCounts: CallookLabelCount[];
|
|
81
|
+
/** How many different call districts the valid records cover. */
|
|
82
|
+
callDistrictCount: number;
|
|
83
|
+
/** Valid record with the earliest expiry date, or null when none has one. */
|
|
84
|
+
soonestExpiry: CallookCallsign | null;
|
|
85
|
+
/** Valid record with the latest expiry date, or null when none has one. */
|
|
86
|
+
latestExpiry: CallookCallsign | null;
|
|
87
|
+
}
|
|
88
|
+
/** Builds the request URL for one callsign lookup. */
|
|
89
|
+
export declare function buildCallookCallsignUrl(callsign: string): string;
|
|
90
|
+
/** Folds a list of callsign records into the shape the page reads. */
|
|
91
|
+
export declare function buildCallookCallsignDirectory(callsigns: CallookCallsign[]): CallookCallsignDirectory;
|
|
92
|
+
/**
|
|
93
|
+
* Parses a batch of Callook lookup responses.
|
|
94
|
+
*
|
|
95
|
+
* A lookup with status INVALID or UPDATING is a valid answer from the service
|
|
96
|
+
* and stays in `callsigns`, but it carries no licence fields and does not
|
|
97
|
+
* contribute to the counts. A VALID record without a current callsign is a
|
|
98
|
+
* changed shape and stops the parse.
|
|
99
|
+
*
|
|
100
|
+
* @param payload - an array of JSON bodies, one per callsign lookup
|
|
101
|
+
* @returns the records and the folded counts
|
|
102
|
+
*/
|
|
103
|
+
export declare function parseCallookCallsignsPayload(payload: unknown): CallookCallsignDirectory;
|
|
104
|
+
/**
|
|
105
|
+
* Reads one Callook lookup for each callsign.
|
|
106
|
+
*
|
|
107
|
+
* The service answers one callsign per request and needs no key. When the
|
|
108
|
+
* caller names no callsigns, the adapter reads the ten-district sample.
|
|
109
|
+
*
|
|
110
|
+
* @param options - an optional callsign list and fetch stub
|
|
111
|
+
* @returns the records and the folded counts
|
|
112
|
+
*/
|
|
113
|
+
export declare function fetchCallookCallsigns(options?: {
|
|
114
|
+
callsigns?: readonly string[];
|
|
115
|
+
fetchImpl?: typeof globalThis.fetch;
|
|
116
|
+
}): Promise<CallookCallsignDirectory>;
|
|
117
|
+
/** Callook.info US amateur radio callsigns, one record per lookup, keyless. */
|
|
118
|
+
export declare const callookCallsignsAdapter: UsDataAdapter<CallookCallsignDirectory>;
|
|
119
|
+
//# sourceMappingURL=callookCallsigns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callookCallsigns.d.ts","sourceRoot":"","sources":["../src/callookCallsigns.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { UsSourceParseError } from './errors.js';
|
|
3
|
+
import { httpGet } from './http.js';
|
|
4
|
+
import { readFixtureJson } from './fixtures.js';
|
|
5
|
+
/** Adapter id for the Callook.info amateur radio callsign source. */
|
|
6
|
+
export const CALLOOK_CALLSIGNS_SOURCE_ID = 'callook-callsigns';
|
|
7
|
+
/** Base URL for the Callook.info callsign lookup service. */
|
|
8
|
+
export const CALLOOK_API_BASE = 'https://callook.info';
|
|
9
|
+
/**
|
|
10
|
+
* One callsign for each of the ten US amateur radio call districts.
|
|
11
|
+
*
|
|
12
|
+
* Callook answers one callsign per request rather than exposing a bulk list.
|
|
13
|
+
* The W1ABC through W0ABC callsigns give the same lookup a ten-district
|
|
14
|
+
* sample, so the adapter can show how the licence file varies across the
|
|
15
|
+
* country instead of reporting a single record.
|
|
16
|
+
*/
|
|
17
|
+
export const CALLOOK_DISTRICT_CALLSIGNS = [
|
|
18
|
+
'W1ABC',
|
|
19
|
+
'W2ABC',
|
|
20
|
+
'W3ABC',
|
|
21
|
+
'W4ABC',
|
|
22
|
+
'W5ABC',
|
|
23
|
+
'W6ABC',
|
|
24
|
+
'W7ABC',
|
|
25
|
+
'W8ABC',
|
|
26
|
+
'W9ABC',
|
|
27
|
+
'W0ABC',
|
|
28
|
+
];
|
|
29
|
+
/** Committed snapshot, so a build works when Callook is unreachable. */
|
|
30
|
+
const CALLOOK_CALLSIGNS_FIXTURE_FILENAME = 'callook-callsigns-2026-10-21.json';
|
|
31
|
+
/** Shape of a US amateur radio callsign, without spaces or punctuation. */
|
|
32
|
+
const CALLSIGN_PATTERN = /^[A-Z0-9]{3,10}$/;
|
|
33
|
+
/** Shape of the dates Callook writes, e.g. "03/24/2036". */
|
|
34
|
+
const CALLOOK_DATE_PATTERN = /^(\d{2})\/(\d{2})\/(\d{4})$/;
|
|
35
|
+
/** Shape of a US mailing address line ending in a state and ZIP code. */
|
|
36
|
+
const STATE_AND_ZIP_PATTERN = /\b([A-Z]{2})\s+\d{5}(?:-\d{4})?\s*$/;
|
|
37
|
+
const CALLOOK_ADDRESS_SCHEMA = z.object({
|
|
38
|
+
line1: z.string().optional(),
|
|
39
|
+
line2: z.string().optional(),
|
|
40
|
+
attn: z.string().optional(),
|
|
41
|
+
});
|
|
42
|
+
const CALLOOK_LOCATION_SCHEMA = z.object({
|
|
43
|
+
latitude: z.string().optional(),
|
|
44
|
+
longitude: z.string().optional(),
|
|
45
|
+
gridsquare: z.string().optional(),
|
|
46
|
+
});
|
|
47
|
+
const CALLOOK_OTHER_INFO_SCHEMA = z.object({
|
|
48
|
+
grantDate: z.string().optional(),
|
|
49
|
+
expiryDate: z.string().optional(),
|
|
50
|
+
lastActionDate: z.string().optional(),
|
|
51
|
+
frn: z.string().optional(),
|
|
52
|
+
ulsUrl: z.string().optional(),
|
|
53
|
+
});
|
|
54
|
+
const CALLOOK_NAME_SCHEMA = z.object({
|
|
55
|
+
callsign: z.string().optional(),
|
|
56
|
+
operClass: z.string().optional(),
|
|
57
|
+
name: z.string().optional(),
|
|
58
|
+
});
|
|
59
|
+
const CALLOOK_CALLSIGN_SCHEMA = z.object({
|
|
60
|
+
status: z.enum(['VALID', 'INVALID', 'UPDATING']),
|
|
61
|
+
type: z.string().optional(),
|
|
62
|
+
current: CALLOOK_NAME_SCHEMA.optional(),
|
|
63
|
+
previous: CALLOOK_NAME_SCHEMA.optional(),
|
|
64
|
+
trustee: CALLOOK_NAME_SCHEMA.optional(),
|
|
65
|
+
name: z.string().optional(),
|
|
66
|
+
address: CALLOOK_ADDRESS_SCHEMA.optional(),
|
|
67
|
+
location: CALLOOK_LOCATION_SCHEMA.optional(),
|
|
68
|
+
otherInfo: CALLOOK_OTHER_INFO_SCHEMA.optional(),
|
|
69
|
+
});
|
|
70
|
+
const CALLOOK_CALLSIGNS_SCHEMA = z.array(CALLOOK_CALLSIGN_SCHEMA);
|
|
71
|
+
/** Builds the request URL for one callsign lookup. */
|
|
72
|
+
export function buildCallookCallsignUrl(callsign) {
|
|
73
|
+
const normalized = callsign.trim().toUpperCase();
|
|
74
|
+
if (!CALLSIGN_PATTERN.test(normalized)) {
|
|
75
|
+
throw new UsSourceParseError(CALLOOK_CALLSIGNS_SOURCE_ID, `A callsign must be 3 to 10 letters or digits: ${callsign}`);
|
|
76
|
+
}
|
|
77
|
+
return `${CALLOOK_API_BASE}/${normalized}/json`;
|
|
78
|
+
}
|
|
79
|
+
/** Converts Callook's MM/DD/YYYY date to YYYY-MM-DD, or null when unreadable. */
|
|
80
|
+
function toIsoDate(value) {
|
|
81
|
+
if (value === undefined || value.trim() === '') {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const match = CALLOOK_DATE_PATTERN.exec(value.trim());
|
|
85
|
+
if (match === null) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const [, month, day, year] = match;
|
|
89
|
+
return `${year}-${month}-${day}`;
|
|
90
|
+
}
|
|
91
|
+
/** Reads a numeric coordinate from Callook's string field, or null. */
|
|
92
|
+
function toCoordinate(value) {
|
|
93
|
+
if (value === undefined || value.trim() === '') {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const coordinate = Number(value);
|
|
97
|
+
return Number.isFinite(coordinate) ? coordinate : null;
|
|
98
|
+
}
|
|
99
|
+
/** Reads the two-letter state from the end of a mailing address line. */
|
|
100
|
+
function toStateAbbr(addressLine2) {
|
|
101
|
+
return STATE_AND_ZIP_PATTERN.exec(addressLine2)?.[1] ?? '';
|
|
102
|
+
}
|
|
103
|
+
/** Reads the first digit in a callsign as its US call district. */
|
|
104
|
+
function toCallDistrict(callsign) {
|
|
105
|
+
return /\d/.exec(callsign)?.[0] ?? '';
|
|
106
|
+
}
|
|
107
|
+
/** Builds an empty record for a lookup that found no current licence. */
|
|
108
|
+
function emptyCallookCallsign(status) {
|
|
109
|
+
return {
|
|
110
|
+
status,
|
|
111
|
+
callsign: '',
|
|
112
|
+
licenseType: '',
|
|
113
|
+
operatorClass: '',
|
|
114
|
+
previousCallsign: '',
|
|
115
|
+
previousOperatorClass: '',
|
|
116
|
+
licenseeName: '',
|
|
117
|
+
trusteeCallsign: '',
|
|
118
|
+
trusteeName: '',
|
|
119
|
+
addressLine1: '',
|
|
120
|
+
addressLine2: '',
|
|
121
|
+
attention: '',
|
|
122
|
+
latitude: null,
|
|
123
|
+
longitude: null,
|
|
124
|
+
gridsquare: '',
|
|
125
|
+
grantDate: null,
|
|
126
|
+
expiryDate: null,
|
|
127
|
+
lastActionDate: null,
|
|
128
|
+
frn: '',
|
|
129
|
+
ulsUrl: '',
|
|
130
|
+
stateAbbr: '',
|
|
131
|
+
callDistrict: '',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Counts labels in a record list, most records first, ties by label. */
|
|
135
|
+
function countLabels(labels) {
|
|
136
|
+
const counts = new Map();
|
|
137
|
+
for (const label of labels) {
|
|
138
|
+
if (label !== '') {
|
|
139
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return [...counts.entries()]
|
|
143
|
+
.sort(([leftLabel, leftCount], [rightLabel, rightCount]) => leftCount === rightCount ? leftLabel.localeCompare(rightLabel) : rightCount - leftCount)
|
|
144
|
+
.map(([label, count]) => ({ label, count }));
|
|
145
|
+
}
|
|
146
|
+
/** Picks the valid record with the earliest or latest date in one field. */
|
|
147
|
+
function edgeByDate(callsigns, direction) {
|
|
148
|
+
let edge = null;
|
|
149
|
+
let edgeDate = null;
|
|
150
|
+
for (const callsign of callsigns) {
|
|
151
|
+
const value = callsign.expiryDate;
|
|
152
|
+
if (value === null) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (edgeDate === null || (direction === 'earliest' ? value < edgeDate : value > edgeDate)) {
|
|
156
|
+
edge = callsign;
|
|
157
|
+
edgeDate = value;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return edge;
|
|
161
|
+
}
|
|
162
|
+
/** Folds a list of callsign records into the shape the page reads. */
|
|
163
|
+
export function buildCallookCallsignDirectory(callsigns) {
|
|
164
|
+
const valid = callsigns.filter((callsign) => callsign.status === 'VALID');
|
|
165
|
+
return {
|
|
166
|
+
callsigns,
|
|
167
|
+
callsignCount: callsigns.length,
|
|
168
|
+
validCount: valid.length,
|
|
169
|
+
invalidCount: callsigns.filter((callsign) => callsign.status === 'INVALID').length,
|
|
170
|
+
updatingCount: callsigns.filter((callsign) => callsign.status === 'UPDATING').length,
|
|
171
|
+
licenseTypeCounts: countLabels(valid.map((callsign) => callsign.licenseType)),
|
|
172
|
+
operatorClassCounts: countLabels(valid.map((callsign) => callsign.operatorClass)),
|
|
173
|
+
callDistrictCount: new Set(valid.map((callsign) => callsign.callDistrict).filter((district) => district !== '')).size,
|
|
174
|
+
soonestExpiry: edgeByDate(valid, 'earliest'),
|
|
175
|
+
latestExpiry: edgeByDate(valid, 'latest'),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Parses a batch of Callook lookup responses.
|
|
180
|
+
*
|
|
181
|
+
* A lookup with status INVALID or UPDATING is a valid answer from the service
|
|
182
|
+
* and stays in `callsigns`, but it carries no licence fields and does not
|
|
183
|
+
* contribute to the counts. A VALID record without a current callsign is a
|
|
184
|
+
* changed shape and stops the parse.
|
|
185
|
+
*
|
|
186
|
+
* @param payload - an array of JSON bodies, one per callsign lookup
|
|
187
|
+
* @returns the records and the folded counts
|
|
188
|
+
*/
|
|
189
|
+
export function parseCallookCallsignsPayload(payload) {
|
|
190
|
+
const parsed = CALLOOK_CALLSIGNS_SCHEMA.safeParse(payload);
|
|
191
|
+
if (!parsed.success) {
|
|
192
|
+
throw new UsSourceParseError(CALLOOK_CALLSIGNS_SOURCE_ID, parsed.error.message);
|
|
193
|
+
}
|
|
194
|
+
if (parsed.data.length === 0) {
|
|
195
|
+
throw new UsSourceParseError(CALLOOK_CALLSIGNS_SOURCE_ID, 'The response carried no lookups');
|
|
196
|
+
}
|
|
197
|
+
const callsigns = parsed.data.map((record) => {
|
|
198
|
+
if (record.status !== 'VALID') {
|
|
199
|
+
return emptyCallookCallsign(record.status);
|
|
200
|
+
}
|
|
201
|
+
const currentCallsign = record.current?.callsign;
|
|
202
|
+
if (currentCallsign === undefined || currentCallsign === '') {
|
|
203
|
+
throw new UsSourceParseError(CALLOOK_CALLSIGNS_SOURCE_ID, 'A valid lookup arrived without a current callsign');
|
|
204
|
+
}
|
|
205
|
+
const addressLine2 = record.address?.line2 ?? '';
|
|
206
|
+
return {
|
|
207
|
+
status: record.status,
|
|
208
|
+
callsign: currentCallsign,
|
|
209
|
+
licenseType: record.type ?? '',
|
|
210
|
+
operatorClass: record.current?.operClass ?? '',
|
|
211
|
+
previousCallsign: record.previous?.callsign ?? '',
|
|
212
|
+
previousOperatorClass: record.previous?.operClass ?? '',
|
|
213
|
+
licenseeName: record.name ?? '',
|
|
214
|
+
trusteeCallsign: record.trustee?.callsign ?? '',
|
|
215
|
+
trusteeName: record.trustee?.name ?? '',
|
|
216
|
+
addressLine1: record.address?.line1 ?? '',
|
|
217
|
+
addressLine2,
|
|
218
|
+
attention: record.address?.attn ?? '',
|
|
219
|
+
latitude: toCoordinate(record.location?.latitude),
|
|
220
|
+
longitude: toCoordinate(record.location?.longitude),
|
|
221
|
+
gridsquare: record.location?.gridsquare ?? '',
|
|
222
|
+
grantDate: toIsoDate(record.otherInfo?.grantDate),
|
|
223
|
+
expiryDate: toIsoDate(record.otherInfo?.expiryDate),
|
|
224
|
+
lastActionDate: toIsoDate(record.otherInfo?.lastActionDate),
|
|
225
|
+
frn: record.otherInfo?.frn ?? '',
|
|
226
|
+
ulsUrl: record.otherInfo?.ulsUrl ?? '',
|
|
227
|
+
stateAbbr: toStateAbbr(addressLine2),
|
|
228
|
+
callDistrict: toCallDistrict(currentCallsign),
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
return buildCallookCallsignDirectory(callsigns);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Reads one Callook lookup for each callsign.
|
|
235
|
+
*
|
|
236
|
+
* The service answers one callsign per request and needs no key. When the
|
|
237
|
+
* caller names no callsigns, the adapter reads the ten-district sample.
|
|
238
|
+
*
|
|
239
|
+
* @param options - an optional callsign list and fetch stub
|
|
240
|
+
* @returns the records and the folded counts
|
|
241
|
+
*/
|
|
242
|
+
export async function fetchCallookCallsigns(options) {
|
|
243
|
+
const callsigns = options?.callsigns ?? CALLOOK_DISTRICT_CALLSIGNS;
|
|
244
|
+
if (callsigns.length === 0) {
|
|
245
|
+
throw new UsSourceParseError(CALLOOK_CALLSIGNS_SOURCE_ID, 'No callsigns were requested');
|
|
246
|
+
}
|
|
247
|
+
const fetchImpl = options?.fetchImpl ?? globalThis.fetch;
|
|
248
|
+
const payloads = await Promise.all(callsigns.map(async (callsign) => {
|
|
249
|
+
const response = await httpGet(CALLOOK_CALLSIGNS_SOURCE_ID, buildCallookCallsignUrl(callsign), { fetchImpl });
|
|
250
|
+
return (await response.json());
|
|
251
|
+
}));
|
|
252
|
+
return parseCallookCallsignsPayload(payloads);
|
|
253
|
+
}
|
|
254
|
+
/** Callook.info US amateur radio callsigns, one record per lookup, keyless. */
|
|
255
|
+
export const callookCallsignsAdapter = {
|
|
256
|
+
id: CALLOOK_CALLSIGNS_SOURCE_ID,
|
|
257
|
+
name: 'Callook callsigns',
|
|
258
|
+
auth: 'none',
|
|
259
|
+
description: 'US amateur radio callsign records from the FCC licence file, looked up through Callook.info, with the licence class, address, location, and expiry for each callsign.',
|
|
260
|
+
fetchLive: async (options) => fetchCallookCallsigns(options?.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
261
|
+
parse: (payload) => parseCallookCallsignsPayload(payload),
|
|
262
|
+
loadFixture: () => parseCallookCallsignsPayload(readFixtureJson(CALLOOK_CALLSIGNS_FIXTURE_FILENAME)),
|
|
263
|
+
};
|
|
264
|
+
//# sourceMappingURL=callookCallsigns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callookCallsigns.js","sourceRoot":"","sources":["../src/callookCallsigns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,qEAAqE;AACrE,MAAM,CAAC,MAAM,2BAA2B,GAAG,mBAAmB,CAAC;AAE/D,6DAA6D;AAC7D,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAsB;IAC3D,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;CACR,CAAC;AA+EF,wEAAwE;AACxE,MAAM,kCAAkC,GAAG,mCAAmC,CAAC;AAE/E,2EAA2E;AAC3E,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAE5C,4DAA4D;AAC5D,MAAM,oBAAoB,GAAG,6BAA6B,CAAC;AAE3D,yEAAyE;AACzE,MAAM,qBAAqB,GAAG,qCAAqC,CAAC;AAEpE,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAEH,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAEH,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAEH,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAEH,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IAChD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,OAAO,EAAE,mBAAmB,CAAC,QAAQ,EAAE;IACvC,QAAQ,EAAE,mBAAmB,CAAC,QAAQ,EAAE;IACxC,OAAO,EAAE,mBAAmB,CAAC,QAAQ,EAAE;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,OAAO,EAAE,sBAAsB,CAAC,QAAQ,EAAE;IAC1C,QAAQ,EAAE,uBAAuB,CAAC,QAAQ,EAAE;IAC5C,SAAS,EAAE,yBAAyB,CAAC,QAAQ,EAAE;CAChD,CAAC,CAAC;AAEH,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;AAElE,sDAAsD;AACtD,MAAM,UAAU,uBAAuB,CAAC,QAAgB;IACtD,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACjD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,kBAAkB,CAC1B,2BAA2B,EAC3B,iDAAiD,QAAQ,EAAE,CAC5D,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,gBAAgB,IAAI,UAAU,OAAO,CAAC;AAClD,CAAC;AAED,iFAAiF;AACjF,SAAS,SAAS,CAAC,KAAyB;IAC1C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC;IACnC,OAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;AACnC,CAAC;AAED,uEAAuE;AACvE,SAAS,YAAY,CAAC,KAAyB;IAC7C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC;AAED,yEAAyE;AACzE,SAAS,WAAW,CAAC,YAAoB;IACvC,OAAO,qBAAqB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC7D,CAAC;AAED,mEAAmE;AACnE,SAAS,cAAc,CAAC,QAAgB;IACtC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACxC,CAAC;AAED,yEAAyE;AACzE,SAAS,oBAAoB,CAAC,MAA2B;IACvD,OAAO;QACL,MAAM;QACN,QAAQ,EAAE,EAAE;QACZ,WAAW,EAAE,EAAE;QACf,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,EAAE;QACpB,qBAAqB,EAAE,EAAE;QACzB,YAAY,EAAE,EAAE;QAChB,eAAe,EAAE,EAAE;QACnB,WAAW,EAAE,EAAE;QACf,YAAY,EAAE,EAAE;QAChB,YAAY,EAAE,EAAE;QAChB,SAAS,EAAE,EAAE;QACb,QAAQ,EAAE,IAAI;QACd,SAAS,EAAE,IAAI;QACf,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,IAAI;QACf,UAAU,EAAE,IAAI;QAChB,cAAc,EAAE,IAAI;QACpB,GAAG,EAAE,EAAE;QACP,MAAM,EAAE,EAAE;QACV,SAAS,EAAE,EAAE;QACb,YAAY,EAAE,EAAE;KACjB,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,SAAS,WAAW,CAAC,MAAgB;IACnC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACjB,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;SACzB,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE,CACzD,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,SAAS,CACxF;SACA,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,4EAA4E;AAC5E,SAAS,UAAU,CACjB,SAA4B,EAC5B,SAAgC;IAEhC,IAAI,IAAI,GAA2B,IAAI,CAAC;IACxC,IAAI,QAAQ,GAAkB,IAAI,CAAC;IACnC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC;QAClC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,SAAS;QACX,CAAC;QACD,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,EAAE,CAAC;YAC1F,IAAI,GAAG,QAAQ,CAAC;YAChB,QAAQ,GAAG,KAAK,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,6BAA6B,CAC3C,SAA4B;IAE5B,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;IAC1E,OAAO;QACL,SAAS;QACT,aAAa,EAAE,SAAS,CAAC,MAAM;QAC/B,UAAU,EAAE,KAAK,CAAC,MAAM;QACxB,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM;QAClF,aAAa,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,MAAM;QACpF,iBAAiB,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC7E,mBAAmB,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QACjF,iBAAiB,EAAE,IAAI,GAAG,CACxB,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,CACrF,CAAC,IAAI;QACN,aAAa,EAAE,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC;QAC5C,YAAY,EAAE,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,4BAA4B,CAAC,OAAgB;IAC3D,MAAM,MAAM,GAAG,wBAAwB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,kBAAkB,CAAC,2BAA2B,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,kBAAkB,CAAC,2BAA2B,EAAE,iCAAiC,CAAC,CAAC;IAC/F,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,EAAmB,EAAE;QAC5D,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YAC9B,OAAO,oBAAoB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7C,CAAC;QACD,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC;QACjD,IAAI,eAAe,KAAK,SAAS,IAAI,eAAe,KAAK,EAAE,EAAE,CAAC;YAC5D,MAAM,IAAI,kBAAkB,CAC1B,2BAA2B,EAC3B,mDAAmD,CACpD,CAAC;QACJ,CAAC;QACD,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC;QACjD,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,eAAe;YACzB,WAAW,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE;YAC9B,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,SAAS,IAAI,EAAE;YAC9C,gBAAgB,EAAE,MAAM,CAAC,QAAQ,EAAE,QAAQ,IAAI,EAAE;YACjD,qBAAqB,EAAE,MAAM,CAAC,QAAQ,EAAE,SAAS,IAAI,EAAE;YACvD,YAAY,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE;YAC/B,eAAe,EAAE,MAAM,CAAC,OAAO,EAAE,QAAQ,IAAI,EAAE;YAC/C,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE;YACvC,YAAY,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;YACzC,YAAY;YACZ,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE;YACrC,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC;YACjD,SAAS,EAAE,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC;YACnD,UAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,UAAU,IAAI,EAAE;YAC7C,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC;YACjD,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC;YACnD,cAAc,EAAE,SAAS,CAAC,MAAM,CAAC,SAAS,EAAE,cAAc,CAAC;YAC3D,GAAG,EAAE,MAAM,CAAC,SAAS,EAAE,GAAG,IAAI,EAAE;YAChC,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,IAAI,EAAE;YACtC,SAAS,EAAE,WAAW,CAAC,YAAY,CAAC;YACpC,YAAY,EAAE,cAAc,CAAC,eAAe,CAAC;SAC9C,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,6BAA6B,CAAC,SAAS,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,OAG3C;IACC,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,0BAA0B,CAAC;IACnE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,kBAAkB,CAAC,2BAA2B,EAAE,6BAA6B,CAAC,CAAC;IAC3F,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC;IACzD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QAC/B,MAAM,QAAQ,GAAG,MAAM,OAAO,CAC5B,2BAA2B,EAC3B,uBAAuB,CAAC,QAAQ,CAAC,EACjC,EAAE,SAAS,EAAE,CACd,CAAC;QACF,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAY,CAAC;IAC5C,CAAC,CAAC,CACH,CAAC;IACF,OAAO,4BAA4B,CAAC,QAAQ,CAAC,CAAC;AAChD,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,MAAM,uBAAuB,GAA4C;IAC9E,EAAE,EAAE,2BAA2B;IAC/B,IAAI,EAAE,mBAAmB;IACzB,IAAI,EAAE,MAAM;IACZ,WAAW,EACT,uKAAuK;IACzK,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAC3B,qBAAqB,CAAC,OAAO,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;IACjG,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,4BAA4B,CAAC,OAAO,CAAC;IACzD,WAAW,EAAE,GAAG,EAAE,CAChB,4BAA4B,CAAC,eAAe,CAAC,kCAAkC,CAAC,CAAC;CACpF,CAAC"}
|