@mailwoman/resolver-wof-sqlite 10.7.0 → 10.8.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 (156) hide show
  1. package/lib/address/point/interpolation.ts +4 -4
  2. package/lib/address/point/spine-schema.ts +59 -0
  3. package/lib/ancestry/backfill.ts +3 -25
  4. package/lib/build/candidate.ts +22 -5
  5. package/lib/candidate/alias-bags.ts +32 -14
  6. package/lib/candidate/bare-admin-names.ts +54 -0
  7. package/lib/candidate/importance.ts +2 -2
  8. package/lib/candidate/lookup.ts +54 -26
  9. package/lib/candidate/own-name.ts +1 -5
  10. package/lib/candidate/scoring.ts +4 -4
  11. package/lib/capitals.ts +2 -2
  12. package/lib/coincident-roles.ts +3 -3
  13. package/lib/currency-backfill.ts +4 -4
  14. package/lib/fst/autocomplete.ts +1 -1
  15. package/lib/fst/binary-matcher.ts +379 -0
  16. package/lib/fst/builder.ts +1 -1
  17. package/lib/fst/deserialize-web.ts +14 -143
  18. package/lib/fst/matcher.ts +3 -3
  19. package/lib/fst/serialize.ts +1 -1
  20. package/lib/fst.ts +1 -0
  21. package/lib/geonames/aliases.ts +2 -2
  22. package/lib/interpolation.ts +3 -2
  23. package/lib/lookup.ts +6 -6
  24. package/lib/nsul/lookup.ts +2 -1
  25. package/lib/nsul/schema.ts +2 -8
  26. package/lib/place-importance-schema.ts +1 -1
  27. package/lib/poi/lookup.ts +14 -14
  28. package/lib/proximity-rerank.ts +2 -2
  29. package/lib/reverse.ts +8 -5
  30. package/lib/search-fetch.ts +3 -3
  31. package/lib/sqlite/convention-source.ts +1 -1
  32. package/lib/street/centroid.ts +2 -2
  33. package/lib/street/morphology/fst/builder.ts +3 -3
  34. package/lib/street/morphology/fst/loader.ts +3 -3
  35. package/lib/street/normalize.ts +59 -1
  36. package/lib/types.ts +8 -0
  37. package/lib/uprn/existence.ts +0 -1
  38. package/lib/uprn/lookup.ts +5 -5
  39. package/lib/uprn/schema.ts +3 -4
  40. package/lib/weights-overlay-linker.ts +4 -4
  41. package/out/address/point/interpolation.js +4 -4
  42. package/out/address/point/interpolation.js.map +1 -1
  43. package/out/address/point/spine-schema.d.ts +40 -0
  44. package/out/address/point/spine-schema.d.ts.map +1 -0
  45. package/out/address/point/spine-schema.js +40 -0
  46. package/out/address/point/spine-schema.js.map +1 -0
  47. package/out/ancestry/backfill.d.ts.map +1 -1
  48. package/out/ancestry/backfill.js +3 -22
  49. package/out/ancestry/backfill.js.map +1 -1
  50. package/out/build/candidate.d.ts.map +1 -1
  51. package/out/build/candidate.js +17 -5
  52. package/out/build/candidate.js.map +1 -1
  53. package/out/candidate/alias-bags.d.ts +8 -0
  54. package/out/candidate/alias-bags.d.ts.map +1 -1
  55. package/out/candidate/alias-bags.js +20 -11
  56. package/out/candidate/alias-bags.js.map +1 -1
  57. package/out/candidate/bare-admin-names.d.ts +26 -0
  58. package/out/candidate/bare-admin-names.d.ts.map +1 -0
  59. package/out/candidate/bare-admin-names.js +38 -0
  60. package/out/candidate/bare-admin-names.js.map +1 -0
  61. package/out/candidate/importance.js +2 -2
  62. package/out/candidate/importance.js.map +1 -1
  63. package/out/candidate/lookup.d.ts.map +1 -1
  64. package/out/candidate/lookup.js +35 -19
  65. package/out/candidate/lookup.js.map +1 -1
  66. package/out/candidate/own-name.d.ts.map +1 -1
  67. package/out/candidate/own-name.js +1 -5
  68. package/out/candidate/own-name.js.map +1 -1
  69. package/out/candidate/scoring.js +4 -4
  70. package/out/candidate/scoring.js.map +1 -1
  71. package/out/capitals.js +2 -2
  72. package/out/capitals.js.map +1 -1
  73. package/out/coincident-roles.js +3 -3
  74. package/out/coincident-roles.js.map +1 -1
  75. package/out/currency-backfill.js +4 -4
  76. package/out/currency-backfill.js.map +1 -1
  77. package/out/env.d.ts +3 -0
  78. package/out/env.d.ts.map +1 -1
  79. package/out/fst/autocomplete.js +1 -1
  80. package/out/fst/autocomplete.js.map +1 -1
  81. package/out/fst/binary-matcher.d.ts +34 -0
  82. package/out/fst/binary-matcher.d.ts.map +1 -0
  83. package/out/fst/binary-matcher.js +294 -0
  84. package/out/fst/binary-matcher.js.map +1 -0
  85. package/out/fst/builder.js +1 -1
  86. package/out/fst/builder.js.map +1 -1
  87. package/out/fst/deserialize-web.d.ts +11 -6
  88. package/out/fst/deserialize-web.d.ts.map +1 -1
  89. package/out/fst/deserialize-web.js +12 -94
  90. package/out/fst/deserialize-web.js.map +1 -1
  91. package/out/fst/matcher.js +3 -3
  92. package/out/fst/matcher.js.map +1 -1
  93. package/out/fst/serialize.js +1 -1
  94. package/out/fst/serialize.js.map +1 -1
  95. package/out/fst.d.ts +1 -0
  96. package/out/fst.d.ts.map +1 -1
  97. package/out/fst.js +1 -0
  98. package/out/fst.js.map +1 -1
  99. package/out/geonames/aliases.js +2 -2
  100. package/out/geonames/aliases.js.map +1 -1
  101. package/out/interpolation.d.ts.map +1 -1
  102. package/out/interpolation.js +3 -2
  103. package/out/interpolation.js.map +1 -1
  104. package/out/lookup.d.ts +2 -2
  105. package/out/lookup.d.ts.map +1 -1
  106. package/out/lookup.js +4 -4
  107. package/out/lookup.js.map +1 -1
  108. package/out/nsul/lookup.d.ts +1 -1
  109. package/out/nsul/lookup.d.ts.map +1 -1
  110. package/out/nsul/lookup.js +1 -1
  111. package/out/nsul/lookup.js.map +1 -1
  112. package/out/nsul/schema.d.ts +2 -5
  113. package/out/nsul/schema.d.ts.map +1 -1
  114. package/out/nsul/schema.js +2 -7
  115. package/out/nsul/schema.js.map +1 -1
  116. package/out/place-importance-schema.js +1 -1
  117. package/out/place-importance-schema.js.map +1 -1
  118. package/out/poi/lookup.d.ts +3 -3
  119. package/out/poi/lookup.d.ts.map +1 -1
  120. package/out/poi/lookup.js +9 -9
  121. package/out/poi/lookup.js.map +1 -1
  122. package/out/proximity-rerank.js +2 -2
  123. package/out/proximity-rerank.js.map +1 -1
  124. package/out/reverse.d.ts.map +1 -1
  125. package/out/reverse.js +7 -5
  126. package/out/reverse.js.map +1 -1
  127. package/out/search-fetch.js +3 -3
  128. package/out/search-fetch.js.map +1 -1
  129. package/out/sqlite/convention-source.js +1 -1
  130. package/out/sqlite/convention-source.js.map +1 -1
  131. package/out/street/centroid.js +2 -2
  132. package/out/street/centroid.js.map +1 -1
  133. package/out/street/morphology/fst/builder.d.ts +2 -2
  134. package/out/street/morphology/fst/builder.js +2 -2
  135. package/out/street/morphology/fst/builder.js.map +1 -1
  136. package/out/street/morphology/fst/loader.d.ts +2 -2
  137. package/out/street/morphology/fst/loader.js +2 -2
  138. package/out/street/morphology/fst/loader.js.map +1 -1
  139. package/out/street/normalize.d.ts +16 -0
  140. package/out/street/normalize.d.ts.map +1 -1
  141. package/out/street/normalize.js +51 -1
  142. package/out/street/normalize.js.map +1 -1
  143. package/out/types.d.ts +7 -0
  144. package/out/types.d.ts.map +1 -1
  145. package/out/uprn/existence.d.ts.map +1 -1
  146. package/out/uprn/existence.js.map +1 -1
  147. package/out/uprn/lookup.d.ts.map +1 -1
  148. package/out/uprn/lookup.js +3 -3
  149. package/out/uprn/lookup.js.map +1 -1
  150. package/out/uprn/schema.d.ts +1 -1
  151. package/out/uprn/schema.d.ts.map +1 -1
  152. package/out/uprn/schema.js +1 -1
  153. package/out/uprn/schema.js.map +1 -1
  154. package/out/weights-overlay-linker.js +4 -4
  155. package/out/weights-overlay-linker.js.map +1 -1
  156. package/package.json +16 -13
@@ -25,9 +25,10 @@
25
25
  */
26
26
 
27
27
  import type { UkCountryCode } from "@mailwoman/codex/gb/country"
28
+ import { compactPostcode } from "@mailwoman/postcode/display-form"
28
29
  import { SQLiteLookup, type SQLiteLookupOptions } from "@mailwoman/sqlite/lookup"
29
30
 
30
- import { compactPostcode, type NSULDatabase } from "#nsul/schema"
31
+ import type { NSULDatabase } from "#nsul/schema"
31
32
  import { prepareAll, prepareGet } from "#sqlite-utils"
32
33
 
33
34
  /**
@@ -70,13 +70,6 @@ export interface NSULDatabase extends layerschemadatabase {
70
70
  nsul_meta: NSULMetaTable
71
71
  }
72
72
 
73
- /**
74
- * The compact form of a unit postcode, every space removed and upper-cased.
75
- */
76
- export function compactPostcode(pcds: string): string {
77
- return pcds.replaceAll(/\s+/g, "").toUpperCase()
78
- }
79
-
80
73
  export async function createUPRNPostcodeTable(db: Kysely<NSULDatabase>): Promise<void> {
81
74
  await db.schema
82
75
  .createTable("uprn_postcode")
@@ -102,7 +95,8 @@ export async function createNSULMetaTable(db: Kysely<NSULDatabase>): Promise<voi
102
95
  /**
103
96
  * The `pcds_compact` index the `uprnsForPostcode` probe reads.
104
97
  *
105
- * There is no index on the spaced `pcds` because it is derivable through {@link compactPostcode}.
98
+ * There is no index on the spaced `pcds` because `compactPostcode` from
99
+ * `@mailwoman/postcode/display-form` derives the compact column from it.
106
100
  */
107
101
  export async function createNSULIndexes(db: Kysely<NSULDatabase>): Promise<void> {
108
102
  await db.schema.createIndex("uprn_postcode_pcds_compact").on("uprn_postcode").column("pcds_compact").execute()
@@ -262,7 +262,7 @@ export function loadImportanceSplit<DB>(db: DatabaseClient<DB>): ImportanceSplit
262
262
  for (const row of rows) {
263
263
  const split = splitLegacyImportance(row.importance, population.get(row.id))
264
264
 
265
- if (split.encyclopedic === undefined) {
265
+ if (!split.encyclopedic) {
266
266
  legacyFallbackRows++
267
267
  } else {
268
268
  encyclopedic.set(row.id, split.encyclopedic)
package/lib/poi/lookup.ts CHANGED
@@ -7,16 +7,16 @@
7
7
  * `without rowid` B-tree `poi-schema.ts` builds. Category, brand and name searches share one
8
8
  * artifact. Each path has a method that returns its details.
9
9
  *
10
- * `latLngToCell` and `gridDisk` come from `h3-js`. The 48-bit short-cell packing that turns a raw
10
+ * `latLngToCell` and `gridDisk` come from `@sister.software/h3`. The 48-bit short-cell packing that turns a raw
11
11
  * H3 cell into the integer `poi.h3_cell` stores is `@mailwoman/spatial`'s `shortCellToInt`, and
12
12
  * that math is never reimplemented here.
13
13
  */
14
14
 
15
15
  import { allRows } from "@mailwoman/core/utils"
16
- import { haversineKm, shortCellToInt, type H3Cell } from "@mailwoman/spatial"
16
+ import { haversine, shortCellToInt } from "@mailwoman/spatial"
17
17
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
18
18
  import { SQLiteLookup, type SQLiteLookupOptions } from "@mailwoman/sqlite/lookup"
19
- import { gridDisk, latLngToCell } from "h3-js"
19
+ import { gridDisk, latLngToCell } from "@sister.software/h3"
20
20
 
21
21
  import type { POICategoryCodeTable, POIDatabase, POITable } from "#poi/schema"
22
22
  /**
@@ -79,7 +79,7 @@ export interface POISearchQuery {
79
79
  *
80
80
  * Required for category/brand queries (k-ring expansion).
81
81
  */
82
- center?: { latitude: number; longitude: number }
82
+ center?: { latitude: number; longitude: number } | null
83
83
  /**
84
84
  * Ring budget: the count of res-9 k-rings to expand before giving up.
85
85
  *
@@ -88,7 +88,7 @@ export interface POISearchQuery {
88
88
  * @defaultValue `16` ≈ ~5.4 km
89
89
  */
90
90
  maxRings?: number
91
- limit?: number
91
+ limit?: number | null
92
92
  }
93
93
 
94
94
  export interface POISearchHit {
@@ -183,7 +183,7 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
183
183
  const limit = Math.max(1, query.limit ?? DEFAULT_LIMIT)
184
184
 
185
185
  if (query.name) {
186
- return this.#searchByName(query.name, limit, query.center)
186
+ return this.#searchByName(query.name, limit, query.center ?? undefined)
187
187
  }
188
188
 
189
189
  if (query.categoryIDs?.length || query.brandWikidata) {
@@ -216,7 +216,7 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
216
216
 
217
217
  return sortByDistance(rows, center)
218
218
  .filter(
219
- (row) => haversineKm(center.latitude, center.longitude, row.latitude, row.longitude) <= BRAND_MAX_DISTANCE_KM
219
+ (row) => haversine(center.latitude, center.longitude, row.latitude, row.longitude) <= BRAND_MAX_DISTANCE_KM
220
220
  )
221
221
  .slice(0, limit)
222
222
  .map((row) => toHit(row, this.#idToCategory, center))
@@ -235,7 +235,7 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
235
235
  for (const id of query.categoryIDs ?? []) {
236
236
  const resolved = this.#categoryToID.get(id)
237
237
 
238
- if (resolved !== undefined) {
238
+ if (resolved) {
239
239
  categoryIDs.push(resolved)
240
240
  }
241
241
  }
@@ -244,7 +244,7 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
244
244
  // This is a clean miss instead of a throw.
245
245
  if (!categoryIDs.length) return []
246
246
 
247
- const origin = latLngToCell(center.latitude, center.longitude, POI_H3_RESOLUTION) as H3Cell
247
+ const origin = latLngToCell(center.latitude, center.longitude, POI_H3_RESOLUTION)
248
248
  const seenCells = new Set<string>()
249
249
  let rows: POIRow[] = []
250
250
 
@@ -252,12 +252,12 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
252
252
  for (let ring = 0; ring < maxRings; ring++) {
253
253
  // gridDisk(origin, ring) returns the whole disk out to `ring`, so diffing against
254
254
  // the already-probed set derives only this ring's new cells.
255
- const diskCells = gridDisk(origin, ring) as string[]
255
+ const diskCells = gridDisk(origin, ring)
256
256
  const newCells = diskCells.filter((cell) => !seenCells.has(cell))
257
257
 
258
258
  for (const cell of newCells) {
259
259
  seenCells.add(cell)
260
- const shortCell = shortCellToInt(cell as H3Cell)
260
+ const shortCell = shortCellToInt(cell)
261
261
 
262
262
  for (const categoryID of categoryIDs) {
263
263
  rows.push(...allRows<POIRow>(this.#categoryCellProbe, shortCell, categoryID, limit))
@@ -344,8 +344,8 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
344
344
  function sortByDistance(rows: POIRow[], center: { latitude: number; longitude: number }): POIRow[] {
345
345
  return [...rows].toSorted(
346
346
  (a, b) =>
347
- haversineKm(center.latitude, center.longitude, a.latitude, a.longitude) -
348
- haversineKm(center.latitude, center.longitude, b.latitude, b.longitude)
347
+ haversine(center.latitude, center.longitude, a.latitude, a.longitude) -
348
+ haversine(center.latitude, center.longitude, b.latitude, b.longitude)
349
349
  )
350
350
  }
351
351
 
@@ -363,7 +363,7 @@ function toHit(
363
363
  country: row.country,
364
364
  confidence: row.confidence,
365
365
  gersID: row.gers_id,
366
- distanceM: center ? haversineKm(center.latitude, center.longitude, row.latitude, row.longitude) * 1000 : null,
366
+ distanceM: center ? haversine(center.latitude, center.longitude, row.latitude, row.longitude) * 1000 : null,
367
367
  }
368
368
  }
369
369
 
@@ -23,7 +23,7 @@
23
23
  * ordering silently discarded downstream.
24
24
  */
25
25
 
26
- import { haversineKm } from "@mailwoman/spatial"
26
+ import { haversine } from "@mailwoman/spatial/geometries/point/distance"
27
27
 
28
28
  /**
29
29
  * Full magnitude of the nearness term at distance 0, before decay.
@@ -92,7 +92,7 @@ export function combinedProminence(candidate: ProximityRerankable, bias: readonl
92
92
  // A 0,0 pair interpreted as a real coordinate would receive an enormous score.
93
93
  if (!(candidate.lat === 0 && candidate.lon === 0)) {
94
94
  for (const b of bias) {
95
- const d = haversineKm(b.lat, b.lon, candidate.lat, candidate.lon)
95
+ const d = haversine(b.lat, b.lon, candidate.lat, candidate.lon)
96
96
  const term = (BIAS_BOOST * (b.weight ?? 1)) / (1 + d / PROX_SCALE_KM)
97
97
 
98
98
  if (term > proxTerm) {
package/lib/reverse.ts CHANGED
@@ -24,7 +24,10 @@
24
24
 
25
25
  import { tryParsingJSON } from "@mailwoman/core/json"
26
26
  import { allRows } from "@mailwoman/core/utils"
27
- import { bboxAround, geometryContains, haversineKm, type ParsedGeometry } from "@mailwoman/spatial"
27
+ import type { ParsedGeometry } from "@mailwoman/spatial"
28
+ import { bboxAround } from "@mailwoman/spatial/bbox"
29
+ import { haversine } from "@mailwoman/spatial/geometries/point/distance"
30
+ import { geometryContains } from "@mailwoman/spatial/geometries/polygon"
28
31
  import { DatabaseClient } from "@mailwoman/sqlite/client"
29
32
  import { tableExists } from "@mailwoman/sqlite/introspection"
30
33
  import type { PathBuilderLike } from "path-ts"
@@ -311,7 +314,7 @@ export class WOFReverseGeocoder implements Disposable {
311
314
  let bestKm = Infinity
312
315
 
313
316
  for (const c of pointOnly) {
314
- const km = haversineKm(lat, lon, c.lat, c.lon)
317
+ const km = haversine(lat, lon, c.lat, c.lon)
315
318
 
316
319
  if (km < bestKm) {
317
320
  bestKm = km
@@ -325,7 +328,7 @@ export class WOFReverseGeocoder implements Disposable {
325
328
  // Approximate descent into finer tiers than the winner.
326
329
  let current = winner
327
330
  let currentConfirmed = winnerConfirmed
328
- let currentDistanceKm = currentConfirmed ? null : haversineKm(lat, lon, current.lat, current.lon)
331
+ let currentDistanceKm = currentConfirmed ? null : haversine(lat, lon, current.lat, current.lon)
329
332
 
330
333
  for (const tier of DESCENT_TIERS) {
331
334
  if (placetypeDepth(tier) <= placetypeDepth(current.placetype)) continue
@@ -349,7 +352,7 @@ export class WOFReverseGeocoder implements Disposable {
349
352
  }
350
353
 
351
354
  if (contains === false) continue // known not-here, polygon rejected
352
- const km = haversineKm(lat, lon, k.lat, k.lon)
355
+ const km = haversine(lat, lon, k.lat, k.lon)
353
356
 
354
357
  if (km <= maxApproximateKm && km < bestKm) {
355
358
  bestKm = km
@@ -494,7 +497,7 @@ export class WOFReverseGeocoder implements Disposable {
494
497
  if (!this.#polygons) return null
495
498
  const cached = this.#geometryCache.get(id)
496
499
 
497
- if (cached !== undefined) return cached
500
+ if (cached) return cached
498
501
 
499
502
  if (this.#geometryCache.size >= WOFReverseGeocoder.#GEOMETRY_CACHE_CAP) {
500
503
  this.#geometryCache.clear()
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import { allRows } from "@mailwoman/core/utils"
16
- import { bboxAround } from "@mailwoman/spatial"
16
+ import { bboxAround } from "@mailwoman/spatial/bbox"
17
17
  import type { DatabaseClient, SQLInputValue } from "@mailwoman/sqlite/client"
18
18
 
19
19
  import { PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE } from "#fts"
@@ -142,7 +142,7 @@ export function fetchSearchRows<DB>(options: {
142
142
  params.push(query.country)
143
143
  }
144
144
 
145
- if (query.parentID !== undefined) {
145
+ if (query.parentID) {
146
146
  where.push(`(spr.parent_id = ? OR spr.id IN (SELECT id FROM ${sch}.ancestors WHERE ancestor_id = ?))`)
147
147
  params.push(query.parentID, query.parentID)
148
148
  }
@@ -152,7 +152,7 @@ export function fetchSearchRows<DB>(options: {
152
152
  // A requested filter on a legacy extract is silently treated as no bbox filter
153
153
  // so the query does not crash.
154
154
  const extractHasBbox = hasBboxIndex.get(sch) === true
155
- const useBboxJoin = (query.bbox || query.near?.maxDistanceKm !== undefined) && extractHasBbox
155
+ const useBboxJoin = (query.bbox || query.near?.maxDistanceKm) && extractHasBbox
156
156
  let joinClause = `JOIN ${sch}.spr ON spr.id = place_search.wof_id`
157
157
 
158
158
  if (useBboxJoin) {
@@ -35,7 +35,7 @@ export class SqliteConventionSource<DB> implements ConventionSource {
35
35
  get(wofID: number): Convention | null {
36
36
  const cached = this.#cache.get(wofID)
37
37
 
38
- if (cached !== undefined) return cached
38
+ if (cached) return cached
39
39
  let value: Convention | null = null
40
40
 
41
41
  try {
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { StreetCentroidHit, StreetCentroidLookup } from "@mailwoman/core/resolver"
9
- import { haversineKm } from "@mailwoman/spatial"
9
+ import { haversine } from "@mailwoman/spatial/geometries/point/distance"
10
10
  import { DatabaseClient } from "@mailwoman/sqlite/client"
11
11
  import type { PathBuilderLike } from "path-ts"
12
12
 
@@ -51,7 +51,7 @@ const AGG_SELECT =
51
51
  * Half the bounding-box diagonal in meters, used as a coarse uncertainty radius for a street centroid.
52
52
  */
53
53
  function extentRadiusM(minLat: number, maxLat: number, minLon: number, maxLon: number): number {
54
- return Math.round(haversineKm(minLat, minLon, maxLat, maxLon) * 500)
54
+ return Math.round(haversine(minLat, minLon, maxLat, maxLon) * 500)
55
55
  }
56
56
 
57
57
  /**
@@ -17,7 +17,7 @@
17
17
  * Design rationale + the four-layer street-supplement architecture lives in
18
18
  * `docs/articles/concepts/street-supplement-architecture.md`.
19
19
  *
20
- * Source: `core/data/libpostal/dictionaries/{locale}/street_types.txt`. Each line is pipe-delimited
20
+ * Source: `dictionaries/data/libpostal/dictionaries/{locale}/street_types.txt`. Each line is pipe-delimited
21
21
  * surface forms with the canonical form first: avenue|av|ave|aven|avenu|avn|avnu|avnue
22
22
  *
23
23
  * Output: an `FSTMatcher` ready to serialize via `serializeFST` to e.g.
@@ -61,7 +61,7 @@ export const REGION_ABBREVIATION_VARIANTS: ReadonlySet<string> = new Set(
61
61
 
62
62
  export interface BuildStreetMorphologyFSTOpts {
63
63
  /**
64
- * Path to the `core/data/libpostal/dictionaries` directory containing per-locale subfolders.
64
+ * Path to the `dictionaries/data/libpostal/dictionaries` directory containing per-locale subfolders.
65
65
  */
66
66
  dictionariesDir: PathBuilderLike
67
67
  /**
@@ -213,7 +213,7 @@ export async function buildStreetMorphologyFST(
213
213
  const node = nodes[stateID]!
214
214
  let next = node.edges.get(t)
215
215
 
216
- if (next === undefined) {
216
+ if (!next) {
217
217
  next = nodes.length
218
218
  nodes.push({ edges: new Map(), places: [] })
219
219
  node.edges.set(t, next)
@@ -10,7 +10,7 @@
10
10
  * 1. An explicit `artifactPath`, e.g. the weights-package sibling surfaced by
11
11
  * `NeuralAddressClassifier.streetMorphologyPath`. When given, it is the only artifact probed.
12
12
  * 2. Otherwise the staged sealed artifact at `$MAILWOMAN_DATA_ROOT/db/wof/fst-street-morphology.bin`.
13
- * 3. A build-from-dictionaries fallback: `buildStreetMorphologyFST` over core's bundled libpostal
13
+ * 3. A build-from-dictionaries fallback: `buildStreetMorphologyFST` over `@mailwoman/dictionaries`' libpostal
14
14
  * `street_types.txt` files, kept so a missing artifact degrades to a per-process build instead
15
15
  * of a crash.
16
16
  *
@@ -21,7 +21,7 @@
21
21
 
22
22
  import { readLocalBuffer } from "@mailwoman/core/fs/readers"
23
23
  import { pathExists } from "@mailwoman/core/fs/readers/stat"
24
- import { resourceDictionaryPath } from "@mailwoman/core/paths"
24
+ import { resourceDictionaryPath } from "@mailwoman/dictionaries/paths"
25
25
  import type { PathBuilder, PathBuilderLike } from "path-ts"
26
26
 
27
27
  import type { FSTMatcher } from "#fst/matcher"
@@ -54,7 +54,7 @@ export interface LoadStreetMorphologyFSTOpts {
54
54
  /**
55
55
  * Dictionaries dir for the build fallback.
56
56
  *
57
- * @defaultValue core's bundled libpostal dictionaries.
57
+ * @defaultValue the libpostal dictionaries `@mailwoman/dictionaries` ships.
58
58
  */
59
59
  dictionariesDir?: PathBuilderLike
60
60
  /**
@@ -68,6 +68,16 @@ function fold(input: string): string {
68
68
  .trim()
69
69
  }
70
70
 
71
+ /**
72
+ * The name and version of the street-key normalization {@link normalizeStreetForKeyLocale} applies.
73
+ *
74
+ * An artifact manifest of a street-keyed role records this pair with its locale,
75
+ * and a reader refuses an artifact keyed by another version.
76
+ * Version `1` is the normalization in this module when versions began to be recorded, on 2026-10-29.
77
+ * A change to any key it produces is a new version.
78
+ */
79
+ export const STREET_KEY_NORMALIZATION = { algorithm: "street-key", version: "1" } as const
80
+
71
81
  /**
72
82
  * Normalizes a US or English street name into an address-point key, folding directionals,
73
83
  * spelled ordinals and the USPS street-type suffix.
@@ -90,7 +100,7 @@ export function normalizeStreetForKey(street: string): StreetKey {
90
100
  AbbreviationToDirectional.get(raw.toUpperCase())?.toLowerCase().replace(" ", "")
91
101
 
92
102
  const mergePair = (a?: string, b?: string) =>
93
- a && b && /^(north|south)$/.test(a) && /^(east|west)$/.test(b) ? a + b : undefined
103
+ a && b && /^(north|south)$/.test(a) && /^(east|west)$/.test(b) ? a + b : null
94
104
 
95
105
  const leadPair = mergePair(tokens[0], tokens[1])
96
106
 
@@ -138,6 +148,23 @@ export function normalizeStreetForKey(street: string): StreetKey {
138
148
  */
139
149
  export type StreetLocale = "us" | "en" | "es" | "fr" | "de" | "it" | "nl" | "pl" | "vn" | "id" | "zh"
140
150
 
151
+ /**
152
+ * Every locale {@link normalizeStreetForKeyLocale} keys streets under.
153
+ */
154
+ export const STREET_LOCALES = [
155
+ "us",
156
+ "en",
157
+ "es",
158
+ "fr",
159
+ "de",
160
+ "it",
161
+ "nl",
162
+ "pl",
163
+ "vn",
164
+ "id",
165
+ "zh",
166
+ ] as const satisfies readonly StreetLocale[]
167
+
141
168
  function foldHan(input: string): string {
142
169
  return input.normalize("NFKC").replaceAll("臺", "台").replaceAll(/\s+/g, "").toLowerCase()
143
170
  }
@@ -434,6 +461,14 @@ const CANONICAL_TYPE_WORDS: ReadonlySet<string> = new Set(
434
461
  [...US_STREET_SUFFIX_LOOKUP.values()].map((v) => v.toLowerCase())
435
462
  )
436
463
 
464
+ const ENGLISH_ORDINAL_RULES = new Intl.PluralRules("en-US", { type: "ordinal" })
465
+
466
+ const ENGLISH_ORDINAL_SUFFIX: Record<string, string> = { one: "st", two: "nd", few: "rd", other: "th" }
467
+
468
+ function ordinalOf(n: number): string {
469
+ return `${n}${ENGLISH_ORDINAL_SUFFIX[ENGLISH_ORDINAL_RULES.select(n)] ?? "th"}`
470
+ }
471
+
437
472
  /**
438
473
  * Lists the lookup keys to probe for a street span, most literal first, adding doubled-type
439
474
  * and Saint/St-swap recovery forms for `us` and `en`.
@@ -463,6 +498,29 @@ export function streetKeyVariants(street: string, locale: StreetLocale = "us"):
463
498
  }
464
499
  }
465
500
 
501
+ // NAD writes a numbered street with its ordinal suffix ("48th St")
502
+ // and NYC Open Data writes the bare number ("48 ST").
503
+ // The build keys each row as written, so the reader probes both forms.
504
+ for (const variant of variants.slice()) {
505
+ const parts = variant.split(" ")
506
+
507
+ const toggled = parts
508
+ .map((token, i) => {
509
+ // Only a number followed by a street type is an ordinal ("48 street"), not "us route 9".
510
+ if (!CANONICAL_TYPE_WORDS.has(parts[i + 1] ?? "")) return token
511
+ const bare = /^(\d+)(?:st|nd|rd|th)$/.exec(token)?.[1]
512
+
513
+ if (bare) return bare
514
+
515
+ return /^\d+$/.test(token) ? ordinalOf(Number(token)) : token
516
+ })
517
+ .join(" ") as StreetKey
518
+
519
+ if (toggled !== variant && !variants.includes(toggled)) {
520
+ variants.push(toggled)
521
+ }
522
+ }
523
+
466
524
  const preSwap = variants.slice()
467
525
 
468
526
  for (const variant of preSwap) {
package/lib/types.ts CHANGED
@@ -155,6 +155,14 @@ export interface FindPlaceQuery {
155
155
  */
156
156
  parentID?: number
157
157
 
158
+ /**
159
+ * A resolved parent place whose containment orders the rows and admits contained sub-locality
160
+ * rows the placetype filter excludes, read through the ancestors sidecar (#2804).
161
+ *
162
+ * It ranks rather than filters, so a query sets this or `parentID`.
163
+ */
164
+ parentQualifierID?: number
165
+
158
166
  /**
159
167
  * The sibling postcode for a `locality` query.
160
168
  *
@@ -18,7 +18,6 @@ import { shortCellToParentInt } from "@mailwoman/spatial"
18
18
 
19
19
  import type { UPRNLookup } from "#uprn/lookup"
20
20
  import { UPRN_COVERAGE_H3_RESOLUTION, UPRN_H3_RESOLUTION, uprnH3Cell } from "#uprn/schema"
21
-
22
21
  /**
23
22
  * The identity fold, on purpose: this probe keys on a coordinate rather than a name,
24
23
  * so there is no string folding for the builder and the probe to disagree about.
@@ -7,9 +7,9 @@
7
7
  */
8
8
 
9
9
  import { allRows } from "@mailwoman/core/utils"
10
- import { haversineKm, shortCellToInt, type GeoCoordinate, type H3Cell } from "@mailwoman/spatial"
10
+ import { haversine, shortCellToInt, type GeoCoordinate } from "@mailwoman/spatial"
11
11
  import { DatabaseClient } from "@mailwoman/sqlite/client"
12
- import { gridDisk } from "h3-js"
12
+ import { gridDisk } from "@sister.software/h3"
13
13
  import type { PathBuilderLike } from "path-ts"
14
14
 
15
15
  import type { UPRNDatabase } from "#uprn/schema"
@@ -128,13 +128,13 @@ export class UPRNLookup implements Disposable {
128
128
 
129
129
  if (closestPossibleM > Math.min(radiusM, best?.distanceM ?? radiusM)) break
130
130
 
131
- const diskCells = gridDisk(origin, ring) as string[]
131
+ const diskCells = gridDisk(origin, ring)
132
132
  const newCells: number[] = []
133
133
 
134
134
  for (const cell of diskCells) {
135
135
  if (!seenCells.has(cell)) {
136
136
  seenCells.add(cell)
137
- newCells.push(shortCellToInt(cell as H3Cell))
137
+ newCells.push(shortCellToInt(cell))
138
138
  }
139
139
  }
140
140
 
@@ -148,7 +148,7 @@ export class UPRNLookup implements Disposable {
148
148
  )
149
149
 
150
150
  for (const row of rows) {
151
- const distanceM = haversineKm(latitude, longitude, row.lat, row.lon) * 1000
151
+ const distanceM = haversine(latitude, longitude, row.lat, row.lon) * 1000
152
152
 
153
153
  if (distanceM <= radiusM && (!best || distanceM < best.distanceM)) {
154
154
  best = { uprn: row.uprn, latitude: row.lat, longitude: row.lon, distanceM }
@@ -7,10 +7,9 @@
7
7
  */
8
8
 
9
9
  import type { layerschemadatabase } from "@mailwoman/core/layers"
10
- import { shortCellToInt, type H3Cell } from "@mailwoman/spatial"
11
- import { latLngToCell } from "h3-js"
10
+ import { shortCellToInt } from "@mailwoman/spatial"
11
+ import { latLngToCell, type H3Cell } from "@sister.software/h3"
12
12
  import type { Kysely } from "kysely"
13
-
14
13
  /**
15
14
  * Resolution the `uprn` table's `h3_cell` column is keyed at, shared with the other layer spines.
16
15
  */
@@ -65,7 +64,7 @@ export interface UPRNDatabase extends layerschemadatabase {
65
64
  * This is the one derivation the builder and its consumers share so their cells cannot disagree.
66
65
  */
67
66
  export function uprnFullCell(latitude: number, longitude: number): H3Cell {
68
- return latLngToCell(latitude, longitude, UPRN_H3_RESOLUTION) as H3Cell
67
+ return latLngToCell(latitude, longitude, UPRN_H3_RESOLUTION)
69
68
  }
70
69
 
71
70
  /**
@@ -385,7 +385,7 @@ async function pairIndexIsFresh(
385
385
  return false
386
386
  }
387
387
 
388
- if (minimumPlausibleBytes !== undefined && (await statPath(dest)).size < minimumPlausibleBytes) {
388
+ if (minimumPlausibleBytes && (await statPath(dest)).size < minimumPlausibleBytes) {
389
389
  console.log(
390
390
  `rebuilding ${artifact} — ${(await statPath(dest)).size.toLocaleString()} bytes is implausibly small ` +
391
391
  `for this recipe (wrong-source clobber)`
@@ -508,8 +508,8 @@ export async function buildPairIndexOverlay(overlay: PairIndexOverlay): Promise<
508
508
  country,
509
509
  "--delta",
510
510
  String(delta),
511
- ...(transitionBeta === undefined ? [] : ["--transition-beta", String(transitionBeta)]),
512
- ...(parentDelta === undefined ? [] : ["--parent-delta", String(parentDelta)]),
511
+ ...(!transitionBeta ? [] : ["--transition-beta", String(transitionBeta)]),
512
+ ...(!parentDelta ? [] : ["--parent-delta", String(parentDelta)]),
513
513
  ...extraArgs,
514
514
  ],
515
515
  { stdio: "inherit" }
@@ -727,7 +727,7 @@ async function linkBaseModelPair(destDir: PathBuilder, digestCard: string | null
727
727
  const recipe = await readReleaseConfig()
728
728
 
729
729
  const dataRoot = dataRootPath()
730
- const digests = digestCard ? (await readWeightsCard(digestCard))?.files_md5 : undefined
730
+ const digests = digestCard ? (await readWeightsCard(digestCard))?.files_md5 : null
731
731
 
732
732
  const pair = [
733
733
  { label: "model", name: "model.onnx", override: $public.MAILWOMAN_DEV_MODEL, recipe: recipe.weights.model },
@@ -28,7 +28,7 @@
28
28
  *
29
29
  * Standalone like the segment tier, wired through the ordered `spatialTiers` list.
30
30
  */
31
- import { haversineKm } from "@mailwoman/spatial";
31
+ import { haversine } from "@mailwoman/spatial/geometries/point/distance";
32
32
  import { DatabaseClient } from "@mailwoman/sqlite/client";
33
33
  import { hasTable, prepareAll } from "#sqlite-utils";
34
34
  import { canonicalizeRouteKey, streetKeyVariants } from "#street/normalize";
@@ -137,7 +137,7 @@ function interpolateFromNeighbors(rows, n) {
137
137
  }
138
138
  if (below && above) {
139
139
  const t = (n - below.n) / (above.n - below.n);
140
- const spanM = haversineKm(below.lat, below.lon, above.lat, above.lon) * 1000;
140
+ const spanM = haversine(below.lat, below.lon, above.lat, above.lon) * 1000;
141
141
  return {
142
142
  lat: below.lat + (above.lat - below.lat) * t,
143
143
  lon: below.lon + (above.lon - below.lon) * t,
@@ -161,8 +161,8 @@ function interpolateFromNeighbors(rows, n) {
161
161
  return null;
162
162
  const lat = far.lat + (near.lat - far.lat) * t;
163
163
  const lon = far.lon + (near.lon - far.lon) * t;
164
- const pairM = haversineKm(near.lat, near.lon, far.lat, far.lon) * 1000;
165
- const overshootM = haversineKm(lat, lon, near.lat, near.lon) * 1000;
164
+ const pairM = haversine(near.lat, near.lon, far.lat, far.lon) * 1000;
165
+ const overshootM = haversine(lat, lon, near.lat, near.lon) * 1000;
166
166
  return {
167
167
  lat,
168
168
  lon,
@@ -1 +1 @@
1
- {"version":3,"file":"interpolation.js","sourceRoot":"","sources":["../../../lib/address/point/interpolation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interpolation.js","sourceRoot":"","sources":["../../../lib/address/point/interpolation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file The shared rooftop schema with an H3 spine column and the layer-interface tables.
6
+ *
7
+ * The OpenStreetMap and G-NAF rooftop builders write this shape. Readers of existing address points
8
+ * project only the columns of `address_point` in `#address/point/schema`, so the H3 spine and the
9
+ * layer-interface tables leave their query path unchanged.
10
+ */
11
+ import { type layerschemadatabase } from "@mailwoman/core/layers";
12
+ import type { Kysely } from "kysely";
13
+ import { type AddressPointTable } from "#address/point/schema";
14
+ /**
15
+ * Jitter-stable address/POI join cell.
16
+ * Matches @mailwoman/address-id.
17
+ */
18
+ export declare const ADDRESS_POINT_H3_RESOLUTION = 9;
19
+ export interface SpineAddressPointTable extends AddressPointTable {
20
+ /**
21
+ * 48-bit shortened H3 cell stored as a safe SQLite integer.
22
+ */
23
+ h3_cell: number;
24
+ }
25
+ export interface SpineAddressPointDatabase extends layerschemadatabase {
26
+ address_point: SpineAddressPointTable;
27
+ }
28
+ /**
29
+ * Positional insert order: the shared address-point fields followed by the H3 spine.
30
+ */
31
+ export declare const SPINE_ADDRESS_POINT_COLUMNS: readonly ["street_norm", "street_key", "number", "unit", "postcode", "locality_norm", "street_raw", "lat", "lon", "source", "release", "admin_code", "certified", "h3_cell"];
32
+ /**
33
+ * Create the legacy-compatible domain table plus the H3 spine and the layer-interface tables.
34
+ */
35
+ export declare function createSpineAddressPointTables(db: Kysely<SpineAddressPointDatabase>): Promise<void>;
36
+ /**
37
+ * Build after the bulk load so the spine index is not updated on every streaming insert.
38
+ */
39
+ export declare function createSpineAddressPointIndexes(db: Kysely<SpineAddressPointDatabase>): Promise<void>;
40
+ //# sourceMappingURL=spine-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine-schema.d.ts","sourceRoot":"","sources":["../../../lib/address/point/spine-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAsD,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AACrH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEpC,OAAO,EAAyB,KAAK,iBAAiB,EAA2B,MAAM,uBAAuB,CAAA;AAE9G;;;GAGG;AACH,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAE5C,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAChE;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACrE,aAAa,EAAE,sBAAsB,CAAA;CACrC;AAED;;GAEG;AACH,eAAO,MAAM,2BAA2B,8KAAiD,CAAA;AAEzF;;GAEG;AACH,wBAAsB,6BAA6B,CAAC,EAAE,EAAE,MAAM,CAAC,yBAAyB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAUxG;AAED;;GAEG;AACH,wBAAsB,8BAA8B,CAAC,EAAE,EAAE,MAAM,CAAC,yBAAyB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzG"}