@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
@@ -0,0 +1,256 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `plausibilityCheck` (2b task 5, spec §3.2/§4) — the heart of the BDC plausibility vertical. Composes
7
+ * `filingLandscape` (2a) + `nearestInfrastructure` (2b task 4) into ONE evidence bundle over a single
8
+ * broadband-service claim, under the registry-backed doctrine's positive-evidence-only invariant (spec
9
+ * §4): a BDC filing or a nearby infrastructure hit can RAISE confidence; their absence can only ever
10
+ * read as "unknown" or "no supporting evidence found, coverage permitting" — NEVER as "implausible."
11
+ * The four §7-2b acceptance gates land in Task 6 as their own test file; this module is designed for
12
+ * them but doesn't assert them itself.
13
+ *
14
+ * **Claim resolution (decision 4).** A claim's spatial key resolves two INDEPENDENT ways:
15
+ *
16
+ * - **Filing evidence's spatial key**: `claim.geoid` wins outright when present — `filingLandscape({
17
+ * geoids: [claim.geoid] })` is the NATIVE, exact path (no h3 approximation needed), so
18
+ * `block_resolution` is `"geoid"`. Otherwise a coordinate is required (`claim.point` directly, or
19
+ * `claim.address` geocoded via `deps.geocode`) and filing evidence goes through
20
+ * `filingLandscape({ h3Cells: [cell] })` — this is the UNSOUND h3-cell approximation decision 4
21
+ * pins (a claim's own res-9 cell can differ from its true block centroid's cell), so
22
+ * `block_resolution` is `"h3_cell_approximation"`. Only one of the two is ever emitted — the union
23
+ * type is deliberately NOT `("geoid" | "h3_cell_approximation")[]`.
24
+ * - **Physical evidence's search center**: independent of the above — `claim.point` (or the geocoded
25
+ * `claim.address`) directly, whenever available. A GEOID-ONLY claim (no point, no address) has NO
26
+ * coordinate to search from: bdc.db stores no public geoid→centroid resolver (deriving one would
27
+ * need the same Fabric-adjacent block-centroid machinery the vertical explicitly keeps out of reach
28
+ * — §2.2's boundary), so physical evidence is skipped entirely for that shape of claim. This is a
29
+ * genuine, documented capability gap, not a missing-layer abstain: `coverage_confidence` still
30
+ * degrades honestly (see below), but no `PlausibilityEvidence` abstain variant fits "no coordinate
31
+ * available," so none is fabricated.
32
+ *
33
+ * **Tech → physical-category mapping (decision 8's "reuse, never re-derive" extended to this table).**
34
+ * {@link PLAUSIBILITY_TECH_PHYSICAL_CATEGORIES}: fiber (`OpticalCarrierFiber`, code 50) implies
35
+ * `telecom_exchange`/`telecom_cabinet`/`data_center`; the three fixed-wireless codes (70/71/72) imply
36
+ * `tower_comms`; every other code (DSL, cable, satellite, power-line, other) maps to `[]` — no physical
37
+ * falsifier is claimed for those technologies, because OSM/Overture carry no plant category that
38
+ * physically falsifies them. A `[]` mapping means the physical-evidence step is skipped as
39
+ * NOT-APPLICABLE (distinct from "layer missing" — see `coverage_confidence` below).
40
+ *
41
+ * **Filing evidence.** Every `ProviderFilingSummary` row `filingLandscape` returns for the resolved
42
+ * block becomes its own `{ type: "filing" }` evidence entry (ANY provider filing there is positive
43
+ * evidence a market exists, informative regardless of tech match — spec §3.2 step 2's "a filing that
44
+ * contradicts it… weak signal, not disproof"). `corroborates` is true only when the filing's
45
+ * `technology_code` matches the claim AND its `speed_bucket` ranks at or above the claimed download
46
+ * speed's own bucket (via the exported {@link speedBucketForDownloadSpeed} + the four bucket consts —
47
+ * decision 8: reused, never re-derived). A same-tech LESSER filing, or a different-tech filing, is
48
+ * still emitted with `corroborates: false` — never treated as disproof of anything.
49
+ *
50
+ * - bdc.db absent entirely → one `{ type: "abstain", reason: "requires_bdc_layer" }` entry (decision
51
+ * 6); `vintage` stays `null` — the ONLY case it does (per the produced type's own doc comment).
52
+ * - bdc.db present but the resolved block/cell itself carries no survey evidence (`unknown_block_count`
53
+ * > 0 for the one queried unit) → `{ type: "abstain", reason: "insufficient_survey_data", layer:
54
+ * "bdc" }`. `vintage` IS still populated here — the LAYER didn't abstain, only this one cell lacks
55
+ * coverage.
56
+ * - bdc.db present, block surveyed, zero filings → the spec's POSITIVE meaning-of-zero case ("a
57
+ * genuine 'surveyed, zero providers here' result" — `filing-landscape.ts`'s own docstring). No
58
+ * `filing` evidence entries are pushed (there's nothing to report), but the filing layer still
59
+ * counts as COVERED for `coverage_confidence` — the absence is informative, not unknown.
60
+ *
61
+ * **Physical evidence.** Symmetric to the above, over `nearestInfrastructure`'s hits — every hit
62
+ * becomes its own `{ type: "physical_plant" }` entry, nearest-first, whatever `nearestInfrastructure`'s
63
+ * own ring/limit budget returns.
64
+ *
65
+ * - The tech maps to `[]` categories (no physical falsifier claimed) → the step is skipped outright;
66
+ * no evidence entry, no abstain, and this axis is excluded from `coverage_confidence` (see below).
67
+ * - The tech implies categories but `deps.poi` is absent → `{ type: "abstain", reason:
68
+ * "requires_build_local_layer", layer: "poi" }` (decision 6 — the poi-executor abstain precedent).
69
+ * - The tech implies categories, `deps.poi` is present, but no coordinate is resolvable (a geoid-only
70
+ * claim) → no evidence entry, no abstain (see the claim-resolution note above); the axis degrades to
71
+ * UNKNOWN for `coverage_confidence` purposes.
72
+ * - Otherwise → `nearestInfrastructure` runs; each hit is emitted, and the searched point's OWN res-6
73
+ * coverage cell (independent of whether any hit was found — a covered-but-empty cell is real
74
+ * evidence the area was surveyed) is read directly via `readLayerCoverage` to determine the layer's
75
+ * coverage state for this claim.
76
+ *
77
+ * **`coverage_confidence` — survey completeness, NOT evidence-found.** This is deliberately orthogonal
78
+ * to whether any evidence was actually found (spec §4 rule 4: "coverage_confidence is mandatory on
79
+ * every answer… the product's honesty is this refusal to guess" — a refusal that has to hold even when
80
+ * the answer turns out to be "nothing found"). Each layer contributes one of `"covered"` / `"unknown"`
81
+ * / `"not_applicable"` (the last only for the physical axis, when the tech maps to no category), and
82
+ * the pair combines per the brief's literal formula: both covered → `"high"`; either UNKNOWN → degrade
83
+ * to `"low"`; both absent/unknown → `"insufficient_survey_data"`.
84
+ *
85
+ * The `"not_applicable"` extension (not literally spelled out in the brief, since the brief's formula
86
+ * assumes a real pair) is this module's own documented decision, deliberately CONSERVATIVE: a tech with
87
+ * no physical falsifier (DSL, cable, satellite, power-line) can never reach `"high"` confidence from
88
+ * filing coverage alone — it degrades to filing-covered → `"low"`, filing-unknown/absent →
89
+ * `"insufficient_survey_data"`. Rationale: spec §4 frames physical co-presence as the vertical's
90
+ * distinguishing "falsifier that only fires positively" and reserves `"high"` for genuine two-channel
91
+ * corroboration (Gate 2 in Task 6: "matching filing + nearby plant in covered cells… high"); a tech
92
+ * that structurally can never offer a second channel should not be able to claim the same top
93
+ * confidence a fiber claim earns by actually having one available. This reading is more conservative
94
+ * than the alternative (treating the missing axis as inert and reporting `"high"` off filing alone) —
95
+ * flagged here for review since Task 6's four gates don't exercise a no-physical-falsifier tech code.
96
+ *
97
+ * **Ledger note (task 4 review; extended task 5 fix round 1, finding 2) — the per-layer coverage-resolution sanity
98
+ * check.** Neither bdc.db's nor poi.db's `layer_manifest` records the COVERAGE-cell h3 resolution (6) that
99
+ * `res9ShortCellToRes6Parent` hardcodes on both sides — only each layer's ROW-spine resolution (9,
100
+ * `spineKeys.h3.resolution`) is ever recorded. A real fix needs a layer-contract schema addition (out
101
+ * of scope for this task; the same follow-up task 4's report already ticketed). What IS practical and
102
+ * cheap: each manifest is a single-row table already read at most once per call here, so whenever a layer is
103
+ * WIRED — `bdcDB`, `poi`, or both, checked independently — {@link assertLayerSpineResolution} compares that one
104
+ * layer's recorded `spineKeys.h3.resolution` directly against the `BDC_H3_RESOLUTION` constant `pointCell` is
105
+ * actually derived from, and throws on a mismatch, catching a layer built at a different spine resolution before it
106
+ * silently mis-joins a coverage cell. This is TWO-SIDED, not gated on both layers being present together: a
107
+ * poi-only call still checks poi's own recorded resolution, since `readLayerCoverage`'s poi-side join key (below)
108
+ * is derived from `BDC_H3_RESOLUTION` regardless of whether `bdcDB` is wired at all (the original one-sided version
109
+ * compared the two manifests to each other, so it silently skipped poi entirely whenever `bdcDB` was absent). It
110
+ * can NOT catch a layer whose row spine is 9 but whose COVERAGE cells were derived at some OTHER resolution than 6
111
+ * — that gap needs the schema addition, not a runtime assertion.
112
+ */
113
+ import type { DatabaseClient } from "@mailwoman/core/kysley/client";
114
+ import { type LayerContractDatabase } from "@mailwoman/core/layers";
115
+ import type { POILookup } from "@mailwoman/resolver-wof-sqlite/poi-lookup";
116
+ import { type PointLiteral } from "@mailwoman/spatial";
117
+ import { type BDCDatabase } from "../schema.ts";
118
+ import { type ProviderFilingSummary } from "./filing-landscape.ts";
119
+ import { type InfrastructureHit } from "./nearest-infrastructure.ts";
120
+ /**
121
+ * One claimed broadband-service assertion to check. Exactly one spatial field is expected in practice (`geoid` wins if
122
+ * present — see the module docstring's claim-resolution note); `plausibilityCheck` throws if NONE of
123
+ * `geoid`/`point`/`address` resolves to something usable.
124
+ */
125
+ export interface PlausibilityClaim {
126
+ address?: string;
127
+ point?: PointLiteral;
128
+ geoid?: string;
129
+ technologyCode: number;
130
+ claimedDownloadMbps: number;
131
+ }
132
+ /**
133
+ * Reasons `plausibilityCheck` can abstain on one evidence channel — decision 6, the poi-executor abstain precedent
134
+ * (`mailwoman/poi-executor.ts`), extended with the bdc-layer-absent case and the survey-gap-for-this-cell case.
135
+ */
136
+ export type PlausibilityAbstainReason = "requires_build_local_layer" | "requires_bdc_layer" | "insufficient_survey_data";
137
+ export type PlausibilityEvidence = {
138
+ type: "filing";
139
+ filing: ProviderFilingSummary;
140
+ vintage: string;
141
+ corroborates: boolean;
142
+ } | {
143
+ type: "physical_plant";
144
+ hit: InfrastructureHit;
145
+ } | {
146
+ type: "abstain";
147
+ reason: PlausibilityAbstainReason;
148
+ layer?: string;
149
+ };
150
+ /**
151
+ * One evidence channel's survey-completeness state for THIS claim, WITH the reason a non-`"covered"` state applies —
152
+ * task 5 fix round 1 (review finding 1): `coverage_confidence` alone folds several genuinely different situations into
153
+ * the same `"low"`/`"insufficient_survey_data"` verdict (a tech with no physical falsifier at all vs. a real poi survey
154
+ * gap vs. a geoid-only claim with no coordinate to search from), and Task 6's gates need to attribute WHICH one
155
+ * applies. `"not_applicable"` and `"no_coordinate"` are only ever produced for the physical axis; the filing axis only
156
+ * ever reaches `"covered"`, `"layer_missing"`, or `"cell_unsurveyed"`.
157
+ */
158
+ export type PlausibilityCoverageAxisState = "covered"
159
+ /**
160
+ * The required dependency (`deps.bdcDB` for filing, `deps.poi` for physical) was never wired at all — the
161
+ * `requires_bdc_layer` / `requires_build_local_layer` abstain precedent.
162
+ */
163
+ | "layer_missing"
164
+ /**
165
+ * The dependency IS wired, but the specific queried block/cell carries no survey coverage of its own — the
166
+ * `insufficient_survey_data` abstain precedent (filing), or an absent `readLayerCoverage` read (physical).
167
+ */
168
+ | "cell_unsurveyed"
169
+ /**
170
+ * Physical axis only: the claim resolved no coordinate (a geoid-only claim — see the module docstring's
171
+ * claim-resolution note), so no physical-evidence search point exists. A genuine capability gap, not a missing layer
172
+ * — distinct from `"layer_missing"` even though both degrade `coverage_confidence` the same way.
173
+ */
174
+ | "no_coordinate"
175
+ /**
176
+ * Physical axis only: the claimed technology maps to no physical-plant category at all (see
177
+ * {@link PLAUSIBILITY_TECH_PHYSICAL_CATEGORIES}) — there is no applicable second channel for this tech, ever,
178
+ * regardless of layer availability. Distinct from every other state: this claim can never earn `"high"`.
179
+ */
180
+ | "not_applicable";
181
+ /**
182
+ * Per-axis attribution for {@link PlausibilityBundle.coverage_confidence} — see {@link PlausibilityCoverageAxisState}.
183
+ * Added alongside `coverage_confidence` (kept as-is for compatibility) rather than replacing it.
184
+ */
185
+ export interface PlausibilityCoverageDetail {
186
+ filing: PlausibilityCoverageAxisState;
187
+ physical: PlausibilityCoverageAxisState;
188
+ }
189
+ export interface PlausibilityBundle {
190
+ claim: PlausibilityClaim;
191
+ evidence_found: PlausibilityEvidence[];
192
+ coverage_confidence: "high" | "low" | "insufficient_survey_data";
193
+ /**
194
+ * Per-axis WHY behind `coverage_confidence` — task 5 fix round 1 (finding 1). ALWAYS present, mirroring
195
+ * `block_resolution`'s always-present discipline.
196
+ */
197
+ coverage_detail: PlausibilityCoverageDetail;
198
+ /**
199
+ * `"geoid"` when `claim.geoid` drove the filing-evidence lookup (the exact, native path); otherwise
200
+ * `"h3_cell_approximation"` (decision 4 — the point/address path's unsound-but-flagged h3 cell). ALWAYS present on
201
+ * every returned bundle.
202
+ */
203
+ block_resolution: "geoid" | "h3_cell_approximation";
204
+ /**
205
+ * `null` ONLY when the bdc layer itself abstained (`deps.bdcDB` absent). Populated in every other case, including
206
+ * when the specific queried block/cell is itself unsurveyed.
207
+ */
208
+ vintage: string | null;
209
+ }
210
+ /**
211
+ * Structural mirror of `mailwoman/geocode-core.ts`'s `GeocodeResult` — `@mailwoman/bdc` MUST NOT import from the
212
+ * `mailwoman` workspace (`mailwoman/package.json` already depends on `@mailwoman/bdc`; the reverse edge would be
213
+ * circular). Only the two fields this scorer actually consumes are typed here; a real `GeocodeResult` is structurally
214
+ * assignable to this type without any adapter, so a caller wiring `deps.geocode` at the CLI/MCP layer can pass a thin
215
+ * wrapper over `geocodeAddress` directly.
216
+ */
217
+ export interface GeocodeLike {
218
+ lat: number | null;
219
+ lon: number | null;
220
+ }
221
+ /**
222
+ * The already-open infra layer this scorer composes against Task 4's `nearestInfrastructure`. The caller owns BOTH
223
+ * handles' open/dispose lifecycle (mirrors `nearestInfrastructure`'s own `using poiLookup = new POILookup(...)`
224
+ * precedent). `contractDB` is used two ways: passed straight through to `nearestInfrastructure` (per-hit coverage), and
225
+ * read directly here (the whole-cell coverage check this module needs for `coverage_confidence`, independent of whether
226
+ * any hit was actually found).
227
+ */
228
+ export interface PlausibilityPOIDeps {
229
+ lookup: POILookup;
230
+ contractDB: DatabaseClient<LayerContractDatabase>;
231
+ }
232
+ export interface PlausibilityDeps {
233
+ bdcDB?: DatabaseClient<BDCDatabase>;
234
+ poi?: PlausibilityPOIDeps;
235
+ geocode?: (address: string) => Promise<GeocodeLike>;
236
+ }
237
+ /**
238
+ * Tech → physical-plant category mapping (exported per the task brief). Fiber implies the three
239
+ * infrastructure-extension categories a fiber network plausibly touches; the three fixed-wireless codes imply a comms
240
+ * tower; every other code maps to `[]` — no physical falsifier is claimed for it (see
241
+ * {@link physicalCategoriesForTechnology}).
242
+ */
243
+ export declare const PLAUSIBILITY_TECH_PHYSICAL_CATEGORIES: Readonly<Record<number, readonly string[]>>;
244
+ /**
245
+ * The poi-taxonomy category ids a physical-plant search should probe for a given BDC technology code, or `[]` when that
246
+ * technology has no physical falsifier in this vertical (see {@link PLAUSIBILITY_TECH_PHYSICAL_CATEGORIES}).
247
+ */
248
+ export declare function physicalCategoriesForTechnology(technologyCode: number): readonly string[];
249
+ /**
250
+ * Compose filing evidence + physical evidence into one `{ claim, evidence_found, coverage_confidence }` bundle — see
251
+ * the module docstring for the full composition rules. Never emits anything expressible as "implausible": absence of a
252
+ * filing or of nearby plant only ever surfaces as an abstain, an omitted evidence entry, or a degraded
253
+ * `coverage_confidence` — never a negative verdict.
254
+ */
255
+ export declare function plausibilityCheck(claim: PlausibilityClaim, deps: PlausibilityDeps): Promise<PlausibilityBundle>;
256
+ //# sourceMappingURL=plausibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plausibility.d.ts","sourceRoot":"","sources":["../../sdk/plausibility.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,334 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `plausibilityCheck` (2b task 5, spec §3.2/§4) — the heart of the BDC plausibility vertical. Composes
7
+ * `filingLandscape` (2a) + `nearestInfrastructure` (2b task 4) into ONE evidence bundle over a single
8
+ * broadband-service claim, under the registry-backed doctrine's positive-evidence-only invariant (spec
9
+ * §4): a BDC filing or a nearby infrastructure hit can RAISE confidence; their absence can only ever
10
+ * read as "unknown" or "no supporting evidence found, coverage permitting" — NEVER as "implausible."
11
+ * The four §7-2b acceptance gates land in Task 6 as their own test file; this module is designed for
12
+ * them but doesn't assert them itself.
13
+ *
14
+ * **Claim resolution (decision 4).** A claim's spatial key resolves two INDEPENDENT ways:
15
+ *
16
+ * - **Filing evidence's spatial key**: `claim.geoid` wins outright when present — `filingLandscape({
17
+ * geoids: [claim.geoid] })` is the NATIVE, exact path (no h3 approximation needed), so
18
+ * `block_resolution` is `"geoid"`. Otherwise a coordinate is required (`claim.point` directly, or
19
+ * `claim.address` geocoded via `deps.geocode`) and filing evidence goes through
20
+ * `filingLandscape({ h3Cells: [cell] })` — this is the UNSOUND h3-cell approximation decision 4
21
+ * pins (a claim's own res-9 cell can differ from its true block centroid's cell), so
22
+ * `block_resolution` is `"h3_cell_approximation"`. Only one of the two is ever emitted — the union
23
+ * type is deliberately NOT `("geoid" | "h3_cell_approximation")[]`.
24
+ * - **Physical evidence's search center**: independent of the above — `claim.point` (or the geocoded
25
+ * `claim.address`) directly, whenever available. A GEOID-ONLY claim (no point, no address) has NO
26
+ * coordinate to search from: bdc.db stores no public geoid→centroid resolver (deriving one would
27
+ * need the same Fabric-adjacent block-centroid machinery the vertical explicitly keeps out of reach
28
+ * — §2.2's boundary), so physical evidence is skipped entirely for that shape of claim. This is a
29
+ * genuine, documented capability gap, not a missing-layer abstain: `coverage_confidence` still
30
+ * degrades honestly (see below), but no `PlausibilityEvidence` abstain variant fits "no coordinate
31
+ * available," so none is fabricated.
32
+ *
33
+ * **Tech → physical-category mapping (decision 8's "reuse, never re-derive" extended to this table).**
34
+ * {@link PLAUSIBILITY_TECH_PHYSICAL_CATEGORIES}: fiber (`OpticalCarrierFiber`, code 50) implies
35
+ * `telecom_exchange`/`telecom_cabinet`/`data_center`; the three fixed-wireless codes (70/71/72) imply
36
+ * `tower_comms`; every other code (DSL, cable, satellite, power-line, other) maps to `[]` — no physical
37
+ * falsifier is claimed for those technologies, because OSM/Overture carry no plant category that
38
+ * physically falsifies them. A `[]` mapping means the physical-evidence step is skipped as
39
+ * NOT-APPLICABLE (distinct from "layer missing" — see `coverage_confidence` below).
40
+ *
41
+ * **Filing evidence.** Every `ProviderFilingSummary` row `filingLandscape` returns for the resolved
42
+ * block becomes its own `{ type: "filing" }` evidence entry (ANY provider filing there is positive
43
+ * evidence a market exists, informative regardless of tech match — spec §3.2 step 2's "a filing that
44
+ * contradicts it… weak signal, not disproof"). `corroborates` is true only when the filing's
45
+ * `technology_code` matches the claim AND its `speed_bucket` ranks at or above the claimed download
46
+ * speed's own bucket (via the exported {@link speedBucketForDownloadSpeed} + the four bucket consts —
47
+ * decision 8: reused, never re-derived). A same-tech LESSER filing, or a different-tech filing, is
48
+ * still emitted with `corroborates: false` — never treated as disproof of anything.
49
+ *
50
+ * - bdc.db absent entirely → one `{ type: "abstain", reason: "requires_bdc_layer" }` entry (decision
51
+ * 6); `vintage` stays `null` — the ONLY case it does (per the produced type's own doc comment).
52
+ * - bdc.db present but the resolved block/cell itself carries no survey evidence (`unknown_block_count`
53
+ * > 0 for the one queried unit) → `{ type: "abstain", reason: "insufficient_survey_data", layer:
54
+ * "bdc" }`. `vintage` IS still populated here — the LAYER didn't abstain, only this one cell lacks
55
+ * coverage.
56
+ * - bdc.db present, block surveyed, zero filings → the spec's POSITIVE meaning-of-zero case ("a
57
+ * genuine 'surveyed, zero providers here' result" — `filing-landscape.ts`'s own docstring). No
58
+ * `filing` evidence entries are pushed (there's nothing to report), but the filing layer still
59
+ * counts as COVERED for `coverage_confidence` — the absence is informative, not unknown.
60
+ *
61
+ * **Physical evidence.** Symmetric to the above, over `nearestInfrastructure`'s hits — every hit
62
+ * becomes its own `{ type: "physical_plant" }` entry, nearest-first, whatever `nearestInfrastructure`'s
63
+ * own ring/limit budget returns.
64
+ *
65
+ * - The tech maps to `[]` categories (no physical falsifier claimed) → the step is skipped outright;
66
+ * no evidence entry, no abstain, and this axis is excluded from `coverage_confidence` (see below).
67
+ * - The tech implies categories but `deps.poi` is absent → `{ type: "abstain", reason:
68
+ * "requires_build_local_layer", layer: "poi" }` (decision 6 — the poi-executor abstain precedent).
69
+ * - The tech implies categories, `deps.poi` is present, but no coordinate is resolvable (a geoid-only
70
+ * claim) → no evidence entry, no abstain (see the claim-resolution note above); the axis degrades to
71
+ * UNKNOWN for `coverage_confidence` purposes.
72
+ * - Otherwise → `nearestInfrastructure` runs; each hit is emitted, and the searched point's OWN res-6
73
+ * coverage cell (independent of whether any hit was found — a covered-but-empty cell is real
74
+ * evidence the area was surveyed) is read directly via `readLayerCoverage` to determine the layer's
75
+ * coverage state for this claim.
76
+ *
77
+ * **`coverage_confidence` — survey completeness, NOT evidence-found.** This is deliberately orthogonal
78
+ * to whether any evidence was actually found (spec §4 rule 4: "coverage_confidence is mandatory on
79
+ * every answer… the product's honesty is this refusal to guess" — a refusal that has to hold even when
80
+ * the answer turns out to be "nothing found"). Each layer contributes one of `"covered"` / `"unknown"`
81
+ * / `"not_applicable"` (the last only for the physical axis, when the tech maps to no category), and
82
+ * the pair combines per the brief's literal formula: both covered → `"high"`; either UNKNOWN → degrade
83
+ * to `"low"`; both absent/unknown → `"insufficient_survey_data"`.
84
+ *
85
+ * The `"not_applicable"` extension (not literally spelled out in the brief, since the brief's formula
86
+ * assumes a real pair) is this module's own documented decision, deliberately CONSERVATIVE: a tech with
87
+ * no physical falsifier (DSL, cable, satellite, power-line) can never reach `"high"` confidence from
88
+ * filing coverage alone — it degrades to filing-covered → `"low"`, filing-unknown/absent →
89
+ * `"insufficient_survey_data"`. Rationale: spec §4 frames physical co-presence as the vertical's
90
+ * distinguishing "falsifier that only fires positively" and reserves `"high"` for genuine two-channel
91
+ * corroboration (Gate 2 in Task 6: "matching filing + nearby plant in covered cells… high"); a tech
92
+ * that structurally can never offer a second channel should not be able to claim the same top
93
+ * confidence a fiber claim earns by actually having one available. This reading is more conservative
94
+ * than the alternative (treating the missing axis as inert and reporting `"high"` off filing alone) —
95
+ * flagged here for review since Task 6's four gates don't exercise a no-physical-falsifier tech code.
96
+ *
97
+ * **Ledger note (task 4 review; extended task 5 fix round 1, finding 2) — the per-layer coverage-resolution sanity
98
+ * check.** Neither bdc.db's nor poi.db's `layer_manifest` records the COVERAGE-cell h3 resolution (6) that
99
+ * `res9ShortCellToRes6Parent` hardcodes on both sides — only each layer's ROW-spine resolution (9,
100
+ * `spineKeys.h3.resolution`) is ever recorded. A real fix needs a layer-contract schema addition (out
101
+ * of scope for this task; the same follow-up task 4's report already ticketed). What IS practical and
102
+ * cheap: each manifest is a single-row table already read at most once per call here, so whenever a layer is
103
+ * WIRED — `bdcDB`, `poi`, or both, checked independently — {@link assertLayerSpineResolution} compares that one
104
+ * layer's recorded `spineKeys.h3.resolution` directly against the `BDC_H3_RESOLUTION` constant `pointCell` is
105
+ * actually derived from, and throws on a mismatch, catching a layer built at a different spine resolution before it
106
+ * silently mis-joins a coverage cell. This is TWO-SIDED, not gated on both layers being present together: a
107
+ * poi-only call still checks poi's own recorded resolution, since `readLayerCoverage`'s poi-side join key (below)
108
+ * is derived from `BDC_H3_RESOLUTION` regardless of whether `bdcDB` is wired at all (the original one-sided version
109
+ * compared the two manifests to each other, so it silently skipped poi entirely whenever `bdcDB` was absent). It
110
+ * can NOT catch a layer whose row spine is 9 but whose COVERAGE cells were derived at some OTHER resolution than 6
111
+ * — that gap needs the schema addition, not a runtime assertion.
112
+ */
113
+ import { readLayerCoverage, readLayerManifest } from "@mailwoman/core/layers";
114
+ import { shortCellToInt } from "@mailwoman/spatial";
115
+ import { latLngToCell } from "h3-js";
116
+ import { BDC_H3_RESOLUTION } from "../schema.js";
117
+ import { BDC_SPEED_BUCKET_100_1000, BDC_SPEED_BUCKET_25_100, BDC_SPEED_BUCKET_GIGABIT, BDC_SPEED_BUCKET_UNDER_25, filingLandscape, res9ShortCellToRes6Parent, speedBucketForDownloadSpeed, } from "./filing-landscape.js";
118
+ import { nearestInfrastructure } from "./nearest-infrastructure.js";
119
+ import { BroadbandTechnologyCategory, BroadbandTechnologyCategoryToCodeSet, BroadbandTechnologyCode, } from "./technologies.js";
120
+ /**
121
+ * The three fixed-wireless codes (unlicensed/licensed/licensed-by-rule), read off
122
+ * {@link BroadbandTechnologyCategoryToCodeSet} (decision 8: reuse, never re-derive) rather than hand-enumerated here a
123
+ * second time — an FCC code addition to the `FixedWireless` category in `technologies.ts` now flows straight through to
124
+ * {@link PLAUSIBILITY_TECH_PHYSICAL_CATEGORIES} instead of silently missing this table.
125
+ */
126
+ const FIXED_WIRELESS_CODES = BroadbandTechnologyCategoryToCodeSet[BroadbandTechnologyCategory.FixedWireless];
127
+ /**
128
+ * Tech → physical-plant category mapping (exported per the task brief). Fiber implies the three
129
+ * infrastructure-extension categories a fiber network plausibly touches; the three fixed-wireless codes imply a comms
130
+ * tower; every other code maps to `[]` — no physical falsifier is claimed for it (see
131
+ * {@link physicalCategoriesForTechnology}).
132
+ */
133
+ export const PLAUSIBILITY_TECH_PHYSICAL_CATEGORIES = {
134
+ [BroadbandTechnologyCode.OpticalCarrierFiber]: ["telecom_exchange", "telecom_cabinet", "data_center"],
135
+ ...Object.fromEntries([...FIXED_WIRELESS_CODES].map((code) => [code, ["tower_comms"]])),
136
+ };
137
+ /**
138
+ * The poi-taxonomy category ids a physical-plant search should probe for a given BDC technology code, or `[]` when that
139
+ * technology has no physical falsifier in this vertical (see {@link PLAUSIBILITY_TECH_PHYSICAL_CATEGORIES}).
140
+ */
141
+ export function physicalCategoriesForTechnology(technologyCode) {
142
+ return PLAUSIBILITY_TECH_PHYSICAL_CATEGORIES[technologyCode] ?? [];
143
+ }
144
+ /**
145
+ * Ordinal rank of each `speed_bucket` label, so "at or above claimed speed" is a numeric comparison rather than a
146
+ * string one. Mirrors the bucket ORDER `filing-landscape.ts` defines (never re-derived — decision 8).
147
+ */
148
+ const SPEED_BUCKET_RANK = {
149
+ [BDC_SPEED_BUCKET_UNDER_25]: 0,
150
+ [BDC_SPEED_BUCKET_25_100]: 1,
151
+ [BDC_SPEED_BUCKET_100_1000]: 2,
152
+ [BDC_SPEED_BUCKET_GIGABIT]: 3,
153
+ };
154
+ /**
155
+ * `BDCDatabase extends LayerContractDatabase` structurally, but Kysely's `transaction()` makes `Kysely<DB>` INVARIANT
156
+ * in `DB` — same cast idiom as `filing-landscape.ts`'s own private `asContractDB` (decision 8: reuse, never re-derive;
157
+ * copied rather than imported since the original is module-private).
158
+ */
159
+ function asContractDB(kdb) {
160
+ return kdb;
161
+ }
162
+ /**
163
+ * `true` when `filing` corroborates the claim: same `technology_code`, AND `filing.speed_bucket` ranks at or above the
164
+ * claimed download speed's own bucket. A different tech, or a same-tech but LESSER filing, is `false` — never disproof,
165
+ * just non-corroborating (spec §3.2 step 2).
166
+ */
167
+ function filingCorroborates(filing, claim) {
168
+ if (filing.technology_code !== claim.technologyCode)
169
+ return false;
170
+ const filingRank = SPEED_BUCKET_RANK[filing.speed_bucket];
171
+ // An unrecognized speed_bucket (a corrupted/foreign row) can't corroborate — never guess a rank for it.
172
+ if (filingRank === undefined)
173
+ return false;
174
+ const claimedRank = SPEED_BUCKET_RANK[speedBucketForDownloadSpeed(claim.claimedDownloadMbps)];
175
+ return filingRank >= claimedRank;
176
+ }
177
+ /**
178
+ * Collapse the fine-grained {@link PlausibilityCoverageAxisState} down to the 3-value space `combineCoverage` actually
179
+ * reasons over: `"layer_missing"` and `"cell_unsurveyed"` are both simply UNKNOWN for confidence-combination purposes
180
+ * (the distinction only matters for `coverage_detail`'s attribution, not for the confidence math itself).
181
+ */
182
+ function confidenceStateForAxis(state) {
183
+ if (state === "covered")
184
+ return "covered";
185
+ if (state === "not_applicable")
186
+ return "not_applicable";
187
+ return "unknown";
188
+ }
189
+ /**
190
+ * Combine the two layers' coverage states into the bundle's `coverage_confidence` — see the module docstring for the
191
+ * `"not_applicable"` extension's reasoning (deliberately conservative: never `"high"` without a real, applicable
192
+ * two-channel opportunity).
193
+ */
194
+ function combineCoverage(filingState, physicalState) {
195
+ const filing = confidenceStateForAxis(filingState);
196
+ const physical = confidenceStateForAxis(physicalState);
197
+ if (physical === "not_applicable") {
198
+ return filing === "covered" ? "low" : "insufficient_survey_data";
199
+ }
200
+ if (filing === "covered" && physical === "covered")
201
+ return "high";
202
+ if (filing === "unknown" && physical === "unknown")
203
+ return "insufficient_survey_data";
204
+ return "low";
205
+ }
206
+ /**
207
+ * See the module docstring's "ledger note" section. Throws when a WIRED layer's manifest disagrees with
208
+ * `BDC_H3_RESOLUTION` — the single constant `plausibilityCheck` actually uses at runtime to derive both the
209
+ * filing-lookup cell (bdc side, via `pointCell`) and the coverage-cell join key `readLayerCoverage` is read against
210
+ * (poi side, via `res9ShortCellToRes6Parent(pointCell)`).
211
+ *
212
+ * Task 5 fix round 1 (review finding 2): checked independently PER LAYER, whenever THAT layer is wired, rather than
213
+ * only when both `bdcDB` and `poi` are wired together. The original one-sided assertion compared the two manifests to
214
+ * EACH OTHER, which meant a poi-only call (no `bdcDB`) never checked poi's recorded resolution at all — even though
215
+ * `pointCell` (computed unconditionally from `BDC_H3_RESOLUTION`) still drives the poi coverage-cell read below.
216
+ * Comparing each layer directly against the constant is also strictly stronger than the retired manifest-vs-manifest
217
+ * check: it catches a layer built under a since-changed `BDC_H3_RESOLUTION` even when the OTHER layer is absent
218
+ * entirely, not just a disagreement between two present layers.
219
+ */
220
+ async function assertLayerSpineResolution(layer, contractDB, expectedResolution) {
221
+ const manifest = await readLayerManifest(contractDB);
222
+ const resolution = manifest.spineKeys.h3?.resolution;
223
+ if (resolution === undefined || resolution !== expectedResolution) {
224
+ throw new Error(`plausibilityCheck: ${layer}.db's recorded h3 spine resolution (${String(resolution)}) does not match ` +
225
+ `BDC_H3_RESOLUTION (${expectedResolution}) — the res-9→res-6 coverage-cell reconstruction this scorer ` +
226
+ `relies on assumes they match; refusing to compose evidence against a mismatched layer rather than ` +
227
+ `silently mis-joining a coverage cell.`);
228
+ }
229
+ }
230
+ /**
231
+ * Compose filing evidence + physical evidence into one `{ claim, evidence_found, coverage_confidence }` bundle — see
232
+ * the module docstring for the full composition rules. Never emits anything expressible as "implausible": absence of a
233
+ * filing or of nearby plant only ever surfaces as an abstain, an omitted evidence entry, or a degraded
234
+ * `coverage_confidence` — never a negative verdict.
235
+ */
236
+ export async function plausibilityCheck(claim, deps) {
237
+ // 1. Resolve a coordinate for physical-evidence search (independent of the filing-evidence spatial key below) —
238
+ // claim.point directly, or claim.address geocoded via deps.geocode.
239
+ let point = claim.point;
240
+ if (!point && claim.address) {
241
+ if (!deps.geocode) {
242
+ throw new Error("plausibilityCheck: claim.address requires deps.geocode");
243
+ }
244
+ const geocoded = await deps.geocode(claim.address);
245
+ if (geocoded.lat == null || geocoded.lon == null) {
246
+ throw new Error(`plausibilityCheck: geocode could not resolve a coordinate for ${JSON.stringify(claim.address)}`);
247
+ }
248
+ point = { type: "Point", coordinates: [geocoded.lon, geocoded.lat] };
249
+ }
250
+ if (!claim.geoid && !point) {
251
+ throw new Error("plausibilityCheck: claim must supply one of `geoid`, `point`, or a resolvable `address`");
252
+ }
253
+ // decision 4: geoid wins outright — the exact, native filing-evidence path. Otherwise the resolved point's own
254
+ // res-9 cell is the (flagged, unsound) approximation.
255
+ const blockResolution = claim.geoid ? "geoid" : "h3_cell_approximation";
256
+ const pointCell = point
257
+ ? shortCellToInt(latLngToCell(point.coordinates[1], point.coordinates[0], BDC_H3_RESOLUTION))
258
+ : undefined;
259
+ // Ledger note (task 4 review; task 5 fix round 1 finding 2): cheap, one-time per-layer sanity check — see the
260
+ // module docstring. Runs independently per WIRED layer, not only when both are present — a poi-only call still
261
+ // joins poi's coverage table against a BDC_H3_RESOLUTION-derived cell (below) and must not do so unchecked.
262
+ if (deps.bdcDB) {
263
+ await assertLayerSpineResolution("bdc", asContractDB(deps.bdcDB), BDC_H3_RESOLUTION);
264
+ }
265
+ if (deps.poi) {
266
+ await assertLayerSpineResolution("poi", deps.poi.contractDB, BDC_H3_RESOLUTION);
267
+ }
268
+ const evidence = [];
269
+ let vintage = null;
270
+ let filingCoverage = "layer_missing";
271
+ if (!deps.bdcDB) {
272
+ evidence.push({ type: "abstain", reason: "requires_bdc_layer", layer: "bdc" });
273
+ }
274
+ else {
275
+ // blockResolution === "geoid" iff claim.geoid is set (see above), so exactly one of these two branches ever
276
+ // runs, and the `pointCell!` assertion below is safe: blockResolution === "h3_cell_approximation" only when
277
+ // claim.geoid is absent, which (per the throw above) means `point` — and therefore `pointCell` — is defined.
278
+ const landscape = blockResolution === "geoid"
279
+ ? await filingLandscape(deps.bdcDB, { geoids: [claim.geoid] })
280
+ : await filingLandscape(deps.bdcDB, { h3Cells: [pointCell] });
281
+ vintage = landscape.vintage;
282
+ if (landscape.surveyed_block_count > 0) {
283
+ filingCoverage = "covered";
284
+ for (const filing of landscape.filings) {
285
+ evidence.push({
286
+ type: "filing",
287
+ filing,
288
+ vintage: landscape.vintage,
289
+ corroborates: filingCorroborates(filing, claim),
290
+ });
291
+ }
292
+ }
293
+ else {
294
+ filingCoverage = "cell_unsurveyed";
295
+ evidence.push({ type: "abstain", reason: "insufficient_survey_data", layer: "bdc" });
296
+ }
297
+ }
298
+ const physicalCategories = physicalCategoriesForTechnology(claim.technologyCode);
299
+ let physicalCoverage = "not_applicable";
300
+ if (physicalCategories.length) {
301
+ if (!deps.poi) {
302
+ physicalCoverage = "layer_missing";
303
+ evidence.push({ type: "abstain", reason: "requires_build_local_layer", layer: "poi" });
304
+ }
305
+ else if (!point) {
306
+ // Geoid-only claim, no coordinate resolvable — see the module docstring's claim-resolution note. A real
307
+ // capability gap, not a missing-layer abstain: no evidence entry is fabricated, but the axis still
308
+ // degrades honestly for coverage_confidence (and now names ITS OWN reason in `coverage_detail`, distinct
309
+ // from `"layer_missing"`, rather than folding into the same generic "unknown" — task 5 fix round 1
310
+ // finding 1).
311
+ physicalCoverage = "no_coordinate";
312
+ }
313
+ else {
314
+ const hits = await nearestInfrastructure(deps.poi.lookup, deps.poi.contractDB, {
315
+ center: point,
316
+ categoryIDs: [...physicalCategories],
317
+ });
318
+ for (const hit of hits) {
319
+ evidence.push({ type: "physical_plant", hit });
320
+ }
321
+ const coverageCell = await readLayerCoverage(deps.poi.contractDB, res9ShortCellToRes6Parent(pointCell));
322
+ physicalCoverage = coverageCell ? "covered" : "cell_unsurveyed";
323
+ }
324
+ }
325
+ return {
326
+ claim,
327
+ evidence_found: evidence,
328
+ coverage_confidence: combineCoverage(filingCoverage, physicalCoverage),
329
+ coverage_detail: { filing: filingCoverage, physical: physicalCoverage },
330
+ block_resolution: blockResolution,
331
+ vintage,
332
+ };
333
+ }
334
+ //# sourceMappingURL=plausibility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plausibility.js","sourceRoot":"","sources":["../../sdk/plausibility.ts"],"names":[],"mappings":"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"}