@mailwoman/resolver-wof-sqlite 2.1.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 +250 -0
- package/out/address-point-interpolation.d.ts +48 -0
- package/out/address-point-interpolation.d.ts.map +1 -0
- package/out/address-point-interpolation.js +164 -0
- package/out/address-point-interpolation.js.map +1 -0
- package/out/address-point-schema.d.ts +58 -0
- package/out/address-point-schema.d.ts.map +1 -0
- package/out/address-point-schema.js +67 -0
- package/out/address-point-schema.js.map +1 -0
- package/out/address-point.d.ts +29 -0
- package/out/address-point.d.ts.map +1 -0
- package/out/address-point.js +62 -0
- package/out/address-point.js.map +1 -0
- package/out/ancestry.d.ts +40 -0
- package/out/ancestry.d.ts.map +1 -0
- package/out/ancestry.js +53 -0
- package/out/ancestry.js.map +1 -0
- package/out/build-candidate-cli.d.ts +16 -0
- package/out/build-candidate-cli.d.ts.map +1 -0
- package/out/build-candidate-cli.js +80 -0
- package/out/build-candidate-cli.js.map +1 -0
- package/out/build-candidate.d.ts +54 -0
- package/out/build-candidate.d.ts.map +1 -0
- package/out/build-candidate.js +230 -0
- package/out/build-candidate.js.map +1 -0
- package/out/build-coincident-roles-cli.d.ts +16 -0
- package/out/build-coincident-roles-cli.d.ts.map +1 -0
- package/out/build-coincident-roles-cli.js +94 -0
- package/out/build-coincident-roles-cli.js.map +1 -0
- package/out/build-fts-cli.d.ts +23 -0
- package/out/build-fts-cli.d.ts.map +1 -0
- package/out/build-fts-cli.js +117 -0
- package/out/build-fts-cli.js.map +1 -0
- package/out/build-slim-cli.d.ts +14 -0
- package/out/build-slim-cli.d.ts.map +1 -0
- package/out/build-slim-cli.js +130 -0
- package/out/build-slim-cli.js.map +1 -0
- package/out/build-slim.d.ts +71 -0
- package/out/build-slim.d.ts.map +1 -0
- package/out/build-slim.js +267 -0
- package/out/build-slim.js.map +1 -0
- package/out/candidate-lookup.d.ts +43 -0
- package/out/candidate-lookup.d.ts.map +1 -0
- package/out/candidate-lookup.js +191 -0
- package/out/candidate-lookup.js.map +1 -0
- package/out/candidate-schema.d.ts +86 -0
- package/out/candidate-schema.d.ts.map +1 -0
- package/out/candidate-schema.js +109 -0
- package/out/candidate-schema.js.map +1 -0
- package/out/coincident-roles.d.ts +86 -0
- package/out/coincident-roles.d.ts.map +1 -0
- package/out/coincident-roles.js +160 -0
- package/out/coincident-roles.js.map +1 -0
- package/out/convention.d.ts +109 -0
- package/out/convention.d.ts.map +1 -0
- package/out/convention.js +94 -0
- package/out/convention.js.map +1 -0
- package/out/fst-autocomplete.d.ts +49 -0
- package/out/fst-autocomplete.d.ts.map +1 -0
- package/out/fst-autocomplete.js +124 -0
- package/out/fst-autocomplete.js.map +1 -0
- package/out/fst-builder.d.ts +20 -0
- package/out/fst-builder.d.ts.map +1 -0
- package/out/fst-builder.js +219 -0
- package/out/fst-builder.js.map +1 -0
- package/out/fst-deserialize-web.d.ts +16 -0
- package/out/fst-deserialize-web.d.ts.map +1 -0
- package/out/fst-deserialize-web.js +133 -0
- package/out/fst-deserialize-web.js.map +1 -0
- package/out/fst-matcher.d.ts +33 -0
- package/out/fst-matcher.d.ts.map +1 -0
- package/out/fst-matcher.js +117 -0
- package/out/fst-matcher.js.map +1 -0
- package/out/fst-serialize.d.ts +30 -0
- package/out/fst-serialize.d.ts.map +1 -0
- package/out/fst-serialize.js +261 -0
- package/out/fst-serialize.js.map +1 -0
- package/out/fst-types.d.ts +60 -0
- package/out/fst-types.d.ts.map +1 -0
- package/out/fst-types.js +11 -0
- package/out/fst-types.js.map +1 -0
- package/out/fts.d.ts +158 -0
- package/out/fts.d.ts.map +1 -0
- package/out/fts.js +261 -0
- package/out/fts.js.map +1 -0
- package/out/geo.d.ts +74 -0
- package/out/geo.d.ts.map +1 -0
- package/out/geo.js +88 -0
- package/out/geo.js.map +1 -0
- package/out/index.d.ts +27 -0
- package/out/index.d.ts.map +1 -0
- package/out/index.js +22 -0
- package/out/index.js.map +1 -0
- package/out/interpolation.d.ts +84 -0
- package/out/interpolation.d.ts.map +1 -0
- package/out/interpolation.js +150 -0
- package/out/interpolation.js.map +1 -0
- package/out/lookup.d.ts +156 -0
- package/out/lookup.d.ts.map +1 -0
- package/out/lookup.js +876 -0
- package/out/lookup.js.map +1 -0
- package/out/postal-city-alias-lookup.d.ts +50 -0
- package/out/postal-city-alias-lookup.d.ts.map +1 -0
- package/out/postal-city-alias-lookup.js +66 -0
- package/out/postal-city-alias-lookup.js.map +1 -0
- package/out/postal-city-alias-schema.d.ts +51 -0
- package/out/postal-city-alias-schema.d.ts.map +1 -0
- package/out/postal-city-alias-schema.js +47 -0
- package/out/postal-city-alias-schema.js.map +1 -0
- package/out/postal-city-candidate-schema.d.ts +58 -0
- package/out/postal-city-candidate-schema.d.ts.map +1 -0
- package/out/postal-city-candidate-schema.js +56 -0
- package/out/postal-city-candidate-schema.js.map +1 -0
- package/out/postcode-point-lookup.d.ts +38 -0
- package/out/postcode-point-lookup.d.ts.map +1 -0
- package/out/postcode-point-lookup.js +46 -0
- package/out/postcode-point-lookup.js.map +1 -0
- package/out/reverse.d.ts +99 -0
- package/out/reverse.d.ts.map +1 -0
- package/out/reverse.js +290 -0
- package/out/reverse.js.map +1 -0
- package/out/schema.d.ts +163 -0
- package/out/schema.d.ts.map +1 -0
- package/out/schema.js +18 -0
- package/out/schema.js.map +1 -0
- package/out/sharding.d.ts +96 -0
- package/out/sharding.d.ts.map +1 -0
- package/out/sharding.js +129 -0
- package/out/sharding.js.map +1 -0
- package/out/sqlite-convention-source.d.ts +29 -0
- package/out/sqlite-convention-source.d.ts.map +1 -0
- package/out/sqlite-convention-source.js +53 -0
- package/out/sqlite-convention-source.js.map +1 -0
- package/out/sqlite-utils.d.ts +17 -0
- package/out/sqlite-utils.d.ts.map +1 -0
- package/out/sqlite-utils.js +24 -0
- package/out/sqlite-utils.js.map +1 -0
- package/out/street-morphology-fst-builder.d.ts +59 -0
- package/out/street-morphology-fst-builder.d.ts.map +1 -0
- package/out/street-morphology-fst-builder.js +174 -0
- package/out/street-morphology-fst-builder.js.map +1 -0
- package/out/street-normalize.d.ts +66 -0
- package/out/street-normalize.d.ts.map +1 -0
- package/out/street-normalize.js +176 -0
- package/out/street-normalize.js.map +1 -0
- package/out/street-segment-schema.d.ts +61 -0
- package/out/street-segment-schema.d.ts.map +1 -0
- package/out/street-segment-schema.js +64 -0
- package/out/street-segment-schema.js.map +1 -0
- package/out/types.d.ts +137 -0
- package/out/types.d.ts.map +1 -0
- package/out/types.js +13 -0
- package/out/types.js.map +1 -0
- package/out/unified-schema.d.ts +25 -0
- package/out/unified-schema.d.ts.map +1 -0
- package/out/unified-schema.js +142 -0
- package/out/unified-schema.js.map +1 -0
- package/package.json +54 -0
package/out/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*/
|
|
6
|
+
export { WofSqlitePlaceLookup } from "./lookup.js";
|
|
7
|
+
export { WofCandidateTableLookup } from "./candidate-lookup.js";
|
|
8
|
+
export { ADDRESS_POINT_COLUMNS, createAddressPointIndexes, createAddressPointTable } from "./address-point-schema.js";
|
|
9
|
+
export { WofPostalCityAliasLookup, } from "./postal-city-alias-lookup.js";
|
|
10
|
+
export { POSTAL_CITY_CANDIDATE_COLUMNS, POSTAL_CITY_CANDIDATE_TABLE, createPostalCityCandidateTable, } from "./postal-city-candidate-schema.js";
|
|
11
|
+
export { ADDRESS_CONVENTION_TABLE, BUILTIN_STRATEGY_NAMES, SeedConventionSource, WORLD_DEFAULT, mergeConventions, resolveConvention, } from "./convention.js";
|
|
12
|
+
export { SqliteConventionSource } from "./sqlite-convention-source.js";
|
|
13
|
+
export { WofPostcodeLookup } from "./postcode-point-lookup.js";
|
|
14
|
+
export { PLACE_BBOX_TABLE, PLACE_SEARCH_TABLE, buildPlaceSearchFts, placeBboxExists, placeSearchFtsExists, } from "./fts.js";
|
|
15
|
+
export { bboxAround, geometryContains, haversineKm, pointInPolygonRings, pointInRing, } from "./geo.js";
|
|
16
|
+
export { PLACETYPE_DEPTH, ancestorLineage, placetypeDepth } from "./ancestry.js";
|
|
17
|
+
export { WofReverseGeocoder, } from "./reverse.js";
|
|
18
|
+
export { AddressPointInterpolator } from "./address-point-interpolation.js";
|
|
19
|
+
export { AddressPointSqliteLookup } from "./address-point.js";
|
|
20
|
+
export { StreetInterpolator, } from "./interpolation.js";
|
|
21
|
+
export { deriveSchemaName, pickShardForPlacetype, resolveShards, } from "./sharding.js";
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
package/out/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,OAAO,EAAE,oBAAoB,EAAsD,MAAM,aAAa,CAAA;AAEtG,OAAO,EAAE,uBAAuB,EAAoC,MAAM,uBAAuB,CAAA;AAEjG,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AAErH,OAAO,EACN,wBAAwB,GAGxB,MAAM,+BAA+B,CAAA;AAEtC,OAAO,EACN,6BAA6B,EAC7B,2BAA2B,EAC3B,8BAA8B,GAC9B,MAAM,mCAAmC,CAAA;AAG1C,OAAO,EACN,wBAAwB,EACxB,sBAAsB,EACtB,oBAAoB,EACpB,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAMjB,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAEtE,OAAO,EAAE,iBAAiB,EAAsB,MAAM,4BAA4B,CAAA;AAElF,OAAO,EACN,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,GAGpB,MAAM,UAAU,CAAA;AAEjB,OAAO,EACN,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,mBAAmB,EACnB,WAAW,GAMX,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAyB,MAAM,eAAe,CAAA;AAEvG,OAAO,EACN,kBAAkB,GAKlB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAA;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,EACN,kBAAkB,GAIlB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACN,gBAAgB,EAChB,qBAAqB,EACrB,aAAa,GAGb,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* House-number interpolation (#483): when the exact address-point tier (#476, `address-point.ts`)
|
|
7
|
+
* misses, estimate the coordinate from TIGER street-segment ranges — parity-aware range match,
|
|
8
|
+
* then linear interpolation along the segment polyline. Design:
|
|
9
|
+
* `docs/articles/plan/2026-06-11-interpolation-design.md`.
|
|
10
|
+
*
|
|
11
|
+
* Reads the per-state shard built by `scripts/build-interpolation-shard.ts` (`street_segment`: one
|
|
12
|
+
* row per TIGER edge SIDE — independent left/right ranges, ZIPs, parity). Query-side
|
|
13
|
+
* normalization is THE shared normalizer (`street-normalize.ts`) — identical to build-side, by
|
|
14
|
+
* construction.
|
|
15
|
+
*
|
|
16
|
+
* Every answer is honest about being an estimate: `interpolated: true`, `parityMatched` (false when
|
|
17
|
+
* only the opposite side's range contained the number — usually the right block, wrong side of
|
|
18
|
+
* the street), and `uncertaintyM` (half the matched segment's length — the #483 issue's honest
|
|
19
|
+
* default). Scoping is postcode-first (a given ZIP that scopes to nothing is a MISS — the
|
|
20
|
+
* statewide retry was measured and rejected, see `find()`); without a postcode the statewide name
|
|
21
|
+
* match must agree on a single postcode or the lookup ABSTAINS (a common street name spanning
|
|
22
|
+
* towns is ambiguity, not an answer).
|
|
23
|
+
*
|
|
24
|
+
* Standalone in this slice — core tier wiring (`resolution_tier: "interpolated"` after the
|
|
25
|
+
* exact-point fall-through) is a noted follow-up on #483, so the `find()` shape mirrors
|
|
26
|
+
* `AddressPointLookup.find()` to keep that wiring mechanical.
|
|
27
|
+
*/
|
|
28
|
+
import { DatabaseSync } from "node:sqlite";
|
|
29
|
+
import type { InterpolationLookup } from "@mailwoman/resolver";
|
|
30
|
+
/**
|
|
31
|
+
* How an interpolated answer was computed (#483 Method 2):
|
|
32
|
+
*
|
|
33
|
+
* - `address_point` — bracketed/extrapolated between REAL neighbor points from the #476 shard
|
|
34
|
+
* (`AddressPointInterpolator`), replacing TIGER's uniform-spacing assumption with occupancy.
|
|
35
|
+
* - `tiger_range` — linear position within a TIGER segment's theoretical house-number range
|
|
36
|
+
* (`StreetInterpolator`), the fallback for streets too sparse to bracket.
|
|
37
|
+
*/
|
|
38
|
+
export type InterpolationMethod = "address_point" | "tiger_range";
|
|
39
|
+
/** One interpolated coordinate estimate. Never an exact situs point — see `uncertaintyM`. */
|
|
40
|
+
export interface InterpolatedHit {
|
|
41
|
+
lat: number;
|
|
42
|
+
lon: number;
|
|
43
|
+
/** Always true — the tier's honesty flag, mirrored into `resolution_tier` when wired. */
|
|
44
|
+
interpolated: true;
|
|
45
|
+
/** Which rung answered — see {@link InterpolationMethod}. */
|
|
46
|
+
method: InterpolationMethod;
|
|
47
|
+
/**
|
|
48
|
+
* `tiger_range` only. True when the matched segment side's parity agrees with the house number
|
|
49
|
+
* (or the side is `mixed`). False = opposite-side fallback: usually the right block, wrong side
|
|
50
|
+
* of the street.
|
|
51
|
+
*/
|
|
52
|
+
parityMatched?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* `address_point` only. `both` = the query number sits between two known neighbor numbers;
|
|
55
|
+
* `single` = neighbors exist on one side only (extrapolated, larger `uncertaintyM`).
|
|
56
|
+
*/
|
|
57
|
+
bracket?: "both" | "single";
|
|
58
|
+
/**
|
|
59
|
+
* Honest uncertainty radius in meters: half the matched segment's polyline length
|
|
60
|
+
* (`tiger_range`), half the bracket span (`address_point`/`both`), or the explicitly larger
|
|
61
|
+
* extrapolation penalty (`address_point`/`single`).
|
|
62
|
+
*/
|
|
63
|
+
uncertaintyM: number;
|
|
64
|
+
/** Provenance, e.g. `"tiger:edges"`. */
|
|
65
|
+
source: string;
|
|
66
|
+
/** Pinned data vintage, e.g. `"TIGER2023"`. */
|
|
67
|
+
release: string;
|
|
68
|
+
}
|
|
69
|
+
export interface InterpolationQuery {
|
|
70
|
+
street: string;
|
|
71
|
+
number: string;
|
|
72
|
+
/** ZIP scope — strongly preferred; without it common street names abstain (see module doc). */
|
|
73
|
+
postcode?: string;
|
|
74
|
+
}
|
|
75
|
+
export declare class StreetInterpolator implements InterpolationLookup {
|
|
76
|
+
#private;
|
|
77
|
+
constructor(opts: {
|
|
78
|
+
dbPath?: string;
|
|
79
|
+
database?: DatabaseSync;
|
|
80
|
+
});
|
|
81
|
+
find(query: InterpolationQuery): InterpolatedHit | null;
|
|
82
|
+
close(): void;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=interpolation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interpolation.d.ts","sourceRoot":"","sources":["../interpolation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAM9D;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG,aAAa,CAAA;AAEjE,6FAA6F;AAC7F,MAAM,WAAW,eAAe;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,yFAAyF;IACzF,YAAY,EAAE,IAAI,CAAA;IAClB,6DAA6D;IAC7D,MAAM,EAAE,mBAAmB,CAAA;IAC3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;IAC3B;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAcD,qBAAa,kBAAmB,YAAW,mBAAmB;;gBAMjD,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;KAAE;IAyB9D,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,eAAe,GAAG,IAAI;IAmDvD,KAAK,IAAI,IAAI;CAGb"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* House-number interpolation (#483): when the exact address-point tier (#476, `address-point.ts`)
|
|
7
|
+
* misses, estimate the coordinate from TIGER street-segment ranges — parity-aware range match,
|
|
8
|
+
* then linear interpolation along the segment polyline. Design:
|
|
9
|
+
* `docs/articles/plan/2026-06-11-interpolation-design.md`.
|
|
10
|
+
*
|
|
11
|
+
* Reads the per-state shard built by `scripts/build-interpolation-shard.ts` (`street_segment`: one
|
|
12
|
+
* row per TIGER edge SIDE — independent left/right ranges, ZIPs, parity). Query-side
|
|
13
|
+
* normalization is THE shared normalizer (`street-normalize.ts`) — identical to build-side, by
|
|
14
|
+
* construction.
|
|
15
|
+
*
|
|
16
|
+
* Every answer is honest about being an estimate: `interpolated: true`, `parityMatched` (false when
|
|
17
|
+
* only the opposite side's range contained the number — usually the right block, wrong side of
|
|
18
|
+
* the street), and `uncertaintyM` (half the matched segment's length — the #483 issue's honest
|
|
19
|
+
* default). Scoping is postcode-first (a given ZIP that scopes to nothing is a MISS — the
|
|
20
|
+
* statewide retry was measured and rejected, see `find()`); without a postcode the statewide name
|
|
21
|
+
* match must agree on a single postcode or the lookup ABSTAINS (a common street name spanning
|
|
22
|
+
* towns is ambiguity, not an answer).
|
|
23
|
+
*
|
|
24
|
+
* Standalone in this slice — core tier wiring (`resolution_tier: "interpolated"` after the
|
|
25
|
+
* exact-point fall-through) is a noted follow-up on #483, so the `find()` shape mirrors
|
|
26
|
+
* `AddressPointLookup.find()` to keep that wiring mechanical.
|
|
27
|
+
*/
|
|
28
|
+
import { DatabaseSync } from "node:sqlite";
|
|
29
|
+
import { haversineKm } from "./geo.js";
|
|
30
|
+
import { hasTable } from "./sqlite-utils.js";
|
|
31
|
+
import { canonicalizeRouteKey, normalizeStreetForKey } from "./street-normalize.js";
|
|
32
|
+
export class StreetInterpolator {
|
|
33
|
+
#db;
|
|
34
|
+
#ownsDb;
|
|
35
|
+
#byPostcode;
|
|
36
|
+
#byStreet;
|
|
37
|
+
constructor(opts) {
|
|
38
|
+
if (opts.database) {
|
|
39
|
+
this.#db = opts.database;
|
|
40
|
+
this.#ownsDb = false;
|
|
41
|
+
}
|
|
42
|
+
else if (opts.dbPath) {
|
|
43
|
+
this.#db = new DatabaseSync(opts.dbPath, { readOnly: true });
|
|
44
|
+
this.#ownsDb = true;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
throw new Error("StreetInterpolator: one of dbPath or database is required");
|
|
48
|
+
}
|
|
49
|
+
// Degrade gracefully on an empty/tableless shard (interrupted build, stray 0-byte file): with no
|
|
50
|
+
// `street_segment` table this interpolator is a no-op miss, not a crash that loses the state (#568).
|
|
51
|
+
if (hasTable(this.#db, "street_segment")) {
|
|
52
|
+
const columns = `from_hn, to_hn, min_hn, max_hn, parity, postcode, geometry, source, release`;
|
|
53
|
+
this.#byPostcode = this.#db.prepare(`SELECT ${columns} FROM street_segment
|
|
54
|
+
WHERE postcode = ? AND street_norm = ? AND min_hn <= ? AND max_hn >= ?`);
|
|
55
|
+
this.#byStreet = this.#db.prepare(`SELECT ${columns} FROM street_segment
|
|
56
|
+
WHERE street_norm = ? AND min_hn <= ? AND max_hn >= ?`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
find(query) {
|
|
60
|
+
if (!this.#byPostcode || !this.#byStreet)
|
|
61
|
+
return null;
|
|
62
|
+
const streetNorm = canonicalizeRouteKey(normalizeStreetForKey(query.street));
|
|
63
|
+
const numberRaw = query.number.trim();
|
|
64
|
+
// Strictly-numeric house numbers only — this tier estimates, it doesn't guess at
|
|
65
|
+
// hyphenated/alphanumeric schemes the ranges don't model.
|
|
66
|
+
if (!streetNorm || !/^\d+$/.test(numberRaw))
|
|
67
|
+
return null;
|
|
68
|
+
const n = Number(numberRaw);
|
|
69
|
+
let rows;
|
|
70
|
+
if (query.postcode) {
|
|
71
|
+
// A given ZIP that scopes to nothing is a MISS, not a statewide guess: the retry was
|
|
72
|
+
// measured (2026-06-11 VT eval) at +2.3pp coverage for a poisoned tail (p99 1.0 → 20.8
|
|
73
|
+
// km, max 204 km — a unique name statewide can live in a far-away town).
|
|
74
|
+
rows = this.#byPostcode.all(query.postcode.trim(), streetNorm, n, n);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// No scope given: a name matching ranges across several ZIPs is ambiguous — abstain.
|
|
78
|
+
rows = this.#byStreet.all(streetNorm, n, n);
|
|
79
|
+
const postcodes = new Set(rows.map((r) => r.postcode ?? ""));
|
|
80
|
+
if (postcodes.size > 1)
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (rows.length === 0)
|
|
84
|
+
return null;
|
|
85
|
+
// Parity preference: exact side first, then 'mixed' (matches either), then the
|
|
86
|
+
// opposite side as a flagged fallback.
|
|
87
|
+
const wantOdd = n % 2 === 1;
|
|
88
|
+
const exact = rows.filter((r) => r.parity === (wantOdd ? "odd" : "even"));
|
|
89
|
+
const mixed = rows.filter((r) => r.parity === "mixed");
|
|
90
|
+
const preferred = exact.length > 0 ? exact : mixed;
|
|
91
|
+
const pool = preferred.length > 0 ? preferred : rows;
|
|
92
|
+
const parityMatched = preferred.length > 0;
|
|
93
|
+
// Tightest range wins — the most specific claim about where this number lives.
|
|
94
|
+
const best = pool.reduce((a, b) => (b.max_hn - b.min_hn < a.max_hn - a.min_hn ? b : a));
|
|
95
|
+
const polyline = JSON.parse(best.geometry);
|
|
96
|
+
const span = best.to_hn - best.from_hn;
|
|
97
|
+
const t = span === 0 ? 0.5 : clamp01((n - best.from_hn) / span);
|
|
98
|
+
const [lon, lat, lengthKm] = pointAlong(polyline, t);
|
|
99
|
+
return {
|
|
100
|
+
lat,
|
|
101
|
+
lon,
|
|
102
|
+
interpolated: true,
|
|
103
|
+
method: "tiger_range",
|
|
104
|
+
parityMatched,
|
|
105
|
+
uncertaintyM: Math.round((lengthKm * 1000) / 2),
|
|
106
|
+
source: best.source,
|
|
107
|
+
release: best.release,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
close() {
|
|
111
|
+
if (this.#ownsDb)
|
|
112
|
+
this.#db.close();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function clamp01(t) {
|
|
116
|
+
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Point at fraction `t` of the polyline's total arc length (haversine), plus the total length in
|
|
120
|
+
* km. `t` is assumed clamped to [0, 1].
|
|
121
|
+
*/
|
|
122
|
+
function pointAlong(polyline, t) {
|
|
123
|
+
const legs = [];
|
|
124
|
+
let total = 0;
|
|
125
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
126
|
+
const [aLon, aLat] = polyline[i - 1];
|
|
127
|
+
const [bLon, bLat] = polyline[i];
|
|
128
|
+
const d = haversineKm(aLat, aLon, bLat, bLon);
|
|
129
|
+
legs.push(d);
|
|
130
|
+
total += d;
|
|
131
|
+
}
|
|
132
|
+
if (total === 0) {
|
|
133
|
+
const [lon, lat] = polyline[0];
|
|
134
|
+
return [lon, lat, 0];
|
|
135
|
+
}
|
|
136
|
+
let remaining = t * total;
|
|
137
|
+
for (let i = 0; i < legs.length; i++) {
|
|
138
|
+
const leg = legs[i];
|
|
139
|
+
if (remaining <= leg || i === legs.length - 1) {
|
|
140
|
+
const f = leg === 0 ? 0 : clamp01(remaining / leg);
|
|
141
|
+
const [aLon, aLat] = polyline[i];
|
|
142
|
+
const [bLon, bLat] = polyline[i + 1];
|
|
143
|
+
return [aLon + (bLon - aLon) * f, aLat + (bLat - aLat) * f, total];
|
|
144
|
+
}
|
|
145
|
+
remaining -= leg;
|
|
146
|
+
}
|
|
147
|
+
const [lon, lat] = polyline[polyline.length - 1];
|
|
148
|
+
return [lon, lat, total];
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=interpolation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interpolation.js","sourceRoot":"","sources":["../interpolation.ts"],"names":[],"mappings":"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"}
|
package/out/lookup.d.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `WofSqlitePlaceLookup` — the resolver implementation backed by `node:sqlite` + a Kysely-typed
|
|
7
|
+
* query layer where the queries are non-trivial, and raw SQL where they aren't (FTS5 MATCH, the
|
|
8
|
+
* FTS index build).
|
|
9
|
+
*
|
|
10
|
+
* See `docs/plan/phases/PHASE_4_2_wof_sqlite.md` for the design rationale.
|
|
11
|
+
*/
|
|
12
|
+
import { DatabaseSync } from "node:sqlite";
|
|
13
|
+
import { type Ancestor, type CoincidentLocality } from "@mailwoman/resolver";
|
|
14
|
+
import { type Convention, type ConventionSource } from "./convention.js";
|
|
15
|
+
import type { WofPostalCityAliasLookup } from "./postal-city-alias-lookup.js";
|
|
16
|
+
import { type ShardConfig } from "./sharding.js";
|
|
17
|
+
import type { FindPlaceQuery, PlaceCandidate, PlaceLookup } from "./types.js";
|
|
18
|
+
export interface WofSqlitePlaceLookupOpts {
|
|
19
|
+
/**
|
|
20
|
+
* Path to the WOF SQLite distribution on disk. Mutually exclusive with `database`.
|
|
21
|
+
*
|
|
22
|
+
* **Single string** — opens that one DB as the main shard.
|
|
23
|
+
*
|
|
24
|
+
* **Array** — opens the first entry as main, then ATTACHes each subsequent entry as a separate
|
|
25
|
+
* SQLite schema. Schema names are derived from the filename (`whosonfirst-data-postalcode-
|
|
26
|
+
* us-latest.db` → `postalcode_us`); override with `ShardConfig.schemaName` when the filename
|
|
27
|
+
* doesn't follow WOF convention. See `sharding.ts` for the derivation rules.
|
|
28
|
+
*
|
|
29
|
+
* Routing: queries with a `placetype` matching a shard's name (or explicit `placetypes` hint) are
|
|
30
|
+
* sent to that shard; everything else hits main. Cross-shard UNION is NOT done — BM25 isn't
|
|
31
|
+
* comparable across separately-indexed corpora.
|
|
32
|
+
*/
|
|
33
|
+
databasePath?: string | ReadonlyArray<string | ShardConfig>;
|
|
34
|
+
/**
|
|
35
|
+
* Pre-opened DatabaseSync — primarily for tests against an inline fixture DB. Mutually exclusive
|
|
36
|
+
* with `databasePath`. Multi-shard requires `databasePath` (so the lookup owns the ATTACH).
|
|
37
|
+
*/
|
|
38
|
+
database?: DatabaseSync;
|
|
39
|
+
/**
|
|
40
|
+
* If true, build the FTS5 `place_search` virtual table on construction if it doesn't already
|
|
41
|
+
* exist. The upstream WOF distribution does NOT ship FTS5, so callers either set this once on
|
|
42
|
+
* first open or pre-build it via the operator-side CLI documented in the README. Default false —
|
|
43
|
+
* the resolver assumes the index already exists and errors loudly if it doesn't.
|
|
44
|
+
*
|
|
45
|
+
* With multi-shard, `buildFts: true` builds the index on the **main** shard only. Other shards
|
|
46
|
+
* must be pre-built via `mailwoman-wof-build-fts` — operator script for predictable cost.
|
|
47
|
+
*/
|
|
48
|
+
buildFts?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Geographic Rule Engine convention source (Direction E, #289). Per-WOF-polygon resolution
|
|
51
|
+
* profiles, either as a ready `ConventionSource` or a plain `{ wofId: Convention }` seed map.
|
|
52
|
+
* Default empty — every query rides `WORLD_DEFAULT` (the EU coordinate-first behavior). JP/KR/TW
|
|
53
|
+
* add rows; #290 wires a build-from-source sqlite-backed source here.
|
|
54
|
+
*/
|
|
55
|
+
conventions?: ConventionSource | Record<number, Convention>;
|
|
56
|
+
/**
|
|
57
|
+
* Opt-in postal-city alias reader (#475). When supplied, the coordinate-first locality scorer
|
|
58
|
+
* treats an observed `postal_city` ("Antioch", postcode 37013) as a name-match alias for the
|
|
59
|
+
* geographic locality the postcode sits in ("Nashville"), recovering the chronic postal-vs-
|
|
60
|
+
* geographic-city mismatch. Absent (the default), the resolver is byte-identical — every alias
|
|
61
|
+
* code path is gated on this being non-null, so an unprovided reader changes no score.
|
|
62
|
+
*/
|
|
63
|
+
postalCityAliases?: WofPostalCityAliasLookup;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Ranking weights for `findPlace`. Tweakable per-instance but defaults match the values declared in
|
|
67
|
+
* the Phase 4.2 plan doc.
|
|
68
|
+
*/
|
|
69
|
+
export interface RankingWeights {
|
|
70
|
+
/** Boost when the candidate's placetype matches an explicit `placetype` filter. */
|
|
71
|
+
placetypeMatchBoost: number;
|
|
72
|
+
/** Boost when the candidate is a locality and no explicit placetype was requested. */
|
|
73
|
+
localityImplicitBoost: number;
|
|
74
|
+
/** Boost when the candidate's country matches an explicit `country` filter. */
|
|
75
|
+
countryMatchBoost: number;
|
|
76
|
+
/** Boost when the candidate is a direct child of the requested `parentId`. */
|
|
77
|
+
directChildBoost: number;
|
|
78
|
+
/** Boost when the candidate is a transitive descendant of the requested `parentId`. */
|
|
79
|
+
descendantBoost: number;
|
|
80
|
+
/** Multiplier on the length-penalty term (penalizes much-longer-than-query names). */
|
|
81
|
+
lengthPenaltyWeight: number;
|
|
82
|
+
/**
|
|
83
|
+
* Magnitude of the proximity boost when the query carries `near`. The contribution is
|
|
84
|
+
* `proximityBoost / (1 + distanceKm / proximityScaleKm)` — at distance 0 the boost is full
|
|
85
|
+
* magnitude, at `proximityScaleKm` it's half, decaying further with distance. Default tuned so
|
|
86
|
+
* proximity can overcome a typical FTS rank tie but not dominate a strong text match.
|
|
87
|
+
*/
|
|
88
|
+
proximityBoost: number;
|
|
89
|
+
/** Distance (km) at which the proximity boost halves. Tune to the typical query radius. */
|
|
90
|
+
proximityScaleKm: number;
|
|
91
|
+
/**
|
|
92
|
+
* Magnitude of the population boost when the candidate has a known `wof:population`. The
|
|
93
|
+
* contribution is `populationBoost * log10(1 + population) / populationScaleLog10`, capped at
|
|
94
|
+
* `populationBoost`. WOF only carries population for ~15% of localities (mostly larger ones);
|
|
95
|
+
* places without it get +0 (never a penalty). Default tuned so the famous Springfield, IL (pop
|
|
96
|
+
* ~112k) gets ~0.42 boost — enough to nudge past tiny same-name peers.
|
|
97
|
+
*/
|
|
98
|
+
populationBoost: number;
|
|
99
|
+
/**
|
|
100
|
+
* Population (in log10) at which the boost reaches its full magnitude. Default 6 — i.e. a
|
|
101
|
+
* population of 1,000,000 gives `populationBoost` exactly. Larger populations cap at the same
|
|
102
|
+
* value (no compounding effect for megacities).
|
|
103
|
+
*/
|
|
104
|
+
populationScaleLog10: number;
|
|
105
|
+
/**
|
|
106
|
+
* Tier candidates with an EXACT name/alias match above candidates that only match partially,
|
|
107
|
+
* BEFORE the weighted-sum score is consulted. Default true.
|
|
108
|
+
*
|
|
109
|
+
* Why this is needed (and why it ALIGNS with — rather than overrides — the population/importance
|
|
110
|
+
* signal): the weighted sum adds population as a large additive boost (`populationBoost`, up to
|
|
111
|
+
* +4) so that famous places surface for unambiguous full-name queries. But population is a
|
|
112
|
+
* _prominence prior_ — its job is to break ties among candidates that match the query EQUALLY
|
|
113
|
+
* WELL (e.g. "Springfield" → Springfield IL over Springfield MA, both exact name matches). It was
|
|
114
|
+
* never meant to promote a place that matches the query WORSE. For a 2-letter region abbreviation
|
|
115
|
+
* that backfires: querying "ME" returns Maine (which has the exact alias `ME`) AND Missouri/
|
|
116
|
+
* Michigan/etc. (which do not), and Missouri's larger population (+4) overcomes Maine's bm25 edge
|
|
117
|
+
* — so "Portland, ME" resolves its region to Missouri and the locality then cascades to the wrong
|
|
118
|
+
* state. Tiering restores the intended ordering: **match quality is the primary key, prominence
|
|
119
|
+
* (population) the secondary key WITHIN a tier.** Springfield-IL-over-MA still works (both exact
|
|
120
|
+
* → same tier → population decides); ME→Maine now works (only Maine is exact → higher tier →
|
|
121
|
+
* population never gets to override it). See
|
|
122
|
+
* docs/articles/evals/2026-05-30-resolver-exact-match.md.
|
|
123
|
+
*
|
|
124
|
+
* Note: tiering re-ranks within the over-fetched candidate window (`limit * 4`); a pathological
|
|
125
|
+
* exact match that falls outside that window is not rescued. For the region-abbrev case the
|
|
126
|
+
* window is comfortably sufficient (a handful of states match a 2-letter query).
|
|
127
|
+
*/
|
|
128
|
+
exactMatchTiering: boolean;
|
|
129
|
+
}
|
|
130
|
+
export declare class WofSqlitePlaceLookup implements PlaceLookup, Disposable {
|
|
131
|
+
#private;
|
|
132
|
+
constructor(opts: WofSqlitePlaceLookupOpts, weights?: Partial<RankingWeights>);
|
|
133
|
+
findPlace(query: FindPlaceQuery): Promise<PlaceCandidate[]>;
|
|
134
|
+
/**
|
|
135
|
+
* Dual-role localities coincident with an admin id, from the precomputed `coincident_roles`
|
|
136
|
+
* relation (#403). Backs {@link ResolveOpts.hierarchyCompletion} (#405): O(1) once the relation is
|
|
137
|
+
* loaded. Returns `[]` when the relation table is absent (older DB) or the admin isn't a
|
|
138
|
+
* dual-role place, so completion degrades gracefully. The relation + `spr` join is loaded once
|
|
139
|
+
* and memoized.
|
|
140
|
+
*/
|
|
141
|
+
coincidentLocalitiesFor(adminId: number | string): CoincidentLocality[];
|
|
142
|
+
/**
|
|
143
|
+
* The ancestor lineage of a place — its containment chain joined with `spr` for canonical names,
|
|
144
|
+
* ordered NEAREST-FIRST (localadmin → county → region → … → country). Backs
|
|
145
|
+
* {@link ResolveOpts.includeAncestors} (#404). Self is excluded; memoized per id. Returns `[]`
|
|
146
|
+
* when the place has no recorded ancestry.
|
|
147
|
+
*
|
|
148
|
+
* The walk itself lives in `ancestry.ts` (shared with the reverse geocoder, #484); the ordering
|
|
149
|
+
* is its `PLACETYPE_DEPTH` table — same ranking as the previous inline SQL CASE, extended below
|
|
150
|
+
* `localadmin` so locality/neighbourhood ancestors order correctly instead of sorting last.
|
|
151
|
+
*/
|
|
152
|
+
ancestors(id: number | string): Ancestor[];
|
|
153
|
+
close(): void;
|
|
154
|
+
[Symbol.dispose](): void;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=lookup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lookup.d.ts","sourceRoot":"","sources":["../lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,YAAY,EAAsB,MAAM,aAAa,CAAA;AAI9D,OAAO,EAAyB,KAAK,QAAQ,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACnG,OAAO,EAIN,KAAK,UAAU,EACf,KAAK,gBAAgB,EAGrB,MAAM,iBAAiB,CAAA;AAcxB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAE7E,OAAO,EAA4D,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAE1G,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAgB,MAAM,YAAY,CAAA;AAE3F,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,GAAG,WAAW,CAAC,CAAA;IAC3D;;;OAGG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC3D;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,wBAAwB,CAAA;CAC5C;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,mFAAmF;IACnF,mBAAmB,EAAE,MAAM,CAAA;IAC3B,sFAAsF;IACtF,qBAAqB,EAAE,MAAM,CAAA;IAC7B,+EAA+E;IAC/E,iBAAiB,EAAE,MAAM,CAAA;IACzB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAA;IACxB,uFAAuF;IACvF,eAAe,EAAE,MAAM,CAAA;IACvB,sFAAsF;IACtF,mBAAmB,EAAE,MAAM,CAAA;IAC3B;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAA;IACtB,2FAA2F;IAC3F,gBAAgB,EAAE,MAAM,CAAA;IACxB;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,oBAAoB,EAAE,MAAM,CAAA;IAC5B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,iBAAiB,EAAE,OAAO,CAAA;CAC1B;AA4HD,qBAAa,oBAAqB,YAAW,WAAW,EAAE,UAAU;;gBAwDvD,IAAI,EAAE,wBAAwB,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC;IAyFvE,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAoBjE;;;;;;OAMG;IACH,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,kBAAkB,EAAE;IAgDvE;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,EAAE;IA8e1C,KAAK,IAAI,IAAI;IASb,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;CAgBxB"}
|