@mailwoman/bdc 9.0.0 → 9.2.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/out/sdk/build-bdc.d.ts +13 -7
- package/out/sdk/build-bdc.d.ts.map +1 -1
- package/out/sdk/build-bdc.js +254 -190
- package/out/sdk/build-bdc.js.map +1 -1
- package/out/sdk/download.d.ts +0 -11
- package/out/sdk/download.d.ts.map +1 -1
- package/out/sdk/download.js +1 -37
- package/out/sdk/download.js.map +1 -1
- package/out/sdk/filing-landscape.d.ts.map +1 -1
- package/out/sdk/filing-landscape.js +2 -9
- package/out/sdk/filing-landscape.js.map +1 -1
- package/out/sdk/parsing.d.ts +65 -27
- package/out/sdk/parsing.d.ts.map +1 -1
- package/out/sdk/parsing.js +103 -70
- package/out/sdk/parsing.js.map +1 -1
- package/out/sdk/plausibility.d.ts.map +1 -1
- package/out/sdk/plausibility.js +2 -10
- package/out/sdk/plausibility.js.map +1 -1
- package/package.json +106 -13
- package/sdk/build-bdc.ts +329 -253
- package/sdk/download.ts +1 -41
- package/sdk/filing-landscape.ts +4 -12
- package/sdk/parsing.ts +115 -82
- package/sdk/plausibility.ts +8 -13
package/sdk/download.ts
CHANGED
|
@@ -3,56 +3,16 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
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 — `bdc.db` is
|
|
10
|
-
* SQLite, not Parquet-backed (`bdc/schema.ts`) — 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 a `bdc`-only
|
|
16
|
-
* dependency.
|
|
17
6
|
*/
|
|
18
7
|
|
|
19
8
|
import * as fs from "node:fs/promises"
|
|
20
9
|
import * as path from "node:path"
|
|
21
10
|
|
|
22
|
-
import {
|
|
11
|
+
import { extractSingleFileZip } from "@mailwoman/core/fs/zip"
|
|
23
12
|
|
|
24
13
|
import type { BDCClient } from "./client.ts"
|
|
25
14
|
import { BDCFilingDataType, type BDCFile } from "./common.ts"
|
|
26
15
|
|
|
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
16
|
/**
|
|
57
17
|
* Download and cache an FCC BDC availability file, extracting its zip-wrapped CSV to `destinationDir`.
|
|
58
18
|
*
|
package/sdk/filing-landscape.ts
CHANGED
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import type { DatabaseClient } from "@mailwoman/core/kysley/client"
|
|
37
|
-
import { readLayerCoverage, readLayerManifest
|
|
37
|
+
import { readLayerCoverage, readLayerManifest } from "@mailwoman/core/layers"
|
|
38
38
|
import { expandH3Cell, shortCellToInt, type H3Cell, type H3CellShort } from "@mailwoman/spatial"
|
|
39
39
|
import { cellToParent } from "h3-js"
|
|
40
|
-
import { sql
|
|
40
|
+
import { sql } from "kysely"
|
|
41
41
|
|
|
42
42
|
import { BDC_COVERAGE_H3_RESOLUTION, BDC_H3_RESOLUTION, type BDCDatabase } from "../schema.ts"
|
|
43
43
|
|
|
@@ -139,14 +139,6 @@ const speedBucketCaseSQL = sql<string>`CASE
|
|
|
139
139
|
ELSE ${BDC_SPEED_BUCKET_GIGABIT}
|
|
140
140
|
END`
|
|
141
141
|
|
|
142
|
-
/**
|
|
143
|
-
* `BDCDatabase extends LayerContractDatabase` structurally, but Kysely's `transaction()` makes `Kysely<DB>` INVARIANT
|
|
144
|
-
* in `DB` — same narrowing cast as `build-bdc.ts`'s `asContractDB`.
|
|
145
|
-
*/
|
|
146
|
-
function asContractDB(kdb: DatabaseClient<BDCDatabase>): Kysely<LayerContractDatabase> {
|
|
147
|
-
return kdb as unknown as Kysely<LayerContractDatabase>
|
|
148
|
-
}
|
|
149
|
-
|
|
150
142
|
/**
|
|
151
143
|
* Reconstruct the res-6 ancestor of a res-9 short-cell int WITHOUT a centroid — see the module docstring for why the
|
|
152
144
|
* centroid is the wrong input. Exported so tests can assert this agrees, cell-for-cell, with `build-bdc.ts`'s own
|
|
@@ -184,7 +176,7 @@ export async function filingLandscape(
|
|
|
184
176
|
|
|
185
177
|
// Read (and validate) the manifest FIRST — a broken/missing manifest must throw before any block is
|
|
186
178
|
// classified, never fall through to an "unstamped" answer (gate 4).
|
|
187
|
-
const manifest = await readLayerManifest(
|
|
179
|
+
const manifest = await readLayerManifest(db)
|
|
188
180
|
|
|
189
181
|
const requestedUnits: ReadonlyArray<string | number> = query.geoids ?? query.h3Cells!
|
|
190
182
|
const unitColumn = query.geoids ? ("geoid" as const) : ("h3_cell" as const)
|
|
@@ -230,7 +222,7 @@ export async function filingLandscape(
|
|
|
230
222
|
}
|
|
231
223
|
|
|
232
224
|
const res6Parent = res9ShortCellToRes6Parent(candidateCell)
|
|
233
|
-
const coverage = await readLayerCoverage(
|
|
225
|
+
const coverage = await readLayerCoverage(db, res6Parent)
|
|
234
226
|
|
|
235
227
|
if (coverage === undefined) {
|
|
236
228
|
unknownBlockCount++
|
package/sdk/parsing.ts
CHANGED
|
@@ -2,45 +2,84 @@
|
|
|
2
2
|
* @copyright Sister Software.
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
|
-
* @file FCC BDC availability CSV
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
5
|
+
* @file FCC BDC availability CSV row reader.
|
|
6
|
+
*
|
|
7
|
+
* Streams the FCC's 12-column availability CSV through `CSVSpliterator` in `array` mode and projects the
|
|
8
|
+
* seven columns 2a keeps. Columns 0-2 (`frn`, `provider_id`, `brand_name`) and 9, 11 (`state_usps`,
|
|
9
|
+
* `h3_res8_id`) are read past and never emitted: `provider_id` comes from the {@linkcode ProviderID}
|
|
10
|
+
* parameter instead (the FCC partitions availability files per provider, so the caller already knows it),
|
|
11
|
+
* and FRN/brand/state/H3 join concerns are a 2c registry-join seam.
|
|
12
|
+
*
|
|
13
|
+
* Two projection decisions are load-bearing and pre-registered. `location_id` (column 3) stays a STRING —
|
|
14
|
+
* the FCC's values are zero-padded 10-digit strings and `parseInt` would lose the leading zeros (decision
|
|
15
|
+
* 1). `geoid` (column 10) is a string joining `TIGERBlockTable.GEOID` (decision 3).
|
|
16
|
+
*
|
|
17
|
+
* ## Why the source is a resource, not a Buffer
|
|
18
|
+
*
|
|
19
|
+
* This read is STREAMING because the files do not fit the alternative. One state × one technology —
|
|
20
|
+
* `bdc_48_FibertothePremises_fixed_broadband_D25` — is 920 MB and 10,369,043 rows, and a national run
|
|
21
|
+
* spans every state × every technology. The previous byte scanner took a `Buffer`, so its caller opened
|
|
22
|
+
* with `readFile(csvPath)` and held the whole file resident per file.
|
|
23
|
+
*
|
|
24
|
+
* ## Why quoting is not optional here
|
|
25
|
+
*
|
|
26
|
+
* Measured on that same file: 421,555 rows carry one embedded comma inside a quoted `brand_name` and 327
|
|
27
|
+
* carry two ("FiberFirst, LLC", "Valor Telecommunications of Texas, LP"). A delimiter scan blind to quotes
|
|
28
|
+
* shifts every column right of `brand_name` on 4% of rows — measured exactly, a quote-blind
|
|
29
|
+
* `String.split(",")` mismatches 81,095 of 2,000,000 real rows. `enableQuoteHandling` also keeps an
|
|
30
|
+
* embedded NEWLINE inside its row — no row in that file needs it (the 12/13/14-field line counts sum
|
|
31
|
+
* exactly to `wc -l`, so no record is split across lines), but the guarantee is what makes the reader safe
|
|
32
|
+
* on a file nobody has measured yet.
|
|
33
|
+
*
|
|
34
|
+
* ## What this costs
|
|
35
|
+
*
|
|
36
|
+
* Measured on the full 920 MB / 10,369,042-row file, projecting these 7 of 12 columns, identical checksums
|
|
37
|
+
* across every arm. One arm per process — arms sharing a process mis-rank, because the JIT warms across
|
|
38
|
+
* them:
|
|
39
|
+
*
|
|
40
|
+
* ```
|
|
41
|
+
* hand-rolled byte scan, whole buffer 778 ns/row peak RSS 978 MB
|
|
42
|
+
* spliterator 6.2.0, streaming 2,435 ns/row peak RSS 112 MB
|
|
43
|
+
* spliterator patched, streaming 860 ns/row peak RSS 100 MB
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* The 6.2.0 row is why this looked like a regression when it landed: CSVSpliterator decoded once per
|
|
47
|
+
* COLUMN, and `TextDecoder`'s per-call overhead dominates at column sizes. Fixed upstream in
|
|
48
|
+
* sister-software/spliterator#6 by decoding the row once — so the streaming path now runs within 10% of a
|
|
49
|
+
* whole-buffer byte scan while holding 9.8x less memory. **Requires a spliterator release carrying that
|
|
50
|
+
* fix**; on 6.2.0 this reader is correct and roughly 3x slower.
|
|
51
|
+
*
|
|
52
|
+
* Do not route around it by hand-rolling a splitter here again. The staging insert this feeds measures 615
|
|
53
|
+
* ns/row, so the parse does sit on the critical path — but a local fast-and-quote-blind parser is exactly
|
|
54
|
+
* the trade that produced the scanner this replaced, and the quote-blind version is 4% wrong.
|
|
22
55
|
*/
|
|
23
56
|
|
|
57
|
+
import type { AsyncDataResource } from "spliterator"
|
|
58
|
+
import { CSVSpliterator } from "spliterator"
|
|
59
|
+
|
|
24
60
|
import type { ProviderID } from "./common.ts"
|
|
25
61
|
|
|
26
62
|
/**
|
|
27
|
-
*
|
|
63
|
+
* Column positions in the FCC's 12-column availability CSV. Named rather than sliced by offset so a reader can check
|
|
64
|
+
* them against the header row without counting commas:
|
|
28
65
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* repo convention forbids TS `enum` anyway (`erasableSyntaxOnly`).
|
|
66
|
+
* `frn,provider_id,brand_name,location_id,technology,max_advertised_download_speed,`
|
|
67
|
+
* `max_advertised_upload_speed,low_latency,business_residential_code,state_usps,block_geoid,h3_res8_id`
|
|
32
68
|
*/
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
69
|
+
const Column = {
|
|
70
|
+
LocationID: 3,
|
|
71
|
+
Technology: 4,
|
|
72
|
+
MaxAdvertisedDownloadSpeed: 5,
|
|
73
|
+
MaxAdvertisedUploadSpeed: 6,
|
|
74
|
+
LowLatency: 7,
|
|
75
|
+
BusinessResidentialCode: 8,
|
|
76
|
+
BlockGeoID: 10,
|
|
38
77
|
} as const
|
|
39
78
|
|
|
40
79
|
/**
|
|
41
80
|
* A single parsed row of FCC BDC availability data.
|
|
42
81
|
*
|
|
43
|
-
* @see {@linkcode
|
|
82
|
+
* @see {@linkcode readAvailabilityRows}
|
|
44
83
|
*/
|
|
45
84
|
export interface BDCAvailabilityRow {
|
|
46
85
|
provider_id: number
|
|
@@ -55,70 +94,64 @@ export interface BDCAvailabilityRow {
|
|
|
55
94
|
low_latency: 0 | 1
|
|
56
95
|
business_residential_code: string
|
|
57
96
|
/**
|
|
58
|
-
*
|
|
59
|
-
* column on that side).
|
|
97
|
+
* Joins `TIGERBlockTable.GEOID` (note: uppercase column on that side) — 2a decision 3.
|
|
60
98
|
*/
|
|
61
99
|
geoid: string
|
|
62
100
|
}
|
|
63
101
|
|
|
64
102
|
/**
|
|
65
|
-
*
|
|
66
|
-
* {@linkcode BDCAvailabilityRow}.
|
|
103
|
+
* Project one already-split CSV row onto {@linkcode BDCAvailabilityRow}.
|
|
67
104
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* so a comma there isn't a column delimiter) — matching the Nexus original's approach for this specific, known-shaped
|
|
71
|
-
* 12-column file rather than reaching for a general CSV library.
|
|
105
|
+
* Separate from the iteration so the sync and async readers cannot drift in what they emit — the one-function
|
|
106
|
+
* discipline. Not exported: a caller with a split row wants {@linkcode readAvailabilityRowsSync}.
|
|
72
107
|
*/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
doubleQuoteCount++
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (byte === CSVByte.Comma && doubleQuoteCount % 2 === 0) {
|
|
89
|
-
contentDelimiters[delimiterIndex] = byteIndex
|
|
90
|
-
|
|
91
|
-
delimiterIndex++
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (byte === CSVByte.Newline) {
|
|
95
|
-
contentDelimiters[delimiterIndex] = CSVByte.Newline
|
|
96
|
-
const slices: Buffer[] = []
|
|
97
|
-
|
|
98
|
-
// Skip columns 0-2 (frn, provider_id, brand_name) — see the file header for why.
|
|
99
|
-
for (let i = 3; i < contentDelimiters.length; i++) {
|
|
100
|
-
const start = contentDelimiters[i]! + 1
|
|
101
|
-
const end = contentDelimiters[i + 1]
|
|
102
|
-
|
|
103
|
-
slices.push(csvBuffer.subarray(start, end))
|
|
104
|
-
}
|
|
108
|
+
function projectRow(columns: readonly string[], providerID: ProviderID): BDCAvailabilityRow {
|
|
109
|
+
return {
|
|
110
|
+
provider_id: providerID,
|
|
111
|
+
location_id: columns[Column.LocationID] ?? "",
|
|
112
|
+
technology_code: Number.parseInt(columns[Column.Technology] ?? "", 10),
|
|
113
|
+
max_advertised_download_speed: Number.parseInt(columns[Column.MaxAdvertisedDownloadSpeed] ?? "", 10),
|
|
114
|
+
max_advertised_upload_speed: Number.parseInt(columns[Column.MaxAdvertisedUploadSpeed] ?? "", 10),
|
|
115
|
+
low_latency: columns[Column.LowLatency] === "1" ? 1 : 0,
|
|
116
|
+
business_residential_code: columns[Column.BusinessResidentialCode] ?? "",
|
|
117
|
+
geoid: columns[Column.BlockGeoID] ?? "",
|
|
118
|
+
}
|
|
119
|
+
}
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
low_latency: slices[4]![0] === CSVByte.One ? 1 : 0,
|
|
113
|
-
business_residential_code: slices[5]!.toString("ascii"),
|
|
114
|
-
geoid: slices[7]!.toString("ascii"),
|
|
115
|
-
}
|
|
121
|
+
/**
|
|
122
|
+
* Shared reader options. `header: true` consumes the first row as the header even in `array` mode;
|
|
123
|
+
* `enableQuoteHandling` is what makes the 421,882 quoted-brand rows keep their column alignment. `crlf` already
|
|
124
|
+
* defaults to `true` for CSV (RFC 4180), so a CRLF file does not leak `\r` into the last column.
|
|
125
|
+
*/
|
|
126
|
+
const READER_OPTIONS = { mode: "array", enableQuoteHandling: true } as const
|
|
116
127
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Stream an FCC BDC availability CSV, yielding every data row. The header row is consumed, never emitted.
|
|
130
|
+
*
|
|
131
|
+
* `source` is anything `spliterator` can open asynchronously — a path string, a `path-ts` builder, a URL, a file
|
|
132
|
+
* handle, or an async chunk iterator. Prefer handing it the path and letting it own the read: that is what keeps a 920
|
|
133
|
+
* MB file off the heap.
|
|
134
|
+
*/
|
|
135
|
+
export async function* readAvailabilityRows(
|
|
136
|
+
source: AsyncDataResource,
|
|
137
|
+
providerID: ProviderID
|
|
138
|
+
): AsyncIterable<BDCAvailabilityRow> {
|
|
139
|
+
for await (const columns of CSVSpliterator.fromAsync<string[]>(source, READER_OPTIONS)) {
|
|
140
|
+
yield projectRow(columns, providerID)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
121
143
|
|
|
122
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Synchronous sibling for an in-memory buffer — fixtures and tests, never the build path.
|
|
146
|
+
*
|
|
147
|
+
* Kept because a test that must assert on a literal CSV should not have to stand up a stream to do it. If you are
|
|
148
|
+
* reaching for this against a file on disk, reach for {@linkcode readAvailabilityRows} instead.
|
|
149
|
+
*/
|
|
150
|
+
export function* readAvailabilityRowsSync(
|
|
151
|
+
csvBuffer: Buffer | string,
|
|
152
|
+
providerID: ProviderID
|
|
153
|
+
): Iterable<BDCAvailabilityRow> {
|
|
154
|
+
for (const columns of CSVSpliterator.from<string[]>(csvBuffer, READER_OPTIONS)) {
|
|
155
|
+
yield projectRow(columns, providerID)
|
|
123
156
|
}
|
|
124
157
|
}
|
package/sdk/plausibility.ts
CHANGED
|
@@ -113,11 +113,15 @@
|
|
|
113
113
|
*/
|
|
114
114
|
|
|
115
115
|
import type { DatabaseClient } from "@mailwoman/core/kysley/client"
|
|
116
|
-
import {
|
|
116
|
+
import {
|
|
117
|
+
readLayerCoverage,
|
|
118
|
+
readLayerManifest,
|
|
119
|
+
type LayerContractDatabase,
|
|
120
|
+
type LayerContractHandle,
|
|
121
|
+
} from "@mailwoman/core/layers"
|
|
117
122
|
import type { POILookup } from "@mailwoman/resolver-wof-sqlite/poi-lookup"
|
|
118
123
|
import { shortCellToInt, type H3Cell, type PointLiteral } from "@mailwoman/spatial"
|
|
119
124
|
import { latLngToCell } from "h3-js"
|
|
120
|
-
import type { Kysely } from "kysely"
|
|
121
125
|
|
|
122
126
|
import { BDC_H3_RESOLUTION, type BDCDatabase } from "../schema.ts"
|
|
123
127
|
import {
|
|
@@ -295,15 +299,6 @@ const SPEED_BUCKET_RANK: Readonly<Record<string, number>> = {
|
|
|
295
299
|
[BDC_SPEED_BUCKET_GIGABIT]: 3,
|
|
296
300
|
}
|
|
297
301
|
|
|
298
|
-
/**
|
|
299
|
-
* `BDCDatabase extends LayerContractDatabase` structurally, but Kysely's `transaction()` makes `Kysely<DB>` INVARIANT
|
|
300
|
-
* in `DB` — same cast idiom as `filing-landscape.ts`'s own private `asContractDB` (decision 8: reuse, never re-derive;
|
|
301
|
-
* copied rather than imported since the original is module-private).
|
|
302
|
-
*/
|
|
303
|
-
function asContractDB(kdb: DatabaseClient<BDCDatabase>): Kysely<LayerContractDatabase> {
|
|
304
|
-
return kdb as unknown as Kysely<LayerContractDatabase>
|
|
305
|
-
}
|
|
306
|
-
|
|
307
302
|
/**
|
|
308
303
|
* `true` when `filing` corroborates the claim: same `technology_code`, AND `filing.speed_bucket` ranks at or above the
|
|
309
304
|
* claimed download speed's own bucket. A different tech, or a same-tech but LESSER filing, is `false` — never disproof,
|
|
@@ -373,7 +368,7 @@ function combineCoverage(
|
|
|
373
368
|
*/
|
|
374
369
|
async function assertLayerSpineResolution(
|
|
375
370
|
layer: "bdc" | "poi",
|
|
376
|
-
contractDB:
|
|
371
|
+
contractDB: LayerContractHandle,
|
|
377
372
|
expectedResolution: number
|
|
378
373
|
): Promise<void> {
|
|
379
374
|
const manifest = await readLayerManifest(contractDB)
|
|
@@ -430,7 +425,7 @@ export async function plausibilityCheck(claim: PlausibilityClaim, deps: Plausibi
|
|
|
430
425
|
// independently per WIRED layer, not only when both are present: a poi-only call still joins poi's coverage
|
|
431
426
|
// table against a BDC_H3_RESOLUTION-derived cell (below) and must not do so unchecked.
|
|
432
427
|
if (deps.bdcDB) {
|
|
433
|
-
await assertLayerSpineResolution("bdc",
|
|
428
|
+
await assertLayerSpineResolution("bdc", deps.bdcDB, BDC_H3_RESOLUTION)
|
|
434
429
|
}
|
|
435
430
|
|
|
436
431
|
if (deps.poi) {
|