@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.
Files changed (87) hide show
  1. package/README.md +7 -0
  2. package/index.ts +2 -0
  3. package/out/index.d.ts +3 -0
  4. package/out/index.d.ts.map +1 -0
  5. package/out/index.js +3 -0
  6. package/out/index.js.map +1 -0
  7. package/out/schema.d.ts +138 -0
  8. package/out/schema.d.ts.map +1 -0
  9. package/out/schema.js +88 -0
  10. package/out/schema.js.map +1 -0
  11. package/out/sdk/build-bdc.d.ts +214 -0
  12. package/out/sdk/build-bdc.d.ts.map +1 -0
  13. package/out/sdk/build-bdc.js +500 -0
  14. package/out/sdk/build-bdc.js.map +1 -0
  15. package/out/sdk/buildings.d.ts +84 -0
  16. package/out/sdk/buildings.d.ts.map +1 -0
  17. package/out/sdk/buildings.js +75 -0
  18. package/out/sdk/buildings.js.map +1 -0
  19. package/out/sdk/client.d.ts +237 -0
  20. package/out/sdk/client.d.ts.map +1 -0
  21. package/out/sdk/client.js +430 -0
  22. package/out/sdk/client.js.map +1 -0
  23. package/out/sdk/common.d.ts +279 -0
  24. package/out/sdk/common.d.ts.map +1 -0
  25. package/out/sdk/common.js +162 -0
  26. package/out/sdk/common.js.map +1 -0
  27. package/out/sdk/data-collection.d.ts +244 -0
  28. package/out/sdk/data-collection.d.ts.map +1 -0
  29. package/out/sdk/data-collection.js +50 -0
  30. package/out/sdk/data-collection.js.map +1 -0
  31. package/out/sdk/download.d.ts +36 -0
  32. package/out/sdk/download.d.ts.map +1 -0
  33. package/out/sdk/download.js +76 -0
  34. package/out/sdk/download.js.map +1 -0
  35. package/out/sdk/filing-dates.d.ts +57 -0
  36. package/out/sdk/filing-dates.d.ts.map +1 -0
  37. package/out/sdk/filing-dates.js +50 -0
  38. package/out/sdk/filing-dates.js.map +1 -0
  39. package/out/sdk/filing-landscape.d.ts +124 -0
  40. package/out/sdk/filing-landscape.d.ts.map +1 -0
  41. package/out/sdk/filing-landscape.js +216 -0
  42. package/out/sdk/filing-landscape.js.map +1 -0
  43. package/out/sdk/index.d.ts +15 -0
  44. package/out/sdk/index.d.ts.map +1 -0
  45. package/out/sdk/index.js +15 -0
  46. package/out/sdk/index.js.map +1 -0
  47. package/out/sdk/list-files.d.ts +39 -0
  48. package/out/sdk/list-files.d.ts.map +1 -0
  49. package/out/sdk/list-files.js +26 -0
  50. package/out/sdk/list-files.js.map +1 -0
  51. package/out/sdk/location.d.ts +40 -0
  52. package/out/sdk/location.d.ts.map +1 -0
  53. package/out/sdk/location.js +23 -0
  54. package/out/sdk/location.js.map +1 -0
  55. package/out/sdk/nearest-infrastructure.d.ts +84 -0
  56. package/out/sdk/nearest-infrastructure.d.ts.map +1 -0
  57. package/out/sdk/nearest-infrastructure.js +84 -0
  58. package/out/sdk/nearest-infrastructure.js.map +1 -0
  59. package/out/sdk/parsing.d.ts +57 -0
  60. package/out/sdk/parsing.d.ts.map +1 -0
  61. package/out/sdk/parsing.js +87 -0
  62. package/out/sdk/parsing.js.map +1 -0
  63. package/out/sdk/plausibility.d.ts +256 -0
  64. package/out/sdk/plausibility.d.ts.map +1 -0
  65. package/out/sdk/plausibility.js +334 -0
  66. package/out/sdk/plausibility.js.map +1 -0
  67. package/out/sdk/technologies.d.ts +229 -0
  68. package/out/sdk/technologies.d.ts.map +1 -0
  69. package/out/sdk/technologies.js +217 -0
  70. package/out/sdk/technologies.js.map +1 -0
  71. package/package.json +97 -0
  72. package/schema.ts +172 -0
  73. package/sdk/build-bdc.ts +810 -0
  74. package/sdk/buildings.ts +87 -0
  75. package/sdk/client.ts +643 -0
  76. package/sdk/common.ts +363 -0
  77. package/sdk/data-collection.ts +268 -0
  78. package/sdk/download.ts +91 -0
  79. package/sdk/filing-dates.ts +88 -0
  80. package/sdk/filing-landscape.ts +287 -0
  81. package/sdk/index.ts +14 -0
  82. package/sdk/list-files.ts +72 -0
  83. package/sdk/location.ts +45 -0
  84. package/sdk/nearest-infrastructure.ts +129 -0
  85. package/sdk/parsing.ts +124 -0
  86. package/sdk/plausibility.ts +512 -0
  87. package/sdk/technologies.ts +258 -0
package/schema.ts ADDED
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Typed schema for bdc.db — the FCC Broadband Data Collection availability read-side layer (2a
7
+ * decisions 4, 8). `bdc_availability` holds, in the default (non-`includeLocationIDs`) build mode, one
8
+ * row per DISTINCT (geoid, provider_id, technology_code, max_advertised_download_speed,
9
+ * max_advertised_upload_speed, low_latency, business_residential_code) tuple parsed from the FCC's
10
+ * per-provider CSV (see `sdk/parsing.ts`) — NOT one row per (block, provider, technology) triple: when
11
+ * two Broadband Serviceable Locations in the same block file different speeds/flags for the same
12
+ * provider/technology, both rows survive `build-bdc.ts`'s materialize-time collapse (see that file's
13
+ * docstring; accepted FCC filing behavior, not a bug). `bdc_provider` is a small dictionary
14
+ * keyed on `provider_id`, populated by a later registry-join task — decision 8 keeps FRN/brand/
15
+ * holding-company resolution out of 2a's scope. The DB also embeds the layer-contract tables from
16
+ * `@mailwoman/core/layers` (manifest tier `shipped`, license `public-domain` — FCC BDC block-level
17
+ * availability data, at the granularity this layer ships, is US government public-domain data, not
18
+ * redistribution-restricted; the CostQuest Fabric boundary this workspace never crosses is the
19
+ * licensing edge, see `bdc/README.md` — spine `h3` res 9 for availability rows, res 6 for coverage
20
+ * cells, matching poi.db's convention).
21
+ *
22
+ * Clustering decision (implementer's pick — the brief allows either): a PLAIN rowid table, NOT
23
+ * `WITHOUT ROWID`, and NOT a composite `(h3_cell, provider_id, technology_code)` primary key.
24
+ * `WITHOUT ROWID` earns its keep on small, PK-probed rows — poi.db's clustered key and
25
+ * `layer_coverage`'s per-cell probe both read by their exact PK and nothing else, so folding the row
26
+ * into the B-tree removes a second lookup. `bdc_availability` doesn't fit that shape: it's a wider,
27
+ * 10-column row read two different ways — an h3-range scan ("everything near this cell", the
28
+ * coverage/overlay path) and a geoid point lookup (the public spatial join key TIGER and callers
29
+ * actually probe by) — never a single composite-key point probe. Clustering the full row under a
30
+ * composite PK would also force the bulk loader to sort-then-insert in that exact key order across
31
+ * millions of rows spread over per-provider, per-state source files, for a locality win a plain
32
+ * index already delivers without constraining ingest order. So: a regular rowid table, an index on
33
+ * `h3_cell` bundled into {@link createBDCAvailabilityTable} (every reader depends on it, same as the
34
+ * table itself), and {@link createBDCGeoidIndex} as a separate, callable-after-bulk-load secondary
35
+ * index for the geoid path — the index-after-load discipline used elsewhere in this repo (see
36
+ * poi-schema.ts's name_key/brand indexes).
37
+ */
38
+
39
+ import type { LayerContractDatabase } from "@mailwoman/core/layers"
40
+ import type { Kysely } from "kysely"
41
+
42
+ /**
43
+ * One availability row from the FCC's per-provider BDC CSV. In the default build mode this is one row per DISTINCT
44
+ * (geoid, provider_id, technology_code, speeds, low_latency, business_residential_code) tuple — NOT one row per (block,
45
+ * provider, technology) triple; a triple whose BSLs carry differing speed tiers keeps multiple rows here (see
46
+ * `build-bdc.ts`'s docstring).
47
+ */
48
+ export interface BDCAvailabilityTable {
49
+ /**
50
+ * Res-9 integer short H3 cell of the block centroid (spine).
51
+ */
52
+ h3_cell: number
53
+ /**
54
+ * 15-char census block GEOID (public spatial key).
55
+ */
56
+ geoid: string
57
+ /**
58
+ * Block-centroid PIP at build time (spine).
59
+ */
60
+ wof_id: number | null
61
+ provider_id: number
62
+ technology_code: number
63
+ max_advertised_download_speed: number
64
+ max_advertised_upload_speed: number
65
+ low_latency: 0 | 1
66
+ business_residential_code: string
67
+ /**
68
+ * Opaque BSL join key — NEVER resolved (spec §2.2); null unless `--include-location-ids`.
69
+ */
70
+ location_id: string | null
71
+ }
72
+
73
+ /**
74
+ * Provider dictionary keyed on `provider_id`. Populated by a later registry-join task (2a decision 8) — that task is 3a
75
+ * Task 8's optional `BuildBDCOptions.providers` (`bdc/sdk/build-bdc.ts`'s `populateBDCProviderTable`); when that option
76
+ * is omitted (the default), this table stays empty, exactly as it did before Task 8. No FK constraint against
77
+ * `bdc_availability.provider_id` — SQLite doesn't enforce FKs without `PRAGMA foreign_keys`, and the join happens at
78
+ * read time, not write time.
79
+ *
80
+ * **Decision 6 — this table is an explicitly LOSSY denormalization, not the source of truth.** `provider_id` is the PK
81
+ * (one row per provider), but the FCC's BDC provider list lets one `provider_id` carry MULTIPLE `frn` values — and
82
+ * conflicting `holding_company` strings — across its rows (Task 3's `parseProviderList` preserves every one of them;
83
+ * see `filer/sdk/provider-list.ts`). A single-row-per-provider table cannot express that cardinality. `filer.db`
84
+ * (`@mailwoman/filer`) is the source of truth: it retains every `provider_id`↔`frn` (and
85
+ * `provider_id`↔`holding_company_name`) edge, never folded or last-wins. When `bdc.db` is built with
86
+ * `BuildBDCOptions.providers` supplied:
87
+ *
88
+ * - `frn` holds only the PRIMARY FRN — the one carrying the most recent Form 499 filing date, per
89
+ * `@mailwoman/filer/sdk`'s `readFRNFilingCandidates` + `pickPrimaryFRN` (imported into `build-bdc.ts`, never
90
+ * reimplemented — Task 7's review found and fixed a temporal bug in that exact query, one a fresh implementation
91
+ * would reintroduce). Every OTHER FRN that `provider_id` carries is discarded here but stays fully recoverable from
92
+ * `filer.db`.
93
+ * - `holding_company` gets the SAME single-distinct-value shortcut `frn` gets (review fix round 1, IMPORTANT-3): when a
94
+ * `provider_id`'s rows carry exactly one distinct non-null `holding_company` string, there is no conflict to resolve,
95
+ * so it's populated directly — no rule needed, same as a single-FRN provider needs no `filerDB` query. When they
96
+ * carry MORE than one distinct value, that ambiguity is the real conflict decision 6 refuses to paper over with
97
+ * last-wins (`holding_company` has no most-recent-filing-date rule the way `frn` does), so it stays NULL and every
98
+ * discarded value remains recoverable from `filer.db`'s `holding_company_name` edges — the identical discipline
99
+ * `frn`'s primary pick already applies.
100
+ * - `brand_name` stays NULL unconditionally: the provider list carries no brand-name column at all, primary or otherwise,
101
+ * so there is nothing to populate it from.
102
+ */
103
+ export interface BDCProviderTable {
104
+ /**
105
+ * PK.
106
+ */
107
+ provider_id: number
108
+ frn: string | null
109
+ brand_name: string | null
110
+ holding_company: string | null
111
+ }
112
+
113
+ export interface BDCDatabase extends LayerContractDatabase {
114
+ bdc_availability: BDCAvailabilityTable
115
+ bdc_provider: BDCProviderTable
116
+ }
117
+
118
+ /**
119
+ * H3 resolution for `bdc_availability.h3_cell` — the block-centroid spine resolution.
120
+ */
121
+ export const BDC_H3_RESOLUTION = 9
122
+
123
+ /**
124
+ * H3 resolution for `layer_coverage.h3_cell` in bdc.db — coarser than the availability spine, matching poi.db's res-6
125
+ * coverage-cell convention.
126
+ */
127
+ export const BDC_COVERAGE_H3_RESOLUTION = 6
128
+
129
+ /**
130
+ * Create `bdc_availability` as a plain rowid table, plus the `h3_cell` range-scan index (bundled here since every
131
+ * reader depends on it — see the file header for the clustering rationale). Call {@link createBDCGeoidIndex}
132
+ * separately, after bulk load, for the geoid point-lookup path.
133
+ */
134
+ export async function createBDCAvailabilityTable(db: Kysely<BDCDatabase>): Promise<void> {
135
+ await db.schema
136
+ .createTable("bdc_availability")
137
+ .addColumn("h3_cell", "integer", (c) => c.notNull())
138
+ .addColumn("geoid", "text", (c) => c.notNull())
139
+ .addColumn("wof_id", "integer")
140
+ .addColumn("provider_id", "integer", (c) => c.notNull())
141
+ .addColumn("technology_code", "integer", (c) => c.notNull())
142
+ .addColumn("max_advertised_download_speed", "integer", (c) => c.notNull())
143
+ .addColumn("max_advertised_upload_speed", "integer", (c) => c.notNull())
144
+ .addColumn("low_latency", "integer", (c) => c.notNull())
145
+ .addColumn("business_residential_code", "text", (c) => c.notNull())
146
+ .addColumn("location_id", "text")
147
+ .execute()
148
+
149
+ await db.schema.createIndex("bdc_availability_h3_cell").on("bdc_availability").column("h3_cell").execute()
150
+ }
151
+
152
+ /**
153
+ * Create `bdc_provider`. A single-column INTEGER PRIMARY KEY is already the SQLite rowid alias, so there's no `WITHOUT
154
+ * ROWID` win here — that modifier only pays off clustering a composite or non-integer PK.
155
+ */
156
+ export async function createBDCProviderTable(db: Kysely<BDCDatabase>): Promise<void> {
157
+ await db.schema
158
+ .createTable("bdc_provider")
159
+ .addColumn("provider_id", "integer", (c) => c.primaryKey())
160
+ .addColumn("frn", "text")
161
+ .addColumn("brand_name", "text")
162
+ .addColumn("holding_company", "text")
163
+ .execute()
164
+ }
165
+
166
+ /**
167
+ * Secondary index for the geoid point-lookup path (the public spatial join key). Call AFTER the bulk materialize
168
+ * (index-after-load), same discipline as poi.db's secondary indexes.
169
+ */
170
+ export async function createBDCGeoidIndex(db: Kysely<BDCDatabase>): Promise<void> {
171
+ await db.schema.createIndex("bdc_availability_geoid").on("bdc_availability").column("geoid").execute()
172
+ }