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

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 CHANGED
@@ -4,7 +4,7 @@ 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 24 source adapters behind one interface.
7
+ - Exposes 26 source adapters behind one interface.
8
8
  - Fetches live data from each source and parses it into plain objects.
9
9
  - Loads a committed fixture when the network is unavailable.
10
10
  - Publishes the `US_DATA_SOURCES` registry for the API and the CLI.
@@ -53,6 +53,9 @@ 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
+ | `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
59
 
57
60
  The HTTP API and the CLI read this package's registry. A new adapter is exposed on
58
61
  `/api/sources`, `/api/sources/{id}/probe`, and `/api/sources/{id}/data` as soon as it is registered.
@@ -277,6 +280,53 @@ validates a bare JSON array against a zod schema over `id`, `name`,
277
280
  throws `UsSourceParseError`. A response with no rows also throws, because an
278
281
  empty catalogue is not a usable answer.
279
282
 
283
+ `bts-transportation-stats`:
284
+
285
+ ```ts
286
+ import {
287
+ buildBtsTransportationStatsUrl,
288
+ parseBtsTransportationStatsPayload,
289
+ } from '@usa-open-data-connectors/usa-sources';
290
+
291
+ const url = buildBtsTransportationStatsUrl({ limit: 10_000 });
292
+ const response = await fetch(url);
293
+ if (!response.ok) {
294
+ throw new Error(`HTTP ${response.status} from ${url}`);
295
+ }
296
+ const catalogue = parseBtsTransportationStatsPayload(await response.json());
297
+ console.log(catalogue.entryCount, catalogue.datasetCount, catalogue.categories[0]);
298
+ ```
299
+
300
+ The public API is `BTS_TRANSPORTATION_STATS_API_BASE`,
301
+ `BTS_TRANSPORTATION_STATS_LIMIT`, `buildBtsTransportationStatsUrl`,
302
+ `parseBtsTransportationStatsPayload`, `fetchBtsTransportationStats`, and
303
+ `btsTransportationStatsAdapter`, with the `BtsCatalogueEntry`,
304
+ `BtsCatalogueCategoryCount`, and `BtsTransportationCatalogue` types. The parser
305
+ validates a bare JSON array against a zod schema over `id`, `name`,
306
+ `assetType`, `category`, `viewType`, `createdAt`, `viewLastModified`,
307
+ `viewCount`, and `downloadCount`. A row missing `id`, `name`, or `createdAt`
308
+ throws `UsSourceParseError`. A response with no rows also throws, because an
309
+ empty catalogue is not a usable answer.
310
+
311
+ `nextbus-transit-vehicles`:
312
+
313
+ ```ts
314
+ import { fetchNextBusVehicles } from '@usa-open-data-connectors/usa-sources';
315
+
316
+ const snapshot = await fetchNextBusVehicles();
317
+ console.log(snapshot.vehicleCount, snapshot.vehicles[0]);
318
+ ```
319
+
320
+ The public API is `NEXTBUS_XML_FEED_BASE`, `NEXTBUS_PORTLAND_STREETCAR_AGENCY_TAG`,
321
+ `NEXTBUS_PORTLAND_STREETCAR_ROUTE_TAG`, `NEXTBUS_PORTLAND_STREETCAR_QUERY`,
322
+ `buildNextBusVehicleUrl`, `parseNextBusVehiclePayload`, `fetchNextBusVehicles`,
323
+ and `nextBusTransitVehiclesAdapter`, with the `NextBusVehicle`,
324
+ `NextBusVehicleQuery`, and `NextBusVehicleSnapshot` types. The parser reads
325
+ the XML attributes as strings and validates every field with zod. An error
326
+ body, a missing timestamp, or a position outside the map throws
327
+ `UsSourceParseError`. A route with no vehicles is valid and returns an empty
328
+ `vehicles` list.
329
+
280
330
  `fema-disaster-declarations`:
281
331
 
282
332
  ```ts
@@ -425,6 +475,65 @@ COVID", because keywords and related terms also match. The response has no
425
475
  total count unless the request adds `countTotal=true`, so count `studies` for
426
476
  the page or ask for the total separately.
427
477
 
478
+ `nws-point-forecast`:
479
+
480
+ ```ts
481
+ import { fetchNwsPointForecast } from '@usa-open-data-connectors/usa-sources';
482
+
483
+ const point = await fetchNwsPointForecast({ latitude: 47.601, longitude: -122.33 });
484
+ console.log(point.gridId, point.gridX, point.gridY, point.forecastUrl);
485
+ ```
486
+
487
+ The public API is `NWS_API_BASE`, `NWS_POINT_FORECAST_PATH`,
488
+ `NWS_POINT_FORECAST_SOURCE_ID`, `NWS_USER_AGENT`, `NWS_DEFAULT_LATITUDE`,
489
+ `NWS_DEFAULT_LONGITUDE`, `buildNwsPointUrl`, `parseNwsPointForecastPayload`,
490
+ `fetchNwsPointForecast`, and `nwsPointForecastAdapter`, with the
491
+ `NwsForecastPoint` type. The parser validates the response with a zod schema
492
+ over `type`, `geometry.coordinates`, and a `properties` object carrying
493
+ `city`, `state`, `gridId`, `gridX`, `gridY`, `forecastOffice`, `forecast`,
494
+ `forecastHourly`, `forecastGridData`, `observationStations`, `timeZone`,
495
+ `radarStation`, `county`, `fireWeatherZone`, and the nested
496
+ `relativeLocation.properties` place label. A response that is not a GeoJSON
497
+ feature, has no coordinate pair, or has no grid id, grid cell, forecast URL, or
498
+ forecast office throws `UsSourceParseError`.
499
+
500
+ Three shape notes. The point lookup returns the forecast grid and the URLs that
501
+ lead to the forecast, not the forecast periods themselves; a caller that needs
502
+ temperatures reads `forecastUrl` or `forecastHourlyUrl` next. Grid cell numbers
503
+ are local to an office, so `gridX` 100 in one office and `gridX` 100 in another
504
+ point at different places; always keep `gridId` next to the cell. The place
505
+ label is the nearest place the service knows, not the caller's own name: a
506
+ lookup for Tacoma came back as "Seattle" on 2026-10-20, and a lookup for
507
+ Tacoma Old City Hall landed in office SEW at cell 125,68.
508
+
509
+ `callook-callsigns`:
510
+
511
+ ```ts
512
+ import { fetchCallookCallsigns } from '@usa-open-data-connectors/usa-sources';
513
+
514
+ const directory = await fetchCallookCallsigns({ callsigns: ['W1AW'] });
515
+ console.log(directory.callsigns[0]?.callsign, directory.soonestExpiry?.expiryDate);
516
+ ```
517
+
518
+ Callook answers one callsign per request. The default fetch reads W1ABC through
519
+ W0ABC, one callsign for each US call district. Pass `callsigns` to read a
520
+ different set. The parser keeps a lookup with status `INVALID` or `UPDATING` as
521
+ a record with no licence fields, and counts only `VALID` records in the folded
522
+ totals.
523
+
524
+ `college-scorecard-institutions`:
525
+
526
+ ```ts
527
+ import { fetchCollegeScorecardInstitutions } from '@usa-open-data-connectors/usa-sources';
528
+
529
+ const snapshot = await fetchCollegeScorecardInstitutions();
530
+ console.log(snapshot.institutionCount, snapshot.highestEarningState?.stateAbbr);
531
+ ```
532
+
533
+ The adapter reads the keyless institution-level bulk download, not the keyed
534
+ College Scorecard API. The archive is a zip that holds one wide CSV, and a live
535
+ fetch takes about 20 seconds on a fast connection.
536
+
428
537
  ## Notes and limits
429
538
 
430
539
  - `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 +557,7 @@ the page or ask for the total separately.
448
557
  - `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
558
  - `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
559
  - `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.
560
+ - `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
561
  - `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
562
  - `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
563
  - `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 +609,38 @@ the page or ask for the total separately.
500
609
  - `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
610
  - `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
611
  - `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.
612
+ - `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
613
  - `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
614
  - `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.
615
+ - `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
616
  - `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.
617
+ - `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.
618
+ - `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.
619
+ - `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.
620
+ - `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.
621
+ - `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
622
  - `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
623
  - `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
624
  - Unit tests use the committed fixtures and run offline. Run them with `npm run test --workspace @usa-open-data-connectors/usa-sources`.
511
625
  - 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
626
 
627
+ - `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.
628
+ - `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).
629
+ - `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.
630
+ - `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.
631
+ - `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.
632
+ - `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.
633
+ - `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.
634
+ - `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.
635
+ - `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.
636
+ - `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.
637
+ - `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".
638
+ - `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.
639
+ - `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.
640
+ - `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.
641
+ - `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.
642
+ - `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.
643
+
513
644
  ## Data sources and licences
514
645
 
515
646
  The data comes from US federal agencies and the open data catalogue services they publish on.
@@ -537,6 +668,8 @@ The data comes from US federal agencies and the open data catalogue services the
537
668
  | EstimateTax | `https://estimatetax.net/api/v1/calculate` |
538
669
  | HealthData.gov | `https://healthdata.gov/api/views.json` |
539
670
  | New York City Open Data | `https://data.cityofnewyork.us/api/views.json` |
671
+ | Callook.info | `https://callook.info/[callsign]/json` |
672
+ | College Scorecard, US Department of Education | `https://ed-public-download.scorecard.network/downloads/Most-Recent-Cohorts-Institution_10152026.zip` |
540
673
 
541
674
  Each publisher sets its own terms for its data. The Socrata discovery responses can carry rights and
542
675
  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":"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"}