@mailwoman/bdc 8.3.0
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 +7 -0
- package/index.ts +2 -0
- package/out/index.d.ts +3 -0
- package/out/index.d.ts.map +1 -0
- package/out/index.js +3 -0
- package/out/index.js.map +1 -0
- package/out/schema.d.ts +138 -0
- package/out/schema.d.ts.map +1 -0
- package/out/schema.js +88 -0
- package/out/schema.js.map +1 -0
- package/out/sdk/build-bdc.d.ts +214 -0
- package/out/sdk/build-bdc.d.ts.map +1 -0
- package/out/sdk/build-bdc.js +500 -0
- package/out/sdk/build-bdc.js.map +1 -0
- package/out/sdk/buildings.d.ts +84 -0
- package/out/sdk/buildings.d.ts.map +1 -0
- package/out/sdk/buildings.js +75 -0
- package/out/sdk/buildings.js.map +1 -0
- package/out/sdk/client.d.ts +237 -0
- package/out/sdk/client.d.ts.map +1 -0
- package/out/sdk/client.js +430 -0
- package/out/sdk/client.js.map +1 -0
- package/out/sdk/common.d.ts +279 -0
- package/out/sdk/common.d.ts.map +1 -0
- package/out/sdk/common.js +162 -0
- package/out/sdk/common.js.map +1 -0
- package/out/sdk/data-collection.d.ts +244 -0
- package/out/sdk/data-collection.d.ts.map +1 -0
- package/out/sdk/data-collection.js +50 -0
- package/out/sdk/data-collection.js.map +1 -0
- package/out/sdk/download.d.ts +36 -0
- package/out/sdk/download.d.ts.map +1 -0
- package/out/sdk/download.js +76 -0
- package/out/sdk/download.js.map +1 -0
- package/out/sdk/filing-dates.d.ts +57 -0
- package/out/sdk/filing-dates.d.ts.map +1 -0
- package/out/sdk/filing-dates.js +50 -0
- package/out/sdk/filing-dates.js.map +1 -0
- package/out/sdk/filing-landscape.d.ts +124 -0
- package/out/sdk/filing-landscape.d.ts.map +1 -0
- package/out/sdk/filing-landscape.js +216 -0
- package/out/sdk/filing-landscape.js.map +1 -0
- package/out/sdk/index.d.ts +15 -0
- package/out/sdk/index.d.ts.map +1 -0
- package/out/sdk/index.js +15 -0
- package/out/sdk/index.js.map +1 -0
- package/out/sdk/list-files.d.ts +39 -0
- package/out/sdk/list-files.d.ts.map +1 -0
- package/out/sdk/list-files.js +26 -0
- package/out/sdk/list-files.js.map +1 -0
- package/out/sdk/location.d.ts +40 -0
- package/out/sdk/location.d.ts.map +1 -0
- package/out/sdk/location.js +23 -0
- package/out/sdk/location.js.map +1 -0
- package/out/sdk/nearest-infrastructure.d.ts +84 -0
- package/out/sdk/nearest-infrastructure.d.ts.map +1 -0
- package/out/sdk/nearest-infrastructure.js +84 -0
- package/out/sdk/nearest-infrastructure.js.map +1 -0
- package/out/sdk/parsing.d.ts +57 -0
- package/out/sdk/parsing.d.ts.map +1 -0
- package/out/sdk/parsing.js +87 -0
- package/out/sdk/parsing.js.map +1 -0
- package/out/sdk/plausibility.d.ts +256 -0
- package/out/sdk/plausibility.d.ts.map +1 -0
- package/out/sdk/plausibility.js +334 -0
- package/out/sdk/plausibility.js.map +1 -0
- package/out/sdk/technologies.d.ts +229 -0
- package/out/sdk/technologies.d.ts.map +1 -0
- package/out/sdk/technologies.js +217 -0
- package/out/sdk/technologies.js.map +1 -0
- package/package.json +97 -0
- package/schema.ts +172 -0
- package/sdk/build-bdc.ts +810 -0
- package/sdk/buildings.ts +87 -0
- package/sdk/client.ts +643 -0
- package/sdk/common.ts +363 -0
- package/sdk/data-collection.ts +268 -0
- package/sdk/download.ts +91 -0
- package/sdk/filing-dates.ts +88 -0
- package/sdk/filing-landscape.ts +287 -0
- package/sdk/index.ts +14 -0
- package/sdk/list-files.ts +72 -0
- package/sdk/location.ts +45 -0
- package/sdk/nearest-infrastructure.ts +129 -0
- package/sdk/parsing.ts +124 -0
- package/sdk/plausibility.ts +512 -0
- package/sdk/technologies.ts +258 -0
package/sdk/common.ts
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file BDC file metadata model — dictionaries for the FCC's `bdc_file` listing rows, the
|
|
6
|
+
* raw-to-parsed record shape, and file-ordering comparators.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Tagged } from "type-fest"
|
|
10
|
+
|
|
11
|
+
import type { BroadbandTechnologyCode } from "./technologies.ts"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Unique identifier for an FCC BDC broadband provider.
|
|
15
|
+
*
|
|
16
|
+
* Re-homed locally as `Tagged<number, ...>` — the Nexus original imported this from `@isp.nexus/fcc`
|
|
17
|
+
* (`BroadbandProvider.ts`), where it's defined identically.
|
|
18
|
+
*
|
|
19
|
+
* @category BDC
|
|
20
|
+
* @category FCC
|
|
21
|
+
*/
|
|
22
|
+
export type ProviderID = Tagged<number, "ProviderID">
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The data type of the file, e.g. what kind of data is in the file.
|
|
26
|
+
*
|
|
27
|
+
* @category BDC
|
|
28
|
+
* @category FCC
|
|
29
|
+
*/
|
|
30
|
+
export const BDCFilingDataType = {
|
|
31
|
+
/**
|
|
32
|
+
* The file contains data about the availability of broadband with a specific provider.
|
|
33
|
+
*/
|
|
34
|
+
Availability: "availability",
|
|
35
|
+
/**
|
|
36
|
+
* The file contains data about the challenge process.
|
|
37
|
+
*/
|
|
38
|
+
Challenge: "challenge",
|
|
39
|
+
} as const
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @category BDC
|
|
43
|
+
* @category FCC
|
|
44
|
+
*/
|
|
45
|
+
export type BDCFilingDataType = (typeof BDCFilingDataType)[keyof typeof BDCFilingDataType]
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @category BDC
|
|
49
|
+
* @category FCC
|
|
50
|
+
*/
|
|
51
|
+
export const BDCGISFileType = {
|
|
52
|
+
ShapeFile: 1,
|
|
53
|
+
GeoPackage: 2,
|
|
54
|
+
} as const
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @category BDC
|
|
58
|
+
* @category FCC
|
|
59
|
+
*/
|
|
60
|
+
export type BDCGISFileType = (typeof BDCGISFileType)[keyof typeof BDCGISFileType]
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The type of file, e.g. what format the file is in.
|
|
64
|
+
*
|
|
65
|
+
* @category BDC
|
|
66
|
+
* @category FCC
|
|
67
|
+
*/
|
|
68
|
+
export const BDCFileFormat = {
|
|
69
|
+
CSV: "csv",
|
|
70
|
+
GIS: "gis",
|
|
71
|
+
} as const
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @category BDC
|
|
75
|
+
* @category FCC
|
|
76
|
+
*/
|
|
77
|
+
export type BDCFileFormat = (typeof BDCFileFormat)[keyof typeof BDCFileFormat]
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @category BDC
|
|
81
|
+
* @category FCC
|
|
82
|
+
*/
|
|
83
|
+
export const BDCFileCategory = {
|
|
84
|
+
Provider: "Provider",
|
|
85
|
+
Summary: "Summary",
|
|
86
|
+
State: "State",
|
|
87
|
+
} as const
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @category BDC
|
|
91
|
+
* @category FCC
|
|
92
|
+
*/
|
|
93
|
+
export type BDCFileCategory = (typeof BDCFileCategory)[keyof typeof BDCFileCategory]
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @category BDC
|
|
97
|
+
* @category FCC
|
|
98
|
+
*/
|
|
99
|
+
export const BDCProviderSubCategory = {
|
|
100
|
+
FixedBroadband: "Fixed Broadband",
|
|
101
|
+
MobileBroadband: "Mobile Broadband",
|
|
102
|
+
MobileVoice: "Mobile Voice",
|
|
103
|
+
SupportingData: "Supporting Data",
|
|
104
|
+
} as const
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @category BDC
|
|
108
|
+
* @category FCC
|
|
109
|
+
*/
|
|
110
|
+
export type BDCProviderSubCategory = (typeof BDCProviderSubCategory)[keyof typeof BDCProviderSubCategory]
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @category BDC
|
|
114
|
+
* @category FCC
|
|
115
|
+
*/
|
|
116
|
+
export const BDCSummarySubCategory = {
|
|
117
|
+
BroadbandSummaryByGeography: "Broadband Summary by Geography Type",
|
|
118
|
+
ProviderSummaryByGeography: "Provider Summary by Geography Type",
|
|
119
|
+
ProviderSummaryFixedBroadband: "Provider Summary - Fixed Broadband",
|
|
120
|
+
ProviderSummaryMobileBroadband: "Provider Summary - Mobile Broadband",
|
|
121
|
+
} as const
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @category BDC
|
|
125
|
+
* @category FCC
|
|
126
|
+
*/
|
|
127
|
+
export type BDCSummarySubCategory = (typeof BDCSummarySubCategory)[keyof typeof BDCSummarySubCategory]
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @category BDC
|
|
131
|
+
* @category FCC
|
|
132
|
+
*/
|
|
133
|
+
export const BDCStateSubCategory = {
|
|
134
|
+
FixedBroadband: "Fixed Broadband",
|
|
135
|
+
MobileBroadband: "Mobile Broadband",
|
|
136
|
+
MobileVoice: "Mobile Voice",
|
|
137
|
+
} as const
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @category BDC
|
|
141
|
+
* @category FCC
|
|
142
|
+
*/
|
|
143
|
+
export type BDCStateSubCategory = (typeof BDCStateSubCategory)[keyof typeof BDCStateSubCategory]
|
|
144
|
+
|
|
145
|
+
export type BDCSubCategory = BDCProviderSubCategory | BDCStateSubCategory | BDCSummarySubCategory
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* A single row from the FCC's BDC file listing, as returned by the API before parsing.
|
|
149
|
+
*
|
|
150
|
+
* @category BDC
|
|
151
|
+
* @category FCC
|
|
152
|
+
*/
|
|
153
|
+
export interface RawBDCFile {
|
|
154
|
+
file_id: number
|
|
155
|
+
category: BDCFileCategory
|
|
156
|
+
subcategory: BDCSubCategory
|
|
157
|
+
/**
|
|
158
|
+
* Comma-separated list of technology codes.
|
|
159
|
+
*
|
|
160
|
+
* Nullable in live data: the FCC's `/map/downloads/listAvailabilityData` response carries `technology_code: null` for
|
|
161
|
+
* at least some State-category rows (first observed in the live FCC smoke test, see
|
|
162
|
+
* `.superpowers/sdd/2026-07-30-bdc-2b-plan/live-smoke-findings.md`). Guarded in {@linkcode parseRawBDCFile} — a null
|
|
163
|
+
* value parses to an empty `technologyCodes` set rather than throwing.
|
|
164
|
+
*
|
|
165
|
+
* @see {@link BroadbandTechnologyCode}
|
|
166
|
+
*/
|
|
167
|
+
technology_code: string | null
|
|
168
|
+
technology_code_desc: string
|
|
169
|
+
/**
|
|
170
|
+
* 2-digit state or territory FIPS code.
|
|
171
|
+
*
|
|
172
|
+
* Loosely typed as `string` for now. The Nexus original was `AdminLevel1Code` (via `@isp.nexus/tiger`); this port
|
|
173
|
+
* drops that dependency, same as `data-collection.ts`'s `FCCStateID`. Task 2c tightens this against
|
|
174
|
+
* `@mailwoman/tiger` if a downstream dictionary needs the literal union.
|
|
175
|
+
*
|
|
176
|
+
* Nullable in live data for rows not scoped to a specific state (e.g. Provider-category rows). Guarded in
|
|
177
|
+
* {@linkcode parseRawBDCFile} — a null value parses to an empty `stateCode` string.
|
|
178
|
+
*/
|
|
179
|
+
state_fips: string | null
|
|
180
|
+
/**
|
|
181
|
+
* State or territory name.
|
|
182
|
+
*
|
|
183
|
+
* Loosely typed as `string` — the Nexus original was `StateName` (via `@isp.nexus/tiger`). Same task-2c deferral as
|
|
184
|
+
* `state_fips` above.
|
|
185
|
+
*/
|
|
186
|
+
state_name: string
|
|
187
|
+
/**
|
|
188
|
+
* Nullable in live data for rows not scoped to a specific provider (e.g. State/Summary-category rows). Guarded in
|
|
189
|
+
* {@linkcode parseRawBDCFile} — a null value parses to a `providerID` of `0`.
|
|
190
|
+
*/
|
|
191
|
+
provider_id: string | null
|
|
192
|
+
/**
|
|
193
|
+
* Nullable in live data — travels with `provider_id` (see above). Guarded in {@linkcode parseRawBDCFile} — a null
|
|
194
|
+
* value parses to an empty `providerName` string.
|
|
195
|
+
*/
|
|
196
|
+
provider_name: string | null
|
|
197
|
+
file_type: string
|
|
198
|
+
file_name: string
|
|
199
|
+
record_count: string
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* A parsed FCC BDC file-listing entry.
|
|
204
|
+
*
|
|
205
|
+
* @category BDC
|
|
206
|
+
* @category FCC
|
|
207
|
+
*/
|
|
208
|
+
export interface BDCFile {
|
|
209
|
+
/**
|
|
210
|
+
* Unique identifier for the file, defined by the FCC.
|
|
211
|
+
*/
|
|
212
|
+
fileID: number
|
|
213
|
+
|
|
214
|
+
revision: Date
|
|
215
|
+
vintage: Date
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The date the file was was downloaded, parsed, and stored in the database.
|
|
219
|
+
*/
|
|
220
|
+
synchronizedAt?: Date
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The category of the file.
|
|
224
|
+
*/
|
|
225
|
+
category: BDCFileCategory
|
|
226
|
+
/**
|
|
227
|
+
* The subcategory of the file.
|
|
228
|
+
*/
|
|
229
|
+
subcategory: BDCSubCategory
|
|
230
|
+
/**
|
|
231
|
+
* The technology codes in the file. Empty when the raw `technology_code` was `null`.
|
|
232
|
+
*/
|
|
233
|
+
technologyCodes: Set<BroadbandTechnologyCode>
|
|
234
|
+
/**
|
|
235
|
+
* The state or territory FIPS code.
|
|
236
|
+
*
|
|
237
|
+
* Loosely typed as `string` — see {@linkcode RawBDCFile} for the task-2c deferral. Empty string when the raw
|
|
238
|
+
* `state_fips` was `null`.
|
|
239
|
+
*/
|
|
240
|
+
stateCode: string
|
|
241
|
+
/**
|
|
242
|
+
* The provider ID associated with the file. `0` when the raw `provider_id` was `null` (no specific provider — see
|
|
243
|
+
* {@linkcode RawBDCFile}).
|
|
244
|
+
*/
|
|
245
|
+
providerID: ProviderID
|
|
246
|
+
/**
|
|
247
|
+
* The provider name associated with the file. Empty string when the raw `provider_name` was `null`.
|
|
248
|
+
*/
|
|
249
|
+
providerName: string
|
|
250
|
+
/**
|
|
251
|
+
* The number of records in the file.
|
|
252
|
+
*/
|
|
253
|
+
recordCount: number
|
|
254
|
+
/**
|
|
255
|
+
* The type of file, e.g. what format the file is in.
|
|
256
|
+
*/
|
|
257
|
+
fileType: string
|
|
258
|
+
/**
|
|
259
|
+
* The name of the file, as provided by the FCC.
|
|
260
|
+
*/
|
|
261
|
+
fileName: string
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const BDCFileNamePattern = /([A-Z])(\d+)_(\d{2})([a-z]{3})(\d{4})$/
|
|
265
|
+
|
|
266
|
+
const MonthAbbreviation = {
|
|
267
|
+
jan: 0,
|
|
268
|
+
feb: 1,
|
|
269
|
+
mar: 2,
|
|
270
|
+
apr: 3,
|
|
271
|
+
may: 4,
|
|
272
|
+
jun: 5,
|
|
273
|
+
jul: 6,
|
|
274
|
+
aug: 7,
|
|
275
|
+
sep: 8,
|
|
276
|
+
oct: 9,
|
|
277
|
+
nov: 10,
|
|
278
|
+
dec: 11,
|
|
279
|
+
} as const
|
|
280
|
+
|
|
281
|
+
export type MonthAbbreviation = keyof typeof MonthAbbreviation
|
|
282
|
+
|
|
283
|
+
const VintageMonthLetter = {
|
|
284
|
+
/**
|
|
285
|
+
* December
|
|
286
|
+
*/
|
|
287
|
+
D: 11,
|
|
288
|
+
/**
|
|
289
|
+
* June
|
|
290
|
+
*/
|
|
291
|
+
J: 5,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
type VintageMonthLetter = keyof typeof VintageMonthLetter
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Given a BDC file, parse the components of the file name.
|
|
298
|
+
*/
|
|
299
|
+
export function parseBDCFileTimestamps(fileName: string) {
|
|
300
|
+
const match = fileName.match(BDCFileNamePattern)
|
|
301
|
+
|
|
302
|
+
if (!match) throw new Error(`Invalid BDC file name: ${fileName}`)
|
|
303
|
+
const [, vintageMonthLetter, vintageYearAbbreviation, revisionDay, revisionMonthAbbreviation, revisionYear] = match
|
|
304
|
+
|
|
305
|
+
const revisionMonth = MonthAbbreviation[revisionMonthAbbreviation as MonthAbbreviation]
|
|
306
|
+
const revision = new Date(Number.parseInt(revisionYear!, 10), revisionMonth, Number.parseInt(revisionDay!, 10))
|
|
307
|
+
|
|
308
|
+
const vintageMonth = VintageMonthLetter[vintageMonthLetter as VintageMonthLetter]
|
|
309
|
+
const vintageYear = Number.parseInt(`20${vintageYearAbbreviation}`, 10)
|
|
310
|
+
|
|
311
|
+
const vintage = new Date(vintageYear, vintageMonth)
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
revision,
|
|
315
|
+
vintage,
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Parses a raw BDC file-listing entry into a {@linkcode BDCFile}.
|
|
321
|
+
*/
|
|
322
|
+
export function parseRawBDCFile(raw: RawBDCFile): BDCFile {
|
|
323
|
+
const parsedBDC: BDCFile = {
|
|
324
|
+
...parseBDCFileTimestamps(raw.file_name),
|
|
325
|
+
fileName: raw.file_name,
|
|
326
|
+
fileType: raw.file_type,
|
|
327
|
+
fileID: raw.file_id,
|
|
328
|
+
recordCount: Number.parseInt(raw.record_count, 10),
|
|
329
|
+
category: raw.category,
|
|
330
|
+
subcategory: raw.subcategory,
|
|
331
|
+
technologyCodes: new Set(
|
|
332
|
+
raw.technology_code === null
|
|
333
|
+
? []
|
|
334
|
+
: raw.technology_code.split(",").map((code) => Number.parseInt(code, 10) as BroadbandTechnologyCode)
|
|
335
|
+
),
|
|
336
|
+
stateCode: raw.state_fips ?? "",
|
|
337
|
+
providerID: (raw.provider_id === null ? 0 : Number.parseInt(raw.provider_id, 10)) as ProviderID,
|
|
338
|
+
providerName: raw.provider_name ?? "",
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return parsedBDC
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Comparator for sorting {@linkcode BDCFile} records ascending by revision date.
|
|
346
|
+
*/
|
|
347
|
+
export function compareRevisionAsc(a: BDCFile, b: BDCFile): number {
|
|
348
|
+
return a.revision.getTime() - b.revision.getTime()
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Comparator for sorting {@linkcode BDCFile} records ascending by provider ID.
|
|
353
|
+
*/
|
|
354
|
+
export function compareProviderIDAsc(a: BDCFile, b: BDCFile): number {
|
|
355
|
+
return a.providerID - b.providerID
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Comparator for sorting {@linkcode BDCFile} records ascending by state FIPS code.
|
|
360
|
+
*/
|
|
361
|
+
export function compareStateCodeAsc(a: BDCFile, b: BDCFile): number {
|
|
362
|
+
return Number.parseInt(a.stateCode, 10) - Number.parseInt(b.stateCode, 10)
|
|
363
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file FCC Broadband Data Collection System
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { BuildingTypeCode } from "./buildings.ts"
|
|
9
|
+
import type { BroadbandServicableLocationID } from "./location.ts"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Snake_case identifier for a US State or Territory.
|
|
13
|
+
*
|
|
14
|
+
* Loosely typed as `string` for now. The Nexus original derived this from `StateName` (via `@isp.nexus/tiger`); this
|
|
15
|
+
* port drops that dependency since 2a doesn't consume the literal union anywhere. Task 2c tightens this against
|
|
16
|
+
* `@mailwoman/tiger`'s `StateName` if a downstream dictionary needs it.
|
|
17
|
+
*
|
|
18
|
+
* @category FCC
|
|
19
|
+
* @title FCC State Identifier
|
|
20
|
+
*/
|
|
21
|
+
export type FCCStateID = string
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Code indicating confidence in the association between the textual address and the location.
|
|
25
|
+
*
|
|
26
|
+
* @category NTIA
|
|
27
|
+
* @category FCC
|
|
28
|
+
*/
|
|
29
|
+
export const AddressConfidenceCode = {
|
|
30
|
+
High: "1",
|
|
31
|
+
Medium: "2",
|
|
32
|
+
Low: "3",
|
|
33
|
+
SuccessfulChallenge: "4",
|
|
34
|
+
} as const
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @category NTIA
|
|
38
|
+
* @category FCC
|
|
39
|
+
*/
|
|
40
|
+
export type AddressConfidenceCode = (typeof AddressConfidenceCode)[keyof typeof AddressConfidenceCode]
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A modeled land use designation estimated from assembled county assessor information.
|
|
44
|
+
*
|
|
45
|
+
* @category NTIA
|
|
46
|
+
* @category FCC
|
|
47
|
+
*/
|
|
48
|
+
export const LandUseCode = {
|
|
49
|
+
Residential: 1,
|
|
50
|
+
Land: 2,
|
|
51
|
+
Business: 3,
|
|
52
|
+
Unknown: 4,
|
|
53
|
+
Agriculture: 5,
|
|
54
|
+
Community: 6,
|
|
55
|
+
Industrial: 7,
|
|
56
|
+
Recreation: 8,
|
|
57
|
+
Utility: 9,
|
|
58
|
+
MixedUse: 10,
|
|
59
|
+
Transportation: 11,
|
|
60
|
+
Water: 12,
|
|
61
|
+
Communications: 13,
|
|
62
|
+
Row: 14,
|
|
63
|
+
Wireless: 15,
|
|
64
|
+
Other: 0,
|
|
65
|
+
} as const
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @category NTIA
|
|
69
|
+
* @category FCC
|
|
70
|
+
*/
|
|
71
|
+
export type LandUseCode = (typeof LandUseCode)[keyof typeof LandUseCode]
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Flag indicating if the location is a broadband serviceable location.
|
|
75
|
+
*/
|
|
76
|
+
export const BSLFlag = {
|
|
77
|
+
Serviceable: 1,
|
|
78
|
+
NotServiceable: 0,
|
|
79
|
+
} as const
|
|
80
|
+
|
|
81
|
+
export type BSLFlag = (typeof BSLFlag)[keyof typeof BSLFlag]
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Interface representing a record from the NTIA data dictionary.
|
|
85
|
+
*
|
|
86
|
+
* @category NTIA
|
|
87
|
+
* @title NTIA Record
|
|
88
|
+
*/
|
|
89
|
+
export interface NTIARecord {
|
|
90
|
+
/**
|
|
91
|
+
* Unique ID for the Fabric location. Remains persistent across versions, even if the location's position or building
|
|
92
|
+
* changes.
|
|
93
|
+
*
|
|
94
|
+
* @title Location ID
|
|
95
|
+
* @pattern ^\d{10}$
|
|
96
|
+
*/
|
|
97
|
+
location_id: BroadbandServicableLocationID
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Primary postal address excluding city, state, and ZIP code.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* 123 Main St
|
|
104
|
+
*
|
|
105
|
+
* @title Primary Address
|
|
106
|
+
*/
|
|
107
|
+
address_primary: string
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* City name from the postal address. Also known as the locality.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* San Francisco
|
|
114
|
+
*
|
|
115
|
+
* @title City
|
|
116
|
+
*/
|
|
117
|
+
city: string
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 2-character state abbreviation from the postal address.
|
|
121
|
+
*
|
|
122
|
+
* Loosely typed as `string` — see {@linkcode FCCStateID} for why the tiger-backed literal union is deferred to task
|
|
123
|
+
* 2c.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* CA
|
|
127
|
+
*
|
|
128
|
+
* @title State Abbreviation
|
|
129
|
+
*/
|
|
130
|
+
state: string
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 5-digit ZIP code associated with the address.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* 94103
|
|
137
|
+
*
|
|
138
|
+
* @title ZIP Code
|
|
139
|
+
*/
|
|
140
|
+
zip: string
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* USPS ZIP+4 extension.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* e.g. 94103-1234
|
|
147
|
+
*
|
|
148
|
+
* @title ZIP+4
|
|
149
|
+
*/
|
|
150
|
+
zip_suffix?: string
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Estimate of the number of units at the location. Includes both residential and non-residential units.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* 10
|
|
157
|
+
*
|
|
158
|
+
* @title Unit Count
|
|
159
|
+
*/
|
|
160
|
+
unit_count: number
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Flag indicating if the location is a broadband serviceable location. 1 for serviceable, 0 for not.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* 1
|
|
167
|
+
*
|
|
168
|
+
* @title BSL Flag
|
|
169
|
+
*/
|
|
170
|
+
bsl_flag: BSLFlag
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Code indicating the type of building at the location. Can be residential (R), non-residential (B), mixed (X), group
|
|
174
|
+
* quarters (G), CAI (C), enterprise (E), or other (O).
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* R
|
|
178
|
+
*
|
|
179
|
+
* @title Building Type Code
|
|
180
|
+
*/
|
|
181
|
+
building_type_code: BuildingTypeCode
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Modeled land use designation from county assessor information. Possible values range from 0 (Other) to 15
|
|
185
|
+
* (Wireless).
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* 1
|
|
189
|
+
*
|
|
190
|
+
* @title Land Use Code
|
|
191
|
+
*/
|
|
192
|
+
land_use_code: LandUseCode | null
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Code indicating confidence in the association between the textual address and the location. 1 = High, 2 = Medium, 3
|
|
196
|
+
* = Low, 4 = successful address challenge.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* 1
|
|
200
|
+
*
|
|
201
|
+
* @title Address Confidence Code
|
|
202
|
+
*/
|
|
203
|
+
address_confidence_code: AddressConfidenceCode
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 5-digit TIGER 2020 identifier for the county based on the latitude and longitude.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* 06075
|
|
210
|
+
*
|
|
211
|
+
* @title County GEOID
|
|
212
|
+
*/
|
|
213
|
+
county_geoid: string
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 15-digit 2020 U.S. Census Bureau FIPS code for the census block, based on the latitude and longitude.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* 060750001000123
|
|
220
|
+
*
|
|
221
|
+
* @title Block GEOID
|
|
222
|
+
*/
|
|
223
|
+
block_geoid: string
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* H3 hex cell ID, level 9, for geospatial referencing.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* 8928308280fffff
|
|
230
|
+
*
|
|
231
|
+
* @title H3 Cell ID (9)
|
|
232
|
+
*/
|
|
233
|
+
h3_9: string
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Latitude coordinate of the location in decimal degrees. Uses WGS84 format with 5-digit precision.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* 37.774929
|
|
240
|
+
*
|
|
241
|
+
* @minimum -90
|
|
242
|
+
* @maximum 90
|
|
243
|
+
* @title Latitude
|
|
244
|
+
*/
|
|
245
|
+
latitude: number
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Longitude coordinate of the location in decimal degrees. Uses WGS84 format with 5-digit precision.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* e.g. -122.419416
|
|
252
|
+
*
|
|
253
|
+
* @minimum -180
|
|
254
|
+
* @maximum 180
|
|
255
|
+
* @title Longitude
|
|
256
|
+
*/
|
|
257
|
+
longitude: number
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* FCC Fabric Release Date, present only for tier 2, 3, and 4 licensees.
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* 07212023
|
|
264
|
+
*
|
|
265
|
+
* @title FCC Fabric Release Date
|
|
266
|
+
*/
|
|
267
|
+
fcc_rel?: string
|
|
268
|
+
}
|
package/sdk/download.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file FCC BDC availability-file download + zip extraction.
|
|
6
|
+
*
|
|
7
|
+
* Re-homed from Nexus's `sync/fcc/bdc/download-file.ts` (relicense-by-copy, no provenance headers),
|
|
8
|
+
* trimmed hard: the Nexus original downloaded AND cached the `.zip`, extracted it, THEN wrote a Parquet
|
|
9
|
+
* file with a row-count integrity check. All Parquet machinery is dropped here — 2a's `bdc.db` is
|
|
10
|
+
* SQLite, not Parquet-backed (see Task 7+) — and the `.zip` itself isn't cached either; only the
|
|
11
|
+
* extracted CSV is written to `destinationDir`, and its presence alone is the cache check.
|
|
12
|
+
*
|
|
13
|
+
* The zip-extraction library also changes: the Nexus original's `extractSingleFileZip` used `adm-zip`
|
|
14
|
+
* (a repo-wide Nexus dependency). No unzip dependency exists anywhere in this repo — every workspace
|
|
15
|
+
* `package.json` was checked, `tiger/` and `osm/` included — so `yauzl-promise` is added to `bdc` alone
|
|
16
|
+
* (noted in this task's commit body per the brief).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as fs from "node:fs/promises"
|
|
20
|
+
import * as path from "node:path"
|
|
21
|
+
|
|
22
|
+
import { fromBuffer } from "yauzl-promise"
|
|
23
|
+
|
|
24
|
+
import type { BDCClient } from "./client.ts"
|
|
25
|
+
import { BDCFilingDataType, type BDCFile } from "./common.ts"
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract the first file entry of a zip archive buffer into a single in-memory `Buffer`.
|
|
29
|
+
*
|
|
30
|
+
* BDC availability downloads are always a single zip-wrapped CSV, so — like the Nexus original — this doesn't walk
|
|
31
|
+
* every entry, just the first non-directory one.
|
|
32
|
+
*/
|
|
33
|
+
async function extractSingleFileZip(zippedBuffer: Buffer): Promise<Buffer> {
|
|
34
|
+
const zip = await fromBuffer(zippedBuffer)
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
for await (const entry of zip) {
|
|
38
|
+
if (entry.filename.endsWith("/")) continue
|
|
39
|
+
|
|
40
|
+
const readStream = await entry.openReadStream()
|
|
41
|
+
const chunks: Buffer[] = []
|
|
42
|
+
|
|
43
|
+
for await (const chunk of readStream) {
|
|
44
|
+
chunks.push(chunk)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return Buffer.concat(chunks)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw new Error("extractSingleFileZip: no file entries found in zip archive.")
|
|
51
|
+
} finally {
|
|
52
|
+
await zip.close()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Download and cache an FCC BDC availability file, extracting its zip-wrapped CSV to `destinationDir`.
|
|
58
|
+
*
|
|
59
|
+
* Cache-if-exists: if the extracted CSV already exists at the destination path, this returns immediately without
|
|
60
|
+
* issuing any network request. Otherwise it downloads the zip via `client`, extracts the CSV, writes it to
|
|
61
|
+
* `destinationDir`, and returns the written path. Only the extracted CSV is ever cached — the intermediate `.zip` is
|
|
62
|
+
* never written to disk.
|
|
63
|
+
*
|
|
64
|
+
* THIS FILE OWNS THE CACHE FOR THE DOWNLOAD PATH, which is why `BDCClient.getArrayBuffer` switches the client's own
|
|
65
|
+
* response cache off: the `existsSync`-equivalent check above is the real cache hit, and running a
|
|
66
|
+
* multi-hundred-megabyte archive through a JSON-validating disk cache would write a second, unreadable copy of a file
|
|
67
|
+
* already on disk here.
|
|
68
|
+
*
|
|
69
|
+
* @returns The path of the extracted (and now cached) CSV file.
|
|
70
|
+
*/
|
|
71
|
+
export async function downloadBDCFile(client: BDCClient, file: BDCFile, destinationDir: string): Promise<string> {
|
|
72
|
+
const csvPath = path.join(destinationDir, `${file.fileName}.csv`)
|
|
73
|
+
|
|
74
|
+
const alreadyCached = await fs
|
|
75
|
+
.access(csvPath)
|
|
76
|
+
.then(() => true)
|
|
77
|
+
.catch(() => false)
|
|
78
|
+
|
|
79
|
+
if (alreadyCached) return csvPath
|
|
80
|
+
|
|
81
|
+
const zippedArrayBuffer = await client.getArrayBuffer(
|
|
82
|
+
`/map/downloads/downloadFile/${BDCFilingDataType.Availability}/${file.fileID}`
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const csvBuffer = await extractSingleFileZip(Buffer.from(zippedArrayBuffer))
|
|
86
|
+
|
|
87
|
+
await fs.mkdir(destinationDir, { recursive: true })
|
|
88
|
+
await fs.writeFile(csvPath, csvBuffer)
|
|
89
|
+
|
|
90
|
+
return csvPath
|
|
91
|
+
}
|