@mailwoman/resolver-wof-sqlite 9.3.0 → 10.0.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 (286) hide show
  1. package/lib/address/index.ts +3 -3
  2. package/lib/address/{point.ts → point/index.ts} +55 -7
  3. package/lib/address/{point-interpolation.ts → point/interpolation.ts} +2 -3
  4. package/lib/ancestry/backfill.ts +4 -2
  5. package/lib/ancestry/index.ts +1 -2
  6. package/lib/{build-candidate.ts → build/candidate.ts} +62 -31
  7. package/lib/candidate/alias-bags.ts +68 -7
  8. package/lib/{candidate-ancestors-schema.ts → candidate/ancestors/schema.ts} +1 -1
  9. package/lib/candidate/{ancestors-sidecar.ts → ancestors/sidecar.ts} +2 -2
  10. package/lib/candidate/extract-fold.ts +91 -10
  11. package/lib/{candidate-importance.ts → candidate/importance.ts} +1 -1
  12. package/lib/{candidate-lookup.ts → candidate/lookup.ts} +13 -16
  13. package/lib/candidate/name-roles.ts +2 -2
  14. package/lib/candidate/own-name.ts +1 -0
  15. package/lib/{candidate-schema.ts → candidate/schema.ts} +2 -2
  16. package/lib/{candidate-scoring.ts → candidate/scoring.ts} +1 -1
  17. package/lib/coincident-roles.ts +2 -3
  18. package/lib/coverage-manifest-schema.ts +2 -1
  19. package/lib/currency-backfill.ts +2 -2
  20. package/lib/fst/builder.ts +1 -1
  21. package/lib/fst/deserialize-web.ts +2 -2
  22. package/lib/fst/freshness.ts +0 -1
  23. package/lib/fst/serialize.ts +9 -9
  24. package/lib/geonames/aliases.ts +1 -7
  25. package/lib/geonames/postal.ts +1 -7
  26. package/lib/index.ts +18 -25
  27. package/lib/interpolation.ts +1 -1
  28. package/lib/lookup.ts +6 -5
  29. package/lib/place-importance-schema.ts +1 -9
  30. package/lib/poi/lookup.ts +1 -2
  31. package/lib/postal/{city-alias-lookup.ts → city/alias/lookup.ts} +1 -1
  32. package/lib/postal/index.ts +3 -3
  33. package/lib/primary-preference.ts +15 -5
  34. package/lib/reverse.ts +1 -2
  35. package/lib/search-fetch.ts +1 -2
  36. package/lib/{sqlite-utils.ts → sqlite/utils.ts} +0 -1
  37. package/lib/street/{centroid.ts → centroid/index.ts} +2 -2
  38. package/lib/street/index.ts +4 -4
  39. package/lib/street/{morphology-fst-builder.ts → morphology/fst/builder.ts} +8 -2
  40. package/lib/street/{morphology-fst-loader.ts → morphology/fst/loader.ts} +1 -1
  41. package/lib/street/normalize.ts +42 -1
  42. package/lib/uprn/lookup.ts +1 -2
  43. package/lib/weights-overlay-linker.ts +5 -4
  44. package/out/address/index.d.ts +3 -3
  45. package/out/address/index.d.ts.map +1 -1
  46. package/out/address/index.js +3 -3
  47. package/out/address/index.js.map +1 -1
  48. package/out/address/{point.d.ts → point/index.d.ts} +5 -3
  49. package/out/address/point/index.d.ts.map +1 -0
  50. package/out/address/{point.js → point/index.js} +38 -7
  51. package/out/address/point/index.js.map +1 -0
  52. package/out/address/{point-interpolation.d.ts → point/interpolation.d.ts} +3 -3
  53. package/out/address/point/interpolation.d.ts.map +1 -0
  54. package/out/address/{point-interpolation.js → point/interpolation.js} +1 -1
  55. package/out/address/point/interpolation.js.map +1 -0
  56. package/out/address/{point-schema.d.ts → point/schema.d.ts} +1 -1
  57. package/out/address/point/schema.d.ts.map +1 -0
  58. package/out/address/{point-schema.js → point/schema.js} +1 -1
  59. package/out/address/point/schema.js.map +1 -0
  60. package/out/ancestry/backfill.d.ts.map +1 -1
  61. package/out/ancestry/backfill.js +4 -2
  62. package/out/ancestry/backfill.js.map +1 -1
  63. package/out/ancestry/index.d.ts.map +1 -1
  64. package/out/ancestry/index.js +1 -1
  65. package/out/ancestry/index.js.map +1 -1
  66. package/out/{build-candidate.d.ts → build/candidate.d.ts} +12 -4
  67. package/out/build/candidate.d.ts.map +1 -0
  68. package/out/{build-candidate.js → build/candidate.js} +39 -24
  69. package/out/build/candidate.js.map +1 -0
  70. package/out/{build-slim.d.ts → build/slim.d.ts} +1 -1
  71. package/out/build/slim.d.ts.map +1 -0
  72. package/out/{build-slim.js → build/slim.js} +1 -1
  73. package/out/build/slim.js.map +1 -0
  74. package/out/candidate/alias-bags.d.ts +10 -2
  75. package/out/candidate/alias-bags.d.ts.map +1 -1
  76. package/out/candidate/alias-bags.js +49 -5
  77. package/out/candidate/alias-bags.js.map +1 -1
  78. package/out/{candidate-ancestors-schema.d.ts → candidate/ancestors/schema.d.ts} +2 -2
  79. package/out/candidate/ancestors/schema.d.ts.map +1 -0
  80. package/out/{candidate-ancestors-schema.js → candidate/ancestors/schema.js} +1 -1
  81. package/out/candidate/ancestors/schema.js.map +1 -0
  82. package/out/candidate/{ancestors-sidecar.d.ts → ancestors/sidecar.d.ts} +2 -2
  83. package/out/candidate/ancestors/sidecar.d.ts.map +1 -0
  84. package/out/candidate/{ancestors-sidecar.js → ancestors/sidecar.js} +2 -2
  85. package/out/candidate/ancestors/sidecar.js.map +1 -0
  86. package/out/candidate/extract-fold.d.ts +12 -3
  87. package/out/candidate/extract-fold.d.ts.map +1 -1
  88. package/out/candidate/extract-fold.js +57 -8
  89. package/out/candidate/extract-fold.js.map +1 -1
  90. package/out/{candidate-fts.d.ts → candidate/fts.d.ts} +1 -1
  91. package/out/candidate/fts.d.ts.map +1 -0
  92. package/out/{candidate-fts.js → candidate/fts.js} +1 -1
  93. package/out/candidate/fts.js.map +1 -0
  94. package/out/{candidate-importance.d.ts → candidate/importance.d.ts} +1 -1
  95. package/out/candidate/importance.d.ts.map +1 -0
  96. package/out/{candidate-importance.js → candidate/importance.js} +1 -1
  97. package/out/candidate/importance.js.map +1 -0
  98. package/out/{candidate-lookup.d.ts → candidate/lookup.d.ts} +7 -7
  99. package/out/candidate/lookup.d.ts.map +1 -0
  100. package/out/{candidate-lookup.js → candidate/lookup.js} +13 -11
  101. package/out/candidate/lookup.js.map +1 -0
  102. package/out/candidate/name-roles.d.ts +1 -1
  103. package/out/candidate/name-roles.d.ts.map +1 -1
  104. package/out/candidate/name-roles.js +1 -1
  105. package/out/candidate/name-roles.js.map +1 -1
  106. package/out/candidate/own-name.d.ts.map +1 -1
  107. package/out/candidate/own-name.js +1 -0
  108. package/out/candidate/own-name.js.map +1 -1
  109. package/out/{candidate-schema.d.ts → candidate/schema.d.ts} +3 -3
  110. package/out/candidate/schema.d.ts.map +1 -0
  111. package/out/{candidate-schema.js → candidate/schema.js} +2 -2
  112. package/out/candidate/schema.js.map +1 -0
  113. package/out/{candidate-scoring.d.ts → candidate/scoring.d.ts} +1 -1
  114. package/out/candidate/scoring.d.ts.map +1 -0
  115. package/out/{candidate-scoring.js → candidate/scoring.js} +2 -2
  116. package/out/candidate/scoring.js.map +1 -0
  117. package/out/capitals.d.ts.map +1 -1
  118. package/out/coincident-roles.d.ts +1 -1
  119. package/out/coincident-roles.d.ts.map +1 -1
  120. package/out/coincident-roles.js +1 -1
  121. package/out/coincident-roles.js.map +1 -1
  122. package/out/convention/index.d.ts.map +1 -1
  123. package/out/convention/index.js.map +1 -1
  124. package/out/coverage-manifest-schema.d.ts.map +1 -1
  125. package/out/coverage-manifest-schema.js +2 -1
  126. package/out/coverage-manifest-schema.js.map +1 -1
  127. package/out/currency-backfill.d.ts +2 -2
  128. package/out/currency-backfill.d.ts.map +1 -1
  129. package/out/env.d.ts +1 -1
  130. package/out/fst/autocomplete.js.map +1 -1
  131. package/out/fst/builder.d.ts.map +1 -1
  132. package/out/fst/builder.js +1 -1
  133. package/out/fst/builder.js.map +1 -1
  134. package/out/fst/deserialize-web.js +2 -2
  135. package/out/fst/deserialize-web.js.map +1 -1
  136. package/out/fst/freshness.d.ts +0 -1
  137. package/out/fst/freshness.d.ts.map +1 -1
  138. package/out/fst/freshness.js +0 -1
  139. package/out/fst/freshness.js.map +1 -1
  140. package/out/fst/matcher.d.ts.map +1 -1
  141. package/out/fst/serialize.js +9 -9
  142. package/out/fst/serialize.js.map +1 -1
  143. package/out/fts/index.js.map +1 -1
  144. package/out/geonames/aliases.d.ts +0 -5
  145. package/out/geonames/aliases.d.ts.map +1 -1
  146. package/out/geonames/aliases.js +1 -6
  147. package/out/geonames/aliases.js.map +1 -1
  148. package/out/geonames/postal.d.ts +0 -6
  149. package/out/geonames/postal.d.ts.map +1 -1
  150. package/out/geonames/postal.js +1 -6
  151. package/out/geonames/postal.js.map +1 -1
  152. package/out/index.d.ts +18 -19
  153. package/out/index.d.ts.map +1 -1
  154. package/out/index.js +13 -14
  155. package/out/index.js.map +1 -1
  156. package/out/interpolation.d.ts +1 -1
  157. package/out/interpolation.d.ts.map +1 -1
  158. package/out/lookup.d.ts +2 -2
  159. package/out/lookup.d.ts.map +1 -1
  160. package/out/lookup.js +4 -4
  161. package/out/lookup.js.map +1 -1
  162. package/out/nsul/lookup.d.ts.map +1 -1
  163. package/out/place-importance-schema.d.ts +0 -1
  164. package/out/place-importance-schema.d.ts.map +1 -1
  165. package/out/place-importance-schema.js +1 -2
  166. package/out/place-importance-schema.js.map +1 -1
  167. package/out/poi/lookup.d.ts.map +1 -1
  168. package/out/poi/lookup.js +1 -1
  169. package/out/poi/lookup.js.map +1 -1
  170. package/out/poi/schema.d.ts.map +1 -1
  171. package/out/postal/{city-alias-lookup.d.ts → city/alias/lookup.d.ts} +2 -2
  172. package/out/postal/city/alias/lookup.d.ts.map +1 -0
  173. package/out/postal/{city-alias-lookup.js → city/alias/lookup.js} +1 -1
  174. package/out/postal/city/alias/lookup.js.map +1 -0
  175. package/out/postal/{city-alias-schema.d.ts → city/alias/schema.d.ts} +1 -1
  176. package/out/postal/city/alias/schema.d.ts.map +1 -0
  177. package/out/postal/{city-alias-schema.js → city/alias/schema.js} +1 -1
  178. package/out/postal/city/alias/schema.js.map +1 -0
  179. package/out/postal/{city-candidate-schema.d.ts → city/candidate-schema.d.ts} +1 -1
  180. package/out/postal/city/candidate-schema.d.ts.map +1 -0
  181. package/out/postal/{city-candidate-schema.js → city/candidate-schema.js} +1 -1
  182. package/out/postal/city/candidate-schema.js.map +1 -0
  183. package/out/postal/index.d.ts +3 -3
  184. package/out/postal/index.js +3 -3
  185. package/out/postcode-point-lookup.d.ts.map +1 -1
  186. package/out/primary-preference.d.ts +3 -3
  187. package/out/primary-preference.d.ts.map +1 -1
  188. package/out/primary-preference.js +12 -4
  189. package/out/primary-preference.js.map +1 -1
  190. package/out/reverse.d.ts.map +1 -1
  191. package/out/reverse.js +1 -1
  192. package/out/reverse.js.map +1 -1
  193. package/out/search-fetch.d.ts.map +1 -1
  194. package/out/search-fetch.js +1 -1
  195. package/out/search-fetch.js.map +1 -1
  196. package/out/{sqlite-convention-source.d.ts → sqlite/convention-source.d.ts} +1 -1
  197. package/out/sqlite/convention-source.d.ts.map +1 -0
  198. package/out/{sqlite-convention-source.js → sqlite/convention-source.js} +1 -1
  199. package/out/sqlite/convention-source.js.map +1 -0
  200. package/out/{sqlite-utils.d.ts → sqlite/utils.d.ts} +1 -2
  201. package/out/sqlite/utils.d.ts.map +1 -0
  202. package/out/{sqlite-utils.js → sqlite/utils.js} +1 -4
  203. package/out/sqlite/utils.js.map +1 -0
  204. package/out/street/{centroid.d.ts → centroid/index.d.ts} +2 -2
  205. package/out/street/centroid/index.d.ts.map +1 -0
  206. package/out/street/{centroid.js → centroid/index.js} +1 -1
  207. package/out/street/centroid/index.js.map +1 -0
  208. package/out/street/{centroid-schema.d.ts → centroid/schema.d.ts} +1 -1
  209. package/out/street/centroid/schema.d.ts.map +1 -0
  210. package/out/street/{centroid-schema.js → centroid/schema.js} +1 -1
  211. package/out/street/centroid/schema.js.map +1 -0
  212. package/out/street/index.d.ts +4 -4
  213. package/out/street/index.d.ts.map +1 -1
  214. package/out/street/index.js +4 -4
  215. package/out/street/index.js.map +1 -1
  216. package/out/street/{morphology-fst-builder.d.ts → morphology/fst/builder.d.ts} +1 -1
  217. package/out/street/morphology/fst/builder.d.ts.map +1 -0
  218. package/out/street/{morphology-fst-builder.js → morphology/fst/builder.js} +8 -3
  219. package/out/street/morphology/fst/builder.js.map +1 -0
  220. package/out/street/{morphology-fst-loader.d.ts → morphology/fst/loader.d.ts} +1 -1
  221. package/out/street/morphology/fst/loader.d.ts.map +1 -0
  222. package/out/street/{morphology-fst-loader.js → morphology/fst/loader.js} +2 -2
  223. package/out/street/morphology/fst/loader.js.map +1 -0
  224. package/out/street/name-lookup.d.ts.map +1 -1
  225. package/out/street/name-lookup.js.map +1 -1
  226. package/out/street/normalize.d.ts +13 -1
  227. package/out/street/normalize.d.ts.map +1 -1
  228. package/out/street/normalize.js +37 -0
  229. package/out/street/normalize.js.map +1 -1
  230. package/out/street/segment-schema.d.ts.map +1 -1
  231. package/out/uprn/lookup.d.ts.map +1 -1
  232. package/out/uprn/lookup.js +1 -1
  233. package/out/uprn/lookup.js.map +1 -1
  234. package/out/weights-overlay-linker.d.ts.map +1 -1
  235. package/out/weights-overlay-linker.js +4 -3
  236. package/out/weights-overlay-linker.js.map +1 -1
  237. package/package.json +64 -64
  238. package/out/address/point-interpolation.d.ts.map +0 -1
  239. package/out/address/point-interpolation.js.map +0 -1
  240. package/out/address/point-schema.d.ts.map +0 -1
  241. package/out/address/point-schema.js.map +0 -1
  242. package/out/address/point.d.ts.map +0 -1
  243. package/out/address/point.js.map +0 -1
  244. package/out/build-candidate.d.ts.map +0 -1
  245. package/out/build-candidate.js.map +0 -1
  246. package/out/build-slim.d.ts.map +0 -1
  247. package/out/build-slim.js.map +0 -1
  248. package/out/candidate/ancestors-sidecar.d.ts.map +0 -1
  249. package/out/candidate/ancestors-sidecar.js.map +0 -1
  250. package/out/candidate-ancestors-schema.d.ts.map +0 -1
  251. package/out/candidate-ancestors-schema.js.map +0 -1
  252. package/out/candidate-fts.d.ts.map +0 -1
  253. package/out/candidate-fts.js.map +0 -1
  254. package/out/candidate-importance.d.ts.map +0 -1
  255. package/out/candidate-importance.js.map +0 -1
  256. package/out/candidate-lookup.d.ts.map +0 -1
  257. package/out/candidate-lookup.js.map +0 -1
  258. package/out/candidate-schema.d.ts.map +0 -1
  259. package/out/candidate-schema.js.map +0 -1
  260. package/out/candidate-scoring.d.ts.map +0 -1
  261. package/out/candidate-scoring.js.map +0 -1
  262. package/out/postal/city-alias-lookup.d.ts.map +0 -1
  263. package/out/postal/city-alias-lookup.js.map +0 -1
  264. package/out/postal/city-alias-schema.d.ts.map +0 -1
  265. package/out/postal/city-alias-schema.js.map +0 -1
  266. package/out/postal/city-candidate-schema.d.ts.map +0 -1
  267. package/out/postal/city-candidate-schema.js.map +0 -1
  268. package/out/sqlite-convention-source.d.ts.map +0 -1
  269. package/out/sqlite-convention-source.js.map +0 -1
  270. package/out/sqlite-utils.d.ts.map +0 -1
  271. package/out/sqlite-utils.js.map +0 -1
  272. package/out/street/centroid-schema.d.ts.map +0 -1
  273. package/out/street/centroid-schema.js.map +0 -1
  274. package/out/street/centroid.d.ts.map +0 -1
  275. package/out/street/centroid.js.map +0 -1
  276. package/out/street/morphology-fst-builder.d.ts.map +0 -1
  277. package/out/street/morphology-fst-builder.js.map +0 -1
  278. package/out/street/morphology-fst-loader.d.ts.map +0 -1
  279. package/out/street/morphology-fst-loader.js.map +0 -1
  280. /package/lib/address/{point-schema.ts → point/schema.ts} +0 -0
  281. /package/lib/{build-slim.ts → build/slim.ts} +0 -0
  282. /package/lib/{candidate-fts.ts → candidate/fts.ts} +0 -0
  283. /package/lib/postal/{city-alias-schema.ts → city/alias/schema.ts} +0 -0
  284. /package/lib/postal/{city-candidate-schema.ts → city/candidate-schema.ts} +0 -0
  285. /package/lib/{sqlite-convention-source.ts → sqlite/convention-source.ts} +0 -0
  286. /package/lib/street/{centroid-schema.ts → centroid/schema.ts} +0 -0
@@ -4,6 +4,6 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  */
6
6
 
7
- export * from "#address/point"
8
- export * from "#address/point-interpolation"
9
- export * from "#address/point-schema"
7
+ export * from "#address/point/index"
8
+ export * from "#address/point/interpolation"
9
+ export * from "#address/point/schema"
@@ -15,13 +15,15 @@
15
15
  * scopes missed). Multiple hits return the first by rowid — unit siblings share the building coord.
16
16
  */
17
17
 
18
- import type { AddressPointHit, AddressPointLookup } from "@mailwoman/resolver"
18
+ import type { AddressPointHit, AddressPointLookup } from "@mailwoman/core/resolver"
19
19
  import { DatabaseClient } from "@mailwoman/sqlite/client"
20
20
 
21
- import type { AddressPointDatabase, AddressPointTable } from "#address/point-schema"
21
+ import type { AddressPointDatabase, AddressPointTable } from "#address/point/schema"
22
22
  import { hasTable, prepareGet, type PreparedGet } from "#sqlite-utils"
23
23
  import {
24
+ normalizeHouseNumberForKey,
24
25
  normalizeLocalityForKey,
26
+ normalizeLocalityForKeyLocale,
25
27
  type NameKey,
26
28
  type StreetKey,
27
29
  streetKeyVariants,
@@ -53,6 +55,13 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
53
55
  | PreparedGet<[postcode: string, locality: NameKey, street: StreetKey, number: string], AddressPointRow>
54
56
  | undefined
55
57
  readonly #byLocality: PreparedGet<[locality: NameKey, street: StreetKey, number: string], AddressPointRow> | undefined
58
+ /**
59
+ * The scope key matched by its TAIL — a `zh` query that names the 鄉鎮市區 without its 縣市. Narrowed by the (street,
60
+ * number) index first, so the LIKE walks the handful of rows that share the pair.
61
+ */
62
+ readonly #byLocalityTail:
63
+ | PreparedGet<[street: StreetKey, number: string, tailPattern: string], AddressPointRow>
64
+ | undefined
56
65
  readonly #byBbox:
57
66
  | PreparedGet<
58
67
  [street: StreetKey, number: string, minLat: number, maxLat: number, minLon: number, maxLon: number],
@@ -102,6 +111,12 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
102
111
  `SELECT ${SELECT_COLS} FROM address_point
103
112
  WHERE street_norm = ? AND number = ? AND lat BETWEEN ? AND ? AND lon BETWEEN ? AND ? LIMIT 1`
104
113
  )
114
+
115
+ this.#byLocalityTail = prepareGet(
116
+ this.#db,
117
+ `SELECT ${SELECT_COLS} FROM address_point
118
+ WHERE street_norm = ? AND number = ? AND locality_norm LIKE ? LIMIT 1`
119
+ )
105
120
  }
106
121
  }
107
122
 
@@ -110,20 +125,33 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
110
125
  number: string
111
126
  postcode?: string
112
127
  locality?: string
128
+ region?: string
129
+ subregion?: string
113
130
  bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
114
131
  }): AddressPointHit | null {
115
132
  if (!this.#byPostcode || !this.#byLocality || !this.#byBbox) return null
116
- const number = query.number.trim().toLowerCase()
133
+ const number = normalizeHouseNumberForKey(query.number, this.#locale)
117
134
 
118
135
  if (!number) return null
119
136
 
137
+ // A `zh` extract scopes a point by 縣市 + 鄉鎮市區, the pair the parse tags `region` + `subregion`; the Taiwanese
138
+ // register carries no postcode and the parse no `locality`. The pair becomes the locality key here, on the
139
+ // reader that was built with the same fold, and a Latin extract never sees the two fields. A line that names
140
+ // only the 鄉鎮市區 (`中和區中興街281號`, 14.7% of the TW board) matches the stored pair by its tail instead.
141
+ const scoped =
142
+ this.#locale === "zh" && !query.locality && query.subregion
143
+ ? query.region
144
+ ? { ...query, locality: `${query.region}${query.subregion}` }
145
+ : { ...query, localityTail: normalizeLocalityForKeyLocale(query.subregion, "zh") }
146
+ : query
147
+
120
148
  // Key-variant ladder (see `streetKeyVariants`): the literal key first, then the doubled-type
121
149
  // collapse and the saint↔st register swap — each variant runs the FULL number ladder below, and
122
150
  // the first variant to answer wins, so an attested literal key is never second-guessed.
123
151
  let row: AddressPointRow | undefined
124
152
 
125
153
  for (const streetNorm of streetKeyVariants(query.street, streetLocaleForSurface(query.street, this.#locale))) {
126
- row = this.#findForKey(streetNorm, number, query)
154
+ row = this.#findForKey(streetNorm, number, scoped)
127
155
 
128
156
  if (row) break
129
157
  }
@@ -152,6 +180,7 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
152
180
  number: string
153
181
  postcode?: string
154
182
  locality?: string
183
+ localityTail?: NameKey
155
184
  bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
156
185
  }
157
186
  ) {
@@ -186,6 +215,19 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
186
215
  }
187
216
  }
188
217
 
218
+ // Sub-number fallback for the Taiwanese register: `14之12` is building 12 off number 14, `30附40` an attached
219
+ // number, stored as number `14` / `30` with the rest in `unit`. A query that kept the pair on the number span
220
+ // falls to the base number — the same adjacent-parcel approximation the letter-suffix rung makes, priced the
221
+ // same: on the 2,000-row served read the rows answered this way sit a median 256 m and at most 1.0 km from the
222
+ // building.
223
+ if (!row && this.#locale === "zh") {
224
+ const base = /^(\d+)(?:[之附]\d+)+$/u.exec(number)?.[1]
225
+
226
+ if (base) {
227
+ row = this.#probe(streetNorm, base, query)
228
+ }
229
+ }
230
+
189
231
  return row
190
232
  }
191
233
 
@@ -199,6 +241,7 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
199
241
  query: {
200
242
  postcode?: string
201
243
  locality?: string
244
+ localityTail?: NameKey
202
245
  bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
203
246
  }
204
247
  ): AddressPointRow | undefined {
@@ -226,6 +269,11 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
226
269
  row = this.#byLocality!(this.#localityKey(query.locality), streetNorm, number)
227
270
  }
228
271
 
272
+ if (!row && query.localityTail && this.#byLocalityTail) {
273
+ // A Han key carries no `%` or `_`, so the tail is a literal suffix pattern.
274
+ row = this.#byLocalityTail(streetNorm, number, `%${query.localityTail}`)
275
+ }
276
+
229
277
  // Bbox fall-through (#247): the point carries no postcode/locality of its own, but its coordinate falls
230
278
  // inside the resolved locality's box. Only reached when the scoped probes missed AND a bbox was supplied.
231
279
  if (!row && query.bbox) {
@@ -248,9 +296,9 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
248
296
  * 13e Arrondissement" and "Paris" both hit; a no-op for every other locale.
249
297
  */
250
298
  #localityKey(locality: string): NameKey {
251
- return this.#locale === "fr"
252
- ? stripArrondissement(normalizeLocalityForKey(locality))
253
- : normalizeLocalityForKey(locality)
299
+ if (this.#locale === "fr") return stripArrondissement(normalizeLocalityForKey(locality))
300
+
301
+ return normalizeLocalityForKeyLocale(locality, this.#locale)
254
302
  }
255
303
 
256
304
  /**
@@ -33,15 +33,14 @@
33
33
  * Standalone like the segment tier — core wiring rides the Phase 2 ordered `spatialTiers` list.
34
34
  */
35
35
 
36
- import type { InterpolationLookup } from "@mailwoman/resolver"
36
+ import type { InterpolationLookup } from "@mailwoman/core/resolver"
37
37
  import { haversineKm } from "@mailwoman/spatial"
38
38
  import { DatabaseClient } from "@mailwoman/sqlite/client"
39
39
 
40
- import type { AddressPointDatabase } from "#address/point-schema"
40
+ import type { AddressPointDatabase } from "#address/point/schema"
41
41
  import type { InterpolatedHit, InterpolationQuery, StreetInterpolator } from "#interpolation"
42
42
  import { hasTable, prepareAll, type PreparedAll } from "#sqlite-utils"
43
43
  import { canonicalizeRouteKey, type RouteKey, streetKeyVariants } from "#street/normalize"
44
-
45
44
  /**
46
45
  * Extrapolation cap for a single-sided bracket: at most one pair-span beyond the nearest known point (`t = 2`). Past
47
46
  * it, the two-point line carries no evidence about the query number.
@@ -45,10 +45,10 @@
45
45
  * nothing.
46
46
  */
47
47
 
48
- import { readDirectoryEntries } from "@mailwoman/core/fs/readers"
49
48
  import { readWOFFeature } from "@mailwoman/core/resources/whosonfirst"
50
49
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
51
50
  import { join, resolvePath, type PathBuilderLike } from "path-ts"
51
+ import { Globerator } from "spliterator/node/fs"
52
52
 
53
53
  import type { WOFDatabase } from "#schema"
54
54
 
@@ -88,7 +88,9 @@ export async function discoverAdminDataRoots(reposRoot: PathBuilderLike): Promis
88
88
  let names: string[]
89
89
 
90
90
  try {
91
- names = (await readDirectoryEntries(dir)).filter((e) => e.isDirectory()).map((e) => e.name)
91
+ names = (await Globerator.from("*", { cwd: dir, withFileTypes: true, onlyFiles: false }).toArray())
92
+ .filter((entry) => entry.isDirectory())
93
+ .map((entry) => entry.name)
92
94
  } catch {
93
95
  return
94
96
  }
@@ -15,10 +15,9 @@
15
15
  * geocoding always does).
16
16
  */
17
17
 
18
+ import { allRows } from "@mailwoman/core/utils"
18
19
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
19
20
 
20
- import { allRows } from "#sqlite-utils"
21
-
22
21
  /**
23
22
  * WOF placetype → containment depth, coarsest = 1. Higher = finer. Placetypes we never resolve (continent, empire, …)
24
23
  * map to 0 and sort last. NOT the same table as the FST's `PLACETYPE_ORDER` (fst-serialize.ts) — that one is a
@@ -44,20 +44,20 @@ import { pathExists } from "@mailwoman/core/fs/readers"
44
44
  import { removePath } from "@mailwoman/core/fs/writers"
45
45
  import { DatabaseClient } from "@mailwoman/sqlite/client"
46
46
 
47
- import { createCandidateFTS } from "#candidate-fts"
48
- import { IMPORTANCE_JOIN_RADIUS_KM, loadImportanceIndex } from "#candidate-importance"
49
- import {
50
- CANDIDATE_COLUMNS,
51
- createCandidateStagingTables,
52
- createCandidateTable,
53
- type CandidateDatabase,
54
- } from "#candidate-schema"
55
47
  import { explodeAliasBags } from "#candidate/alias-bags"
56
- import { buildAncestorsSidecar } from "#candidate/ancestors-sidecar"
48
+ import { buildAncestorsSidecar } from "#candidate/ancestors/sidecar"
57
49
  import { stageCountryDisplayNames } from "#candidate/country-display-names"
58
50
  import { foldExtract } from "#candidate/extract-fold"
51
+ import { createCandidateFTS } from "#candidate/fts"
52
+ import { IMPORTANCE_JOIN_RADIUS_KM, loadImportanceIndex } from "#candidate/importance"
59
53
  import { GLOSS_KEY_THRESHOLD, stampNameRoles } from "#candidate/name-roles"
60
54
  import type { PlaceAttrs } from "#candidate/place-attrs"
55
+ import {
56
+ CANDIDATE_COLUMNS,
57
+ createCandidateStagingTables,
58
+ createCandidateTable,
59
+ type CandidateDatabase,
60
+ } from "#candidate/schema"
61
61
  import { createCapitalTable } from "#capital-schema"
62
62
  import type { CapitalPoint } from "#capitals"
63
63
  import { resurrectCurrencyHoles } from "#currency-backfill"
@@ -97,9 +97,11 @@ export interface BuildCandidateOptions {
97
97
  /**
98
98
  * Optional LOCALITY extracts (`spr` rows with `placetype='locality'` + real coords, e.g. localities-nz-linz.db — the
99
99
  * #1564 NZ suburb tier) — folded through the same extract loop as the postcode extracts, staged as `locality`
100
- * candidate rows with no region scope and UNMEASURED population (`neg_rank 0`: a extract row ranks behind any
101
- * populated namesake and wins only where its key is the answer). Each extract's `names` table folds as aliases,
102
- * `is_primary = 0`, same as the delivery-city pass.
100
+ * candidate rows with UNMEASURED population (`neg_rank 0`: a extract row ranks behind any populated namesake and wins
101
+ * only where its key is the answer). Each extract's `names` table folds as aliases, `is_primary = 0`, same as the
102
+ * delivery-city pass. An extract whose `ancestors` table names an admin region for a row gives that row the region's
103
+ * scope (`region_id`) plus closure rows for the region and the region's own chain above it; an extract without one
104
+ * stays unscoped, as before.
103
105
  */
104
106
  localities?: string[]
105
107
  /**
@@ -151,6 +153,12 @@ export interface BuildCandidateResult {
151
153
  places: number
152
154
  primaries: number
153
155
  aliases: number
156
+ /**
157
+ * Region aliases refused because another same-country region holds the name as its official name (three pairs in the
158
+ * admin artifact: `新竹市`, `嘉義市`, `충청남도`). Zero on a source without a `names` table means the rule had nothing to read,
159
+ * not that no pair exists.
160
+ */
161
+ regionOfficialRefused: number
154
162
  abbrevs: number
155
163
  postcodes: number
156
164
  /**
@@ -223,7 +231,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
223
231
 
224
232
  await createCandidateStagingTables(kdb)
225
233
 
226
- // --- compact code maps (country/placetype small int, shrinks the clustered key). The ids are
234
+ // Build compact country and placetype code maps for clustered keys.
227
235
  // assigned here; the rows are bulk-inserted via kdb once the passes have discovered every code. ---
228
236
  const ccodes = new Map<string, number>()
229
237
  const ptcodes = new Map<string, number>()
@@ -252,7 +260,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
252
260
  return id
253
261
  }
254
262
 
255
- // --- importance source (#28): loaded BEFORE pass 1, which is the only pass that sees a place's
263
+ // Load the importance source before the only pass that sees each place.
256
264
  // name/country/placetype/centroid together. Absent → every row's `importance` stays NULL. ---
257
265
  let importance: ReturnType<typeof loadImportanceIndex> | undefined
258
266
 
@@ -271,7 +279,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
271
279
  progress("importance", "no score source given — `importance` will be NULL on every row")
272
280
  }
273
281
 
274
- // --- region_id per place (its region-tier ancestor) for same-name disambiguation ---
282
+ // Derive each place's region ancestor for same-name disambiguation.
275
283
  progress("region", "loading region ancestry")
276
284
  const regionOf = new Map<number, number>()
277
285
  let multiRegion = 0
@@ -307,7 +315,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
307
315
  // the column COUNT can't drift; the positional run() args below MUST stay in CANDIDATE_COLUMNS order.
308
316
  const insStage = kdb.prepare(`INSERT INTO cand_stage VALUES (${CANDIDATE_COLUMNS.map(() => "?").join(", ")})`)
309
317
 
310
- // --- pass 1: primaries (and the per-place attrs the alias/abbrev passes reuse) ---
318
+ // Stage primary names and attributes reused by later alias passes.
311
319
  progress("primaries", "indexing place names")
312
320
  const attrs = new Map<number, PlaceAttrs>()
313
321
  let nPrim = 0
@@ -416,7 +424,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
416
424
  )
417
425
  }
418
426
 
419
- // --- pass 1b: country surfaces across scripts (#1678 thread 1) — see stageCountryDisplayNames ---
427
+ // Stage country display names across scripts.
420
428
  // Never a silent zero: a runtime whose ICU lacks these locales degrades to fewer surfaces, and the count is how a
421
429
  // reader tells that apart from the pass not having run.
422
430
  progress(
@@ -430,7 +438,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
430
438
  }).toLocaleString()} country surfaces`
431
439
  )
432
440
 
433
- // --- pass 1c: cross-source currency backfill (#1737 resurrectCurrencyHoles owns the checks). Runs BEFORE
441
+ // Backfill cross-source currency before later candidate passes.
434
442
  // the alias pass so a resurrected place's alt names explode like any primary's. ---
435
443
  if (opts.currencyBackfill) {
436
444
  const nBackfill = await resurrectCurrencyHoles({
@@ -455,12 +463,23 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
455
463
  progress("currency-backfill", "not configured — deprecated-no-successor holes stay dead (#1737)")
456
464
  }
457
465
 
458
- // --- pass 2: distinct normalized aliases from place_search.alt_names (explodeAliasBags owns the loop) ---
466
+ // Stage distinct normalized aliases from alternate names.
459
467
  progress("aliases", "exploding alias bags")
460
- const { nAlias, keyCounts } = explodeAliasBags(src, kdb, attrs, stageRow)
461
- progress("aliases", `${nAlias.toLocaleString()} aliases`)
462
468
 
463
- // --- pass 3: region abbreviations (place_abbr) ---
469
+ const { nAlias, keyCounts, regionOfficialRefused } = explodeAliasBags(src, kdb, attrs, stageRow, {
470
+ regionPlacetypeID: ptID("region"),
471
+ ccID,
472
+ })
473
+
474
+ progress(
475
+ "aliases",
476
+ `${nAlias.toLocaleString()} aliases` +
477
+ (regionOfficialRefused
478
+ ? `; ${regionOfficialRefused.toLocaleString()} region aliases refused as another region's official name`
479
+ : "")
480
+ )
481
+
482
+ // Stage region abbreviations.
464
483
  let nAbbr = 0
465
484
  kdb.exec("BEGIN")
466
485
 
@@ -479,7 +498,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
479
498
  kdb.exec("COMMIT")
480
499
  progress("abbrevs", `${nAbbr.toLocaleString()} abbrevs`)
481
500
 
482
- // --- pass 3b: name roles (#1730 prototype — stampNameRoles owns the detectors) ---
501
+ // Stamp each name's semantic role.
483
502
  // Independent of the sidecar below: this writes `cand_stage.name_role`, that writes the ancestor and
484
503
  // interval tables, and neither reads the other's output. Ordered by label only.
485
504
  const roles = stampNameRoles({
@@ -493,10 +512,10 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
493
512
  progress,
494
513
  })
495
514
 
496
- // --- pass 3c: the ancestors sidecar (candidate-ancestors-schema.ts owns the encoding decision) ---
515
+ // Stage the encoded ancestors sidecar.
497
516
  const sidecar = await buildAncestorsSidecar({ src, out: kdb, attrs, ptID, progress })
498
517
 
499
- // --- pass 4 + 4b: postcode and locality extracts (foldExtract owns the per-extract loop) ---
518
+ // Fold postcode and locality extracts into candidate records.
500
519
  let nPostcode = 0
501
520
  let nPostcodeAlias = 0
502
521
 
@@ -520,6 +539,8 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
520
539
  }
521
540
 
522
541
  let nLocality = 0
542
+ let nLocalityScoped = 0
543
+ let nLocalityAncestor = 0
523
544
 
524
545
  for (const locDB of opts.localities ?? []) {
525
546
  const folded = foldExtract({
@@ -529,17 +550,26 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
529
550
  ccID,
530
551
  ptID,
531
552
  stageRow,
553
+ attrs,
532
554
  progress,
533
555
  })
534
556
 
535
557
  nLocality += folded.primaries
558
+ nLocalityScoped += folded.scoped
559
+ nLocalityAncestor += folded.ancestorRows
536
560
  }
537
561
 
538
562
  if (nLocality > 0) {
539
- progress("localities", `${nLocality.toLocaleString()} extract localities folded`)
563
+ progress(
564
+ "localities",
565
+ `${nLocality.toLocaleString()} extract localities folded` +
566
+ (nLocalityScoped
567
+ ? `; ${nLocalityScoped.toLocaleString()} carry a region scope (${nLocalityAncestor.toLocaleString()} closure rows)`
568
+ : "")
569
+ )
540
570
  }
541
571
 
542
- // --- code dictionaries: typed batch inserts via kdb (a few hundred rows — Kysely is clean here) ---
572
+ // Insert the small typed code dictionaries through Kysely.
543
573
  if (ccodes.size) {
544
574
  await kdb
545
575
  .insertInto("country_codes")
@@ -554,7 +584,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
554
584
  .execute()
555
585
  }
556
586
 
557
- // --- capital-status reference (#1880's distribution home) — cold, small (~3.7k rows), typed ---
587
+ // Insert the small typed capital-status reference table.
558
588
  if (opts.capitals?.length) {
559
589
  await createCapitalTable<CandidateDatabase>(kdb)
560
590
 
@@ -574,7 +604,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
574
604
  progress("capitals", `${opts.capitals.length.toLocaleString()} capital-reference rows carried in-artifact`)
575
605
  }
576
606
 
577
- // --- materialize the clustered WITHOUT ROWID table (sorted insert → contiguous leaves) ---
607
+ // Materialize the clustered WITHOUT ROWID table in sorted order.
578
608
  progress("cluster", "building clustered candidate table + VACUUM")
579
609
  // Column list + clustered-key order are sourced from CANDIDATE_COLUMNS (the first 6 ARE the PRIMARY
580
610
  // KEY) so the SELECT, the ORDER BY, and the table can't drift. The table comes from the shared
@@ -607,11 +637,12 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
607
637
  places: attrs.size,
608
638
  primaries: nPrim,
609
639
  aliases: nAlias,
640
+ regionOfficialRefused,
610
641
  abbrevs: nAbbr,
611
642
  postcodes: nPostcode,
612
643
  postcodeAliases: nPostcodeAlias,
613
- ancestorRows: sidecar.ancestorRows,
614
- ancestorPlaces: sidecar.ancestorPlaces,
644
+ ancestorRows: sidecar.ancestorRows + nLocalityAncestor,
645
+ ancestorPlaces: sidecar.ancestorPlaces + nLocalityScoped,
615
646
  intervalPlaces: sidecar.intervalPlaces,
616
647
  ...roles,
617
648
  ...(importance ? { importanceScored: importance.matched, importanceFiltered: importance.refused } : {}),
@@ -6,33 +6,83 @@
6
6
  */
7
7
 
8
8
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
9
+ import { tableExists } from "@mailwoman/sqlite/introspection"
9
10
 
10
- import type { CandidateDatabase } from "#candidate-schema"
11
11
  import type { PlaceAttrs, StageRow } from "#candidate/place-attrs"
12
+ import type { CandidateDatabase } from "#candidate/schema"
12
13
  import { ALIAS_SEPARATOR } from "#fts/index"
13
14
  import type { WOFDatabase } from "#schema"
14
15
  import { normalizeLocalityForKey } from "#street/normalize"
15
16
 
17
+ /**
18
+ * The official name of every current region, keyed `${country id}\0${name key}` → region id, from the source `names`
19
+ * table's `official` bit. Empty when the source carries no `names` table (a fixture, or an extract shape).
20
+ *
21
+ * This is the positive evidence behind the one alias refusal below: a name is refused from a region's bag ONLY when
22
+ * another region of the same country holds it as its official name. Three such pairs exist in the admin artifact —
23
+ * `新竹市` on Hsinchu County (the city's official name), `嘉義市` on Chiayi County, `충청남도` on Sejong — and each let the more
24
+ * populous holder of the variant outrank the place the name officially is, so a `新竹市` region node resolved the county
25
+ * and every district scoped to the city contradicted it.
26
+ */
27
+ function officialNameHoldersByRegion(
28
+ src: DatabaseClient<WOFDatabase>,
29
+ ccID: (code: string | null) => number
30
+ ): Map<string, number> {
31
+ const keys = new Map<string, number>()
32
+
33
+ if (!tableExists(src, "names")) return keys
34
+
35
+ for (const r of src
36
+ .prepare(
37
+ `SELECT n.id AS id, n.name AS name, s.country AS country
38
+ FROM names n JOIN spr s ON s.id = n.id
39
+ WHERE s.placetype = 'region' AND s.is_current != 0 AND s.is_deprecated = 0 AND n.official = 1`
40
+ )
41
+ .iterate()) {
42
+ const k = normalizeLocalityForKey(String(r.name ?? ""))
43
+
44
+ if (k) {
45
+ keys.set(`${ccID(r.country as string | null)}\0${k}`, Number(r.id))
46
+ }
47
+ }
48
+
49
+ return keys
50
+ }
51
+
16
52
  /**
17
53
  * Pass 2 — explode each place's `place_search.alt_names` bag into distinct-key alias rows (`is_primary = 0`), and count
18
54
  * each place's distinct staged keys (primary included) — the gloss detector's key-count signal (#1730).
55
+ *
56
+ * With `regionPlacetypeID` and `ccID` given, a REGION's alias that is another same-country region's official name is
57
+ * refused and counted rather than staged (see {@link officialNameHoldersByRegion}); without them the pass stages every
58
+ * alias.
19
59
  */
20
60
  export function explodeAliasBags(
21
61
  src: DatabaseClient<WOFDatabase>,
22
62
  out: DatabaseClient<CandidateDatabase>,
23
63
  attrs: Map<number, PlaceAttrs>,
24
- stageRow: StageRow
25
- ): { nAlias: number; keyCounts: Map<number, number> } {
64
+ stageRow: StageRow,
65
+ opts: { regionPlacetypeID?: number; ccID?: (code: string | null) => number } = {}
66
+ ): { nAlias: number; keyCounts: Map<number, number>; regionOfficialRefused: number } {
26
67
  let nAlias = 0
68
+ let regionOfficialRefused = 0
27
69
  const keyCounts = new Map<number, number>()
70
+
71
+ const official =
72
+ typeof opts.regionPlacetypeID === "number" && opts.ccID
73
+ ? officialNameHoldersByRegion(src, opts.ccID)
74
+ : new Map<string, number>()
75
+
28
76
  out.exec("BEGIN")
29
77
 
30
78
  for (const r of src.prepare("SELECT wof_id, alt_names FROM place_search").iterate()) {
31
- const a = attrs.get(Number(r.wof_id))
79
+ const id = Number(r.wof_id)
80
+ const a = attrs.get(id)
32
81
  const alt = r.alt_names as string | null
33
82
 
34
83
  if (!a || !alt) continue
35
84
  const seen = new Set<string>([a.pkey])
85
+ const isRegion = a.ptid === opts.regionPlacetypeID
36
86
 
37
87
  // The writer space-pads each separator and appends a trailing one, so every piece arrives with
38
88
  // surrounding whitespace and the last one is empty. `normalizeLocalityForKey` folds both away,
@@ -42,15 +92,26 @@ export function explodeAliasBags(
42
92
 
43
93
  if (!k || seen.has(k)) continue
44
94
  seen.add(k)
45
- stageRow(k, a, Number(r.wof_id), 0)
95
+
96
+ if (isRegion) {
97
+ const holder = official.get(`${a.cid}\0${k}`)
98
+
99
+ if (typeof holder === "number" && holder !== id) {
100
+ regionOfficialRefused++
101
+
102
+ continue
103
+ }
104
+ }
105
+
106
+ stageRow(k, a, id, 0)
46
107
 
47
108
  nAlias++
48
109
  }
49
110
 
50
- keyCounts.set(Number(r.wof_id), seen.size)
111
+ keyCounts.set(id, seen.size)
51
112
  }
52
113
 
53
114
  out.exec("COMMIT")
54
115
 
55
- return { nAlias, keyCounts }
116
+ return { nAlias, keyCounts, regionOfficialRefused }
56
117
  }
@@ -59,7 +59,7 @@ import { sql, type Kysely } from "kysely"
59
59
  // Type-only and circular on purpose (candidate-schema extends CandidateAncestorsDatabase): Kysely's
60
60
  // DB parameter is invariant, so the DDL functions must take the FULL database type their caller
61
61
  // holds. Erased at runtime.
62
- import type { CandidateDatabase } from "#candidate-schema"
62
+ import type { CandidateDatabase } from "#candidate/schema"
63
63
  import type { NameKey } from "#street/normalize"
64
64
 
65
65
  /**
@@ -15,9 +15,9 @@ import {
15
15
  createCandidateAncestorTable,
16
16
  createCandidateIntervalTable,
17
17
  MAX_ANCESTOR_DEPTH,
18
- } from "#candidate-ancestors-schema"
19
- import type { CandidateDatabase } from "#candidate-schema"
18
+ } from "#candidate/ancestors/schema"
20
19
  import type { PlaceAttrs } from "#candidate/place-attrs"
20
+ import type { CandidateDatabase } from "#candidate/schema"
21
21
  import type { WOFDatabase } from "#schema"
22
22
 
23
23
  /**