@mailwoman/corpus 9.0.0 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (524) hide show
  1. package/README.md +3 -3
  2. package/data/PROVENANCE.md +227 -0
  3. package/data/reviewed-ve-postcode-tuples.json +74 -0
  4. package/data/sub-venue-lexicon.json +18269 -0
  5. package/out/src/adapters/ban/adapter.d.ts +1 -1
  6. package/out/src/adapters/ban/adapter.d.ts.map +1 -1
  7. package/out/src/adapters/ban/adapter.js +5 -4
  8. package/out/src/adapters/ban/adapter.js.map +1 -1
  9. package/out/src/adapters/fcc-bdc/adapter.d.ts +1 -1
  10. package/out/src/adapters/fcc-bdc/adapter.d.ts.map +1 -1
  11. package/out/src/adapters/fcc-bdc/adapter.js +3 -3
  12. package/out/src/adapters/fcc-bdc/adapter.js.map +1 -1
  13. package/out/src/adapters/geonames/adapter.d.ts +1 -1
  14. package/out/src/adapters/geonames/adapter.d.ts.map +1 -1
  15. package/out/src/adapters/geonames/adapter.js +2 -2
  16. package/out/src/adapters/geonames/adapter.js.map +1 -1
  17. package/out/src/adapters/geonames-postal/adapter.d.ts +1 -1
  18. package/out/src/adapters/geonames-postal/adapter.d.ts.map +1 -1
  19. package/out/src/adapters/geonames-postal/adapter.js +3 -3
  20. package/out/src/adapters/geonames-postal/adapter.js.map +1 -1
  21. package/out/src/adapters/gnaf/adapter.d.ts +1 -1
  22. package/out/src/adapters/gnaf/adapter.d.ts.map +1 -1
  23. package/out/src/adapters/gnaf/adapter.js +2 -2
  24. package/out/src/adapters/gnaf/adapter.js.map +1 -1
  25. package/out/src/adapters/gnaf/assemble.js +1 -1
  26. package/out/src/adapters/gnaf/assemble.js.map +1 -1
  27. package/out/src/adapters/index.d.ts +1 -1
  28. package/out/src/adapters/index.d.ts.map +1 -1
  29. package/out/src/adapters/index.js +1 -1
  30. package/out/src/adapters/index.js.map +1 -1
  31. package/out/src/adapters/openaddresses/adapter.d.ts +1 -1
  32. package/out/src/adapters/openaddresses/adapter.d.ts.map +1 -1
  33. package/out/src/adapters/openaddresses/adapter.js +3 -3
  34. package/out/src/adapters/openaddresses/adapter.js.map +1 -1
  35. package/out/src/adapters/overture/adapter.d.ts +1 -1
  36. package/out/src/adapters/overture/adapter.d.ts.map +1 -1
  37. package/out/src/adapters/overture/adapter.js +2 -2
  38. package/out/src/adapters/overture/adapter.js.map +1 -1
  39. package/out/src/adapters/state-hi-schools/adapter.d.ts +1 -1
  40. package/out/src/adapters/state-hi-schools/adapter.d.ts.map +1 -1
  41. package/out/src/adapters/state-hi-schools/adapter.js +7 -6
  42. package/out/src/adapters/state-hi-schools/adapter.js.map +1 -1
  43. package/out/src/adapters/state-ia-contractors/adapter.d.ts +1 -1
  44. package/out/src/adapters/state-ia-contractors/adapter.d.ts.map +1 -1
  45. package/out/src/adapters/state-ia-contractors/adapter.js +12 -7
  46. package/out/src/adapters/state-ia-contractors/adapter.js.map +1 -1
  47. package/out/src/adapters/state-ny-notaries/adapter.d.ts +1 -1
  48. package/out/src/adapters/state-ny-notaries/adapter.d.ts.map +1 -1
  49. package/out/src/adapters/state-ny-notaries/adapter.js +12 -18
  50. package/out/src/adapters/state-ny-notaries/adapter.js.map +1 -1
  51. package/out/src/adapters/state-tx-notaries/adapter.d.ts +1 -1
  52. package/out/src/adapters/state-tx-notaries/adapter.d.ts.map +1 -1
  53. package/out/src/adapters/state-tx-notaries/adapter.js +12 -7
  54. package/out/src/adapters/state-tx-notaries/adapter.js.map +1 -1
  55. package/out/src/adapters/synth-po-box/adapter.d.ts +3 -3
  56. package/out/src/adapters/synth-po-box/adapter.d.ts.map +1 -1
  57. package/out/src/adapters/synth-po-box/adapter.js +3 -3
  58. package/out/src/adapters/synth-po-box/adapter.js.map +1 -1
  59. package/out/src/adapters/tiger/adapter.d.ts +1 -1
  60. package/out/src/adapters/tiger/adapter.d.ts.map +1 -1
  61. package/out/src/adapters/tiger/adapter.js +2 -2
  62. package/out/src/adapters/tiger/adapter.js.map +1 -1
  63. package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts +1 -1
  64. package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts.map +1 -1
  65. package/out/src/adapters/usgov-hrsa-fqhc/adapter.js +7 -6
  66. package/out/src/adapters/usgov-hrsa-fqhc/adapter.js.map +1 -1
  67. package/out/src/adapters/usgov-imls-pls/adapter.d.ts +1 -1
  68. package/out/src/adapters/usgov-imls-pls/adapter.d.ts.map +1 -1
  69. package/out/src/adapters/usgov-imls-pls/adapter.js +11 -7
  70. package/out/src/adapters/usgov-imls-pls/adapter.js.map +1 -1
  71. package/out/src/adapters/usgov-irs-bmf/adapter.d.ts +1 -1
  72. package/out/src/adapters/usgov-irs-bmf/adapter.d.ts.map +1 -1
  73. package/out/src/adapters/usgov-irs-bmf/adapter.js +6 -5
  74. package/out/src/adapters/usgov-irs-bmf/adapter.js.map +1 -1
  75. package/out/src/adapters/usgov-nad/adapter.d.ts +1 -1
  76. package/out/src/adapters/usgov-nad/adapter.d.ts.map +1 -1
  77. package/out/src/adapters/usgov-nad/adapter.js +8 -8
  78. package/out/src/adapters/usgov-nad/adapter.js.map +1 -1
  79. package/out/src/adapters/usgov-nppes/adapter.d.ts +1 -1
  80. package/out/src/adapters/usgov-nppes/adapter.d.ts.map +1 -1
  81. package/out/src/adapters/usgov-nppes/adapter.js +10 -10
  82. package/out/src/adapters/usgov-nppes/adapter.js.map +1 -1
  83. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.d.ts +1 -1
  84. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.d.ts.map +1 -1
  85. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js +7 -6
  86. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js.map +1 -1
  87. package/out/src/{adapter.d.ts → adapters/utils/index.d.ts} +8 -2
  88. package/out/src/adapters/utils/index.d.ts.map +1 -0
  89. package/out/src/{adapter.js → adapters/utils/index.js} +11 -3
  90. package/out/src/adapters/utils/index.js.map +1 -0
  91. package/out/src/adapters/wof-admin-jp/adapter.d.ts +1 -1
  92. package/out/src/adapters/wof-admin-jp/adapter.d.ts.map +1 -1
  93. package/out/src/adapters/wof-admin-jp/adapter.js +5 -5
  94. package/out/src/adapters/wof-admin-jp/adapter.js.map +1 -1
  95. package/out/src/adapters/wof-admin-json/adapter.d.ts +4 -15
  96. package/out/src/adapters/wof-admin-json/adapter.d.ts.map +1 -1
  97. package/out/src/adapters/wof-admin-json/adapter.js +11 -35
  98. package/out/src/adapters/wof-admin-json/adapter.js.map +1 -1
  99. package/out/src/adapters/wof-json-rows.d.ts +32 -0
  100. package/out/src/adapters/wof-json-rows.d.ts.map +1 -0
  101. package/out/src/adapters/wof-json-rows.js +45 -0
  102. package/out/src/adapters/wof-json-rows.js.map +1 -0
  103. package/out/src/adapters/wof-postalcode-json/adapter.d.ts +4 -9
  104. package/out/src/adapters/wof-postalcode-json/adapter.d.ts.map +1 -1
  105. package/out/src/adapters/wof-postalcode-json/adapter.js +12 -37
  106. package/out/src/adapters/wof-postalcode-json/adapter.js.map +1 -1
  107. package/out/src/build.d.ts +4 -4
  108. package/out/src/build.d.ts.map +1 -1
  109. package/out/src/build.js +7 -7
  110. package/out/src/build.js.map +1 -1
  111. package/out/src/index.d.ts +3 -17
  112. package/out/src/index.d.ts.map +1 -1
  113. package/out/src/index.js +3 -17
  114. package/out/src/index.js.map +1 -1
  115. package/out/src/name-prone-us-suffixes.d.ts +12 -0
  116. package/out/src/name-prone-us-suffixes.d.ts.map +1 -0
  117. package/out/src/name-prone-us-suffixes.js +12 -0
  118. package/out/src/name-prone-us-suffixes.js.map +1 -0
  119. package/out/src/parquet-wrapper/reader.d.ts.map +1 -1
  120. package/out/src/parquet-wrapper/reader.js +5 -0
  121. package/out/src/parquet-wrapper/reader.js.map +1 -1
  122. package/out/src/runner.d.ts +2 -2
  123. package/out/src/runner.d.ts.map +1 -1
  124. package/out/src/runner.js +1 -1
  125. package/out/src/runner.js.map +1 -1
  126. package/out/src/shard-recipes/anchor-absorption.d.ts.map +1 -1
  127. package/out/src/shard-recipes/anchor-absorption.js +4 -5
  128. package/out/src/shard-recipes/anchor-absorption.js.map +1 -1
  129. package/out/src/shard-recipes/bare-country.d.ts +25 -0
  130. package/out/src/shard-recipes/bare-country.d.ts.map +1 -0
  131. package/out/src/shard-recipes/bare-country.js +87 -0
  132. package/out/src/shard-recipes/bare-country.js.map +1 -0
  133. package/out/src/shard-recipes/boundary-stress.d.ts.map +1 -1
  134. package/out/src/shard-recipes/boundary-stress.js +2 -2
  135. package/out/src/shard-recipes/boundary-stress.js.map +1 -1
  136. package/out/src/shard-recipes/country-balanced.d.ts.map +1 -1
  137. package/out/src/shard-recipes/country-balanced.js +14 -21
  138. package/out/src/shard-recipes/country-balanced.js.map +1 -1
  139. package/out/src/shard-recipes/cz-pcfirst-preposition.d.ts.map +1 -1
  140. package/out/src/shard-recipes/cz-pcfirst-preposition.js +28 -10
  141. package/out/src/shard-recipes/cz-pcfirst-preposition.js.map +1 -1
  142. package/out/src/shard-recipes/fr-admin-split.d.ts.map +1 -1
  143. package/out/src/shard-recipes/fr-admin-split.js +4 -3
  144. package/out/src/shard-recipes/fr-admin-split.js.map +1 -1
  145. package/out/src/shard-recipes/fr-bare-street.d.ts.map +1 -1
  146. package/out/src/shard-recipes/fr-bare-street.js +72 -11
  147. package/out/src/shard-recipes/fr-bare-street.js.map +1 -1
  148. package/out/src/shard-recipes/fr-lieudit.d.ts.map +1 -1
  149. package/out/src/shard-recipes/fr-lieudit.js +4 -3
  150. package/out/src/shard-recipes/fr-lieudit.js.map +1 -1
  151. package/out/src/shard-recipes/fr-order.d.ts.map +1 -1
  152. package/out/src/shard-recipes/fr-order.js +11 -20
  153. package/out/src/shard-recipes/fr-order.js.map +1 -1
  154. package/out/src/shard-recipes/german.d.ts.map +1 -1
  155. package/out/src/shard-recipes/german.js +8 -14
  156. package/out/src/shard-recipes/german.js.map +1 -1
  157. package/out/src/shard-recipes/house-venue.d.ts.map +1 -1
  158. package/out/src/shard-recipes/house-venue.js +1 -1
  159. package/out/src/shard-recipes/house-venue.js.map +1 -1
  160. package/out/src/shard-recipes/index.d.ts.map +1 -1
  161. package/out/src/shard-recipes/index.js +8 -1
  162. package/out/src/shard-recipes/index.js.map +1 -1
  163. package/out/src/shard-recipes/intersection.d.ts.map +1 -1
  164. package/out/src/shard-recipes/intersection.js +7 -13
  165. package/out/src/shard-recipes/intersection.js.map +1 -1
  166. package/out/src/shard-recipes/locale.d.ts +5 -5
  167. package/out/src/shard-recipes/locale.d.ts.map +1 -1
  168. package/out/src/shard-recipes/locale.js +13 -20
  169. package/out/src/shard-recipes/locale.js.map +1 -1
  170. package/out/src/shard-recipes/no-street.d.ts.map +1 -1
  171. package/out/src/shard-recipes/no-street.js +1 -1
  172. package/out/src/shard-recipes/no-street.js.map +1 -1
  173. package/out/src/shard-recipes/po-box-cedex.d.ts.map +1 -1
  174. package/out/src/shard-recipes/po-box-cedex.js +60 -43
  175. package/out/src/shard-recipes/po-box-cedex.js.map +1 -1
  176. package/out/src/shard-recipes/po-box.d.ts.map +1 -1
  177. package/out/src/shard-recipes/po-box.js +1 -1
  178. package/out/src/shard-recipes/po-box.js.map +1 -1
  179. package/out/src/shard-recipes/reviewed-postcode-tail.d.ts +47 -0
  180. package/out/src/shard-recipes/reviewed-postcode-tail.d.ts.map +1 -0
  181. package/out/src/shard-recipes/reviewed-postcode-tail.js +125 -0
  182. package/out/src/shard-recipes/reviewed-postcode-tail.js.map +1 -0
  183. package/out/src/shard-recipes/scaffold.d.ts +51 -23
  184. package/out/src/shard-recipes/scaffold.d.ts.map +1 -1
  185. package/out/src/shard-recipes/scaffold.js +59 -23
  186. package/out/src/shard-recipes/scaffold.js.map +1 -1
  187. package/out/src/shard-recipes/street-affix.d.ts +54 -0
  188. package/out/src/shard-recipes/street-affix.d.ts.map +1 -1
  189. package/out/src/shard-recipes/street-affix.js +203 -35
  190. package/out/src/shard-recipes/street-affix.js.map +1 -1
  191. package/out/src/shard-recipes/street-bare.d.ts.map +1 -1
  192. package/out/src/shard-recipes/street-bare.js +3 -3
  193. package/out/src/shard-recipes/street-bare.js.map +1 -1
  194. package/out/src/shard-recipes/street.d.ts.map +1 -1
  195. package/out/src/shard-recipes/street.js +2 -2
  196. package/out/src/shard-recipes/street.js.map +1 -1
  197. package/out/src/shard-recipes/sub-venue-sources.d.ts +3 -6
  198. package/out/src/shard-recipes/sub-venue-sources.d.ts.map +1 -1
  199. package/out/src/shard-recipes/sub-venue-sources.js +4 -9
  200. package/out/src/shard-recipes/sub-venue-sources.js.map +1 -1
  201. package/out/src/shard-recipes/sub-venue.d.ts +2 -2
  202. package/out/src/shard-recipes/sub-venue.d.ts.map +1 -1
  203. package/out/src/shard-recipes/sub-venue.js +7 -6
  204. package/out/src/shard-recipes/sub-venue.js.map +1 -1
  205. package/out/src/shard-recipes/trailing-region.d.ts +74 -0
  206. package/out/src/shard-recipes/trailing-region.d.ts.map +1 -0
  207. package/out/src/shard-recipes/trailing-region.js +157 -0
  208. package/out/src/shard-recipes/trailing-region.js.map +1 -0
  209. package/out/src/shard-recipes/unit.d.ts.map +1 -1
  210. package/out/src/shard-recipes/unit.js +6 -12
  211. package/out/src/shard-recipes/unit.js.map +1 -1
  212. package/out/src/{synthesize-anchor-absorption.d.ts → synthesizers/anchor-absorption.d.ts} +1 -1
  213. package/out/src/synthesizers/anchor-absorption.d.ts.map +1 -0
  214. package/out/src/{synthesize-anchor-absorption.js → synthesizers/anchor-absorption.js} +1 -1
  215. package/out/src/synthesizers/anchor-absorption.js.map +1 -0
  216. package/out/src/{synthesize-boundary-stress.d.ts → synthesizers/boundary-stress.d.ts} +3 -3
  217. package/out/src/synthesizers/boundary-stress.d.ts.map +1 -0
  218. package/out/src/{synthesize-boundary-stress.js → synthesizers/boundary-stress.js} +2 -2
  219. package/out/src/synthesizers/boundary-stress.js.map +1 -0
  220. package/out/src/{synthesize-german.d.ts → synthesizers/german.d.ts} +2 -2
  221. package/out/src/synthesizers/german.d.ts.map +1 -0
  222. package/out/src/{synthesize-german.js → synthesizers/german.js} +2 -2
  223. package/out/src/synthesizers/german.js.map +1 -0
  224. package/out/src/{synthesize-house-venue.d.ts → synthesizers/house-venue.d.ts} +4 -4
  225. package/out/src/synthesizers/house-venue.d.ts.map +1 -0
  226. package/out/src/{synthesize-house-venue.js → synthesizers/house-venue.js} +20 -6
  227. package/out/src/synthesizers/house-venue.js.map +1 -0
  228. package/out/src/{synthesize-intersection.d.ts → synthesizers/intersection.d.ts} +2 -2
  229. package/out/src/synthesizers/intersection.d.ts.map +1 -0
  230. package/out/src/{synthesize-intersection.js → synthesizers/intersection.js} +1 -1
  231. package/out/src/synthesizers/intersection.js.map +1 -0
  232. package/out/src/{synthesize-no-street.d.ts → synthesizers/no-street.d.ts} +3 -3
  233. package/out/src/synthesizers/no-street.d.ts.map +1 -0
  234. package/out/src/{synthesize-no-street.js → synthesizers/no-street.js} +2 -2
  235. package/out/src/synthesizers/no-street.js.map +1 -0
  236. package/out/src/{synthesize-po-box.d.ts → synthesizers/po-box.d.ts} +2 -2
  237. package/out/src/synthesizers/po-box.d.ts.map +1 -0
  238. package/out/src/{synthesize-po-box.js → synthesizers/po-box.js} +1 -1
  239. package/out/src/synthesizers/po-box.js.map +1 -0
  240. package/out/src/{synthesize-street.d.ts → synthesizers/street.d.ts} +2 -2
  241. package/out/src/synthesizers/street.d.ts.map +1 -0
  242. package/out/src/{synthesize-street.js → synthesizers/street.js} +4 -3
  243. package/out/src/synthesizers/street.js.map +1 -0
  244. package/out/src/{synthesize.d.ts → synthesizers/utils.d.ts} +3 -3
  245. package/out/src/synthesizers/utils.d.ts.map +1 -0
  246. package/out/src/{synthesize.js → synthesizers/utils.js} +5 -4
  247. package/out/src/synthesizers/utils.js.map +1 -0
  248. package/out/src/tools/align-shard.d.ts.map +1 -1
  249. package/out/src/tools/align-shard.js +2 -2
  250. package/out/src/tools/align-shard.js.map +1 -1
  251. package/out/src/tools/fetch/ban.d.ts.map +1 -1
  252. package/out/src/tools/fetch/ban.js +2 -18
  253. package/out/src/tools/fetch/ban.js.map +1 -1
  254. package/out/src/tools/fetch/download.d.ts +12 -3
  255. package/out/src/tools/fetch/download.d.ts.map +1 -1
  256. package/out/src/tools/fetch/download.js +20 -4
  257. package/out/src/tools/fetch/download.js.map +1 -1
  258. package/out/src/tools/fetch/geonames-dump.d.ts +87 -0
  259. package/out/src/tools/fetch/geonames-dump.d.ts.map +1 -0
  260. package/out/src/tools/fetch/geonames-dump.js +178 -0
  261. package/out/src/tools/fetch/geonames-dump.js.map +1 -0
  262. package/out/src/tools/fetch/geonames-postal.d.ts +76 -0
  263. package/out/src/tools/fetch/geonames-postal.d.ts.map +1 -0
  264. package/out/src/tools/fetch/geonames-postal.js +125 -0
  265. package/out/src/tools/fetch/geonames-postal.js.map +1 -0
  266. package/out/src/tools/fetch/imls-pls.d.ts.map +1 -1
  267. package/out/src/tools/fetch/imls-pls.js +5 -17
  268. package/out/src/tools/fetch/imls-pls.js.map +1 -1
  269. package/out/src/tools/fetch/index.d.ts +11 -0
  270. package/out/src/tools/fetch/index.d.ts.map +1 -1
  271. package/out/src/tools/fetch/index.js +11 -0
  272. package/out/src/tools/fetch/index.js.map +1 -1
  273. package/out/src/tools/fetch/nad.d.ts.map +1 -1
  274. package/out/src/tools/fetch/nad.js +20 -11
  275. package/out/src/tools/fetch/nad.js.map +1 -1
  276. package/out/src/tools/fetch/nppes.d.ts.map +1 -1
  277. package/out/src/tools/fetch/nppes.js +17 -22
  278. package/out/src/tools/fetch/nppes.js.map +1 -1
  279. package/out/src/tools/fetch/openaddresses.d.ts.map +1 -1
  280. package/out/src/tools/fetch/openaddresses.js +18 -17
  281. package/out/src/tools/fetch/openaddresses.js.map +1 -1
  282. package/out/src/tools/fetch/ourairports.d.ts.map +1 -1
  283. package/out/src/tools/fetch/ourairports.js +7 -2
  284. package/out/src/tools/fetch/ourairports.js.map +1 -1
  285. package/out/src/tools/fetch/ppd.d.ts +0 -4
  286. package/out/src/tools/fetch/ppd.d.ts.map +1 -1
  287. package/out/src/tools/fetch/ppd.js +1 -1
  288. package/out/src/tools/fetch/ppd.js.map +1 -1
  289. package/out/src/tools/fetch/state-hi-schools.d.ts.map +1 -1
  290. package/out/src/tools/fetch/state-hi-schools.js +3 -19
  291. package/out/src/tools/fetch/state-hi-schools.js.map +1 -1
  292. package/out/src/tools/fetch/state-sources.d.ts +4 -0
  293. package/out/src/tools/fetch/state-sources.d.ts.map +1 -1
  294. package/out/src/tools/fetch/state-sources.js +1 -5
  295. package/out/src/tools/fetch/state-sources.js.map +1 -1
  296. package/out/src/tools/fetch/tiger-full.d.ts.map +1 -1
  297. package/out/src/tools/fetch/tiger-full.js +15 -21
  298. package/out/src/tools/fetch/tiger-full.js.map +1 -1
  299. package/out/src/tools/golden-expand.d.ts.map +1 -1
  300. package/out/src/tools/golden-expand.js +34 -25
  301. package/out/src/tools/golden-expand.js.map +1 -1
  302. package/out/src/tools/golden-relabel-street.d.ts.map +1 -1
  303. package/out/src/tools/golden-relabel-street.js +4 -77
  304. package/out/src/tools/golden-relabel-street.js.map +1 -1
  305. package/out/src/tools/index.d.ts +2 -2
  306. package/out/src/tools/index.d.ts.map +1 -1
  307. package/out/src/tools/index.js +2 -2
  308. package/out/src/tools/index.js.map +1 -1
  309. package/out/src/tools/ingest-csv.d.ts.map +1 -1
  310. package/out/src/tools/ingest-csv.js +0 -11
  311. package/out/src/tools/ingest-csv.js.map +1 -1
  312. package/out/src/tools/overlay-manifest.d.ts +4 -0
  313. package/out/src/tools/overlay-manifest.d.ts.map +1 -1
  314. package/out/src/tools/overlay-manifest.js +16 -6
  315. package/out/src/tools/overlay-manifest.js.map +1 -1
  316. package/out/src/tools/postcode-triples.d.ts +175 -0
  317. package/out/src/tools/postcode-triples.d.ts.map +1 -0
  318. package/out/src/tools/postcode-triples.js +304 -0
  319. package/out/src/tools/postcode-triples.js.map +1 -0
  320. package/out/src/tools/shard-kryptonite.d.ts.map +1 -1
  321. package/out/src/tools/shard-kryptonite.js +1 -3
  322. package/out/src/tools/shard-kryptonite.js.map +1 -1
  323. package/out/src/tools/shard-translit.d.ts.map +1 -1
  324. package/out/src/tools/shard-translit.js +4 -27
  325. package/out/src/tools/shard-translit.js.map +1 -1
  326. package/out/src/tools/sub-venue/harvest.d.ts +100 -0
  327. package/out/src/tools/sub-venue/harvest.d.ts.map +1 -0
  328. package/out/src/tools/sub-venue/harvest.js +168 -0
  329. package/out/src/tools/sub-venue/harvest.js.map +1 -0
  330. package/out/src/tools/sub-venue/head-nouns.d.ts +50 -0
  331. package/out/src/tools/sub-venue/head-nouns.d.ts.map +1 -0
  332. package/out/src/tools/sub-venue/head-nouns.js +210 -0
  333. package/out/src/tools/sub-venue/head-nouns.js.map +1 -0
  334. package/out/src/tools/sub-venue/surfaces.d.ts +45 -0
  335. package/out/src/tools/sub-venue/surfaces.d.ts.map +1 -0
  336. package/out/src/tools/sub-venue/surfaces.js +77 -0
  337. package/out/src/tools/sub-venue/surfaces.js.map +1 -0
  338. package/out/src/tools/sub-venue/table.d.ts +229 -0
  339. package/out/src/tools/sub-venue/table.d.ts.map +1 -0
  340. package/out/src/tools/sub-venue/table.js +108 -0
  341. package/out/src/tools/sub-venue/table.js.map +1 -0
  342. package/out/src/tools/sub-venue/wikidata.d.ts +26 -0
  343. package/out/src/tools/sub-venue/wikidata.d.ts.map +1 -0
  344. package/out/src/tools/sub-venue/wikidata.js +62 -0
  345. package/out/src/tools/sub-venue/wikidata.js.map +1 -0
  346. package/out/src/tools/sub-venue-lexicon.d.ts +27 -378
  347. package/out/src/tools/sub-venue-lexicon.d.ts.map +1 -1
  348. package/out/src/tools/sub-venue-lexicon.js +30 -565
  349. package/out/src/tools/sub-venue-lexicon.js.map +1 -1
  350. package/out/src/{align.d.ts → utils/align.d.ts} +1 -1
  351. package/out/src/utils/align.d.ts.map +1 -0
  352. package/out/src/utils/align.js.map +1 -0
  353. package/out/src/utils/golden.d.ts.map +1 -0
  354. package/out/src/{golden.js → utils/golden.js} +2 -2
  355. package/out/src/utils/golden.js.map +1 -0
  356. package/out/src/utils/index.d.ts +14 -0
  357. package/out/src/utils/index.d.ts.map +1 -0
  358. package/out/src/utils/index.js +14 -0
  359. package/out/src/utils/index.js.map +1 -0
  360. package/out/src/utils/license.d.ts.map +1 -0
  361. package/out/src/{license.js → utils/license.js} +1 -1
  362. package/out/src/utils/license.js.map +1 -0
  363. package/out/src/{parquet.d.ts → utils/parquet.d.ts} +24 -5
  364. package/out/src/utils/parquet.d.ts.map +1 -0
  365. package/out/src/{parquet.js → utils/parquet.js} +6 -3
  366. package/out/src/utils/parquet.js.map +1 -0
  367. package/out/src/{split.d.ts → utils/split.d.ts} +1 -1
  368. package/out/src/utils/split.d.ts.map +1 -0
  369. package/out/src/{split.js → utils/split.js} +2 -2
  370. package/out/src/utils/split.js.map +1 -0
  371. package/out/src/utils/tokenize.d.ts.map +1 -0
  372. package/out/src/utils/tokenize.js.map +1 -0
  373. package/out/src/utils/wof-json.d.ts.map +1 -0
  374. package/out/src/utils/wof-json.js.map +1 -0
  375. package/package.json +287 -21
  376. package/src/adapters/ban/adapter.ts +7 -5
  377. package/src/adapters/fcc-bdc/adapter.ts +5 -4
  378. package/src/adapters/geonames/adapter.ts +3 -3
  379. package/src/adapters/geonames-postal/adapter.ts +4 -4
  380. package/src/adapters/gnaf/adapter.ts +3 -3
  381. package/src/adapters/gnaf/assemble.ts +1 -1
  382. package/src/adapters/index.ts +3 -2
  383. package/src/adapters/openaddresses/adapter.ts +4 -4
  384. package/src/adapters/overture/adapter.ts +3 -3
  385. package/src/adapters/state-hi-schools/adapter.ts +8 -7
  386. package/src/adapters/state-ia-contractors/adapter.ts +13 -8
  387. package/src/adapters/state-ny-notaries/adapter.ts +13 -32
  388. package/src/adapters/state-tx-notaries/adapter.ts +13 -8
  389. package/src/adapters/synth-po-box/adapter.ts +4 -4
  390. package/src/adapters/tiger/adapter.ts +5 -3
  391. package/src/adapters/usgov-hrsa-fqhc/adapter.ts +8 -7
  392. package/src/adapters/usgov-imls-pls/adapter.ts +12 -8
  393. package/src/adapters/usgov-irs-bmf/adapter.ts +7 -6
  394. package/src/adapters/usgov-nad/adapter.ts +9 -9
  395. package/src/adapters/usgov-nppes/adapter.ts +13 -12
  396. package/src/adapters/usgov-samhsa-treatment-locator/adapter.ts +8 -7
  397. package/src/{adapter.ts → adapters/utils/index.ts} +15 -3
  398. package/src/adapters/wof-admin-jp/adapter.ts +9 -12
  399. package/src/adapters/wof-admin-json/adapter.ts +15 -54
  400. package/src/adapters/wof-json-rows.ts +79 -0
  401. package/src/adapters/wof-postalcode-json/adapter.ts +16 -50
  402. package/src/build.ts +8 -8
  403. package/src/index.ts +3 -17
  404. package/src/name-prone-us-suffixes.ts +12 -0
  405. package/src/parquet-wrapper/reader.ts +9 -0
  406. package/src/runner.ts +2 -2
  407. package/src/shard-recipes/anchor-absorption.ts +5 -5
  408. package/src/shard-recipes/bare-country.ts +95 -0
  409. package/src/shard-recipes/boundary-stress.ts +3 -2
  410. package/src/shard-recipes/country-balanced.ts +16 -27
  411. package/src/shard-recipes/cz-pcfirst-preposition.ts +29 -10
  412. package/src/shard-recipes/fr-admin-split.ts +6 -4
  413. package/src/shard-recipes/fr-bare-street.ts +85 -10
  414. package/src/shard-recipes/fr-lieudit.ts +6 -4
  415. package/src/shard-recipes/fr-order.ts +13 -26
  416. package/src/shard-recipes/german.ts +9 -18
  417. package/src/shard-recipes/house-venue.ts +2 -1
  418. package/src/shard-recipes/index.ts +8 -1
  419. package/src/shard-recipes/intersection.ts +9 -17
  420. package/src/shard-recipes/locale.ts +16 -24
  421. package/src/shard-recipes/no-street.ts +2 -1
  422. package/src/shard-recipes/po-box-cedex.ts +76 -58
  423. package/src/shard-recipes/po-box.ts +2 -1
  424. package/src/shard-recipes/reviewed-postcode-tail.ts +189 -0
  425. package/src/shard-recipes/scaffold.ts +99 -24
  426. package/src/shard-recipes/street-affix.ts +284 -45
  427. package/src/shard-recipes/street-bare.ts +4 -3
  428. package/src/shard-recipes/street.ts +3 -2
  429. package/src/shard-recipes/sub-venue-sources.ts +13 -23
  430. package/src/shard-recipes/sub-venue.ts +11 -9
  431. package/src/shard-recipes/trailing-region.ts +183 -0
  432. package/src/shard-recipes/unit.ts +8 -17
  433. package/src/{synthesize-boundary-stress.ts → synthesizers/boundary-stress.ts} +2 -2
  434. package/src/{synthesize-german.ts → synthesizers/german.ts} +3 -2
  435. package/src/{synthesize-house-venue.ts → synthesizers/house-venue.ts} +20 -6
  436. package/src/{synthesize-intersection.ts → synthesizers/intersection.ts} +1 -1
  437. package/src/{synthesize-no-street.ts → synthesizers/no-street.ts} +2 -2
  438. package/src/{synthesize-po-box.ts → synthesizers/po-box.ts} +1 -1
  439. package/src/{synthesize-street.ts → synthesizers/street.ts} +5 -3
  440. package/src/{synthesize.ts → synthesizers/utils.ts} +6 -4
  441. package/src/tools/align-shard.ts +3 -2
  442. package/src/tools/fetch/ban.ts +2 -22
  443. package/src/tools/fetch/download.ts +22 -4
  444. package/src/tools/fetch/geonames-dump.ts +267 -0
  445. package/src/tools/fetch/geonames-postal.ts +180 -0
  446. package/src/tools/fetch/imls-pls.ts +6 -20
  447. package/src/tools/fetch/index.ts +11 -0
  448. package/src/tools/fetch/nad.ts +21 -10
  449. package/src/tools/fetch/nppes.ts +17 -23
  450. package/src/tools/fetch/openaddresses.ts +18 -22
  451. package/src/tools/fetch/ourairports.ts +7 -2
  452. package/src/tools/fetch/ppd.ts +1 -1
  453. package/src/tools/fetch/state-hi-schools.ts +3 -23
  454. package/src/tools/fetch/state-sources.ts +1 -1
  455. package/src/tools/fetch/tiger-full.ts +17 -24
  456. package/src/tools/golden-expand.ts +44 -33
  457. package/src/tools/golden-relabel-street.ts +5 -68
  458. package/src/tools/index.ts +2 -2
  459. package/src/tools/ingest-csv.ts +0 -13
  460. package/src/tools/overlay-manifest.ts +20 -8
  461. package/src/tools/postcode-triples.ts +375 -0
  462. package/src/tools/shard-kryptonite.ts +4 -5
  463. package/src/tools/shard-translit.ts +16 -35
  464. package/src/tools/sub-venue/harvest.ts +235 -0
  465. package/src/tools/sub-venue/head-nouns.ts +242 -0
  466. package/src/tools/sub-venue/surfaces.ts +95 -0
  467. package/src/tools/sub-venue/table.ts +281 -0
  468. package/src/tools/sub-venue/wikidata.ts +85 -0
  469. package/src/tools/sub-venue-lexicon.ts +67 -886
  470. package/src/{align.ts → utils/align.ts} +2 -1
  471. package/src/{golden.ts → utils/golden.ts} +2 -3
  472. package/src/utils/index.ts +14 -0
  473. package/src/{license.ts → utils/license.ts} +1 -1
  474. package/src/{parquet.ts → utils/parquet.ts} +22 -9
  475. package/src/{split.ts → utils/split.ts} +3 -3
  476. package/out/src/adapter.d.ts.map +0 -1
  477. package/out/src/adapter.js.map +0 -1
  478. package/out/src/align.d.ts.map +0 -1
  479. package/out/src/align.js.map +0 -1
  480. package/out/src/format.d.ts +0 -14
  481. package/out/src/format.d.ts.map +0 -1
  482. package/out/src/format.js +0 -14
  483. package/out/src/format.js.map +0 -1
  484. package/out/src/golden.d.ts.map +0 -1
  485. package/out/src/golden.js.map +0 -1
  486. package/out/src/license.d.ts.map +0 -1
  487. package/out/src/license.js.map +0 -1
  488. package/out/src/parquet.d.ts.map +0 -1
  489. package/out/src/parquet.js.map +0 -1
  490. package/out/src/split.d.ts.map +0 -1
  491. package/out/src/split.js.map +0 -1
  492. package/out/src/synthesize-anchor-absorption.d.ts.map +0 -1
  493. package/out/src/synthesize-anchor-absorption.js.map +0 -1
  494. package/out/src/synthesize-boundary-stress.d.ts.map +0 -1
  495. package/out/src/synthesize-boundary-stress.js.map +0 -1
  496. package/out/src/synthesize-german.d.ts.map +0 -1
  497. package/out/src/synthesize-german.js.map +0 -1
  498. package/out/src/synthesize-house-venue.d.ts.map +0 -1
  499. package/out/src/synthesize-house-venue.js.map +0 -1
  500. package/out/src/synthesize-intersection.d.ts.map +0 -1
  501. package/out/src/synthesize-intersection.js.map +0 -1
  502. package/out/src/synthesize-no-street.d.ts.map +0 -1
  503. package/out/src/synthesize-no-street.js.map +0 -1
  504. package/out/src/synthesize-po-box.d.ts.map +0 -1
  505. package/out/src/synthesize-po-box.js.map +0 -1
  506. package/out/src/synthesize-street.d.ts.map +0 -1
  507. package/out/src/synthesize-street.js.map +0 -1
  508. package/out/src/synthesize.d.ts.map +0 -1
  509. package/out/src/synthesize.js.map +0 -1
  510. package/out/src/tokenize.d.ts.map +0 -1
  511. package/out/src/tokenize.js.map +0 -1
  512. package/out/src/wof-json.d.ts.map +0 -1
  513. package/out/src/wof-json.js.map +0 -1
  514. package/src/format.ts +0 -14
  515. /package/out/src/{align.js → utils/align.js} +0 -0
  516. /package/out/src/{golden.d.ts → utils/golden.d.ts} +0 -0
  517. /package/out/src/{license.d.ts → utils/license.d.ts} +0 -0
  518. /package/out/src/{tokenize.d.ts → utils/tokenize.d.ts} +0 -0
  519. /package/out/src/{tokenize.js → utils/tokenize.js} +0 -0
  520. /package/out/src/{wof-json.d.ts → utils/wof-json.d.ts} +0 -0
  521. /package/out/src/{wof-json.js → utils/wof-json.js} +0 -0
  522. /package/src/{synthesize-anchor-absorption.ts → synthesizers/anchor-absorption.ts} +0 -0
  523. /package/src/{tokenize.ts → utils/tokenize.ts} +0 -0
  524. /package/src/{wof-json.ts → utils/wof-json.ts} +0 -0
@@ -24,7 +24,7 @@
24
24
  */
25
25
 
26
26
  import { readFileSync, statSync, writeFileSync } from "node:fs"
27
- import { join } from "node:path"
27
+ import { basename, dirname, join } from "node:path"
28
28
 
29
29
  import { parseJSONStrict } from "@mailwoman/core/objects"
30
30
  import { sha256Hex } from "@mailwoman/core/utils"
@@ -96,6 +96,24 @@ export interface OverlayManifestOptions {
96
96
  note: string
97
97
  }
98
98
 
99
+ /**
100
+ * Resolve a base manifest's shard path to the mounted corpus tree used by Modal.
101
+ */
102
+ export function rerootBaseShardPath(path: string, baseManifestPath: string): string {
103
+ const versionedIndex = path.indexOf("/corpus/versioned/")
104
+
105
+ if (versionedIndex !== -1) return "/data" + path.slice(versionedIndex)
106
+
107
+ if (/^\/data\/(?:train|val|test)\//u.test(path)) {
108
+ const localBaseDir = dirname(baseManifestPath)
109
+ const baseModalRoot = `/data/corpus/versioned/${basename(dirname(localBaseDir))}/${basename(localBaseDir)}`
110
+
111
+ return baseModalRoot + path.slice("/data".length)
112
+ }
113
+
114
+ return path
115
+ }
116
+
99
117
  export async function assembleOverlayManifest(args: OverlayManifestOptions): Promise<void> {
100
118
  const base = parseJSONStrict<BaseManifest>(readFileSync(args.base, "utf8"))
101
119
 
@@ -103,13 +121,7 @@ export async function assembleOverlayManifest(args: OverlayManifestOptions): Pro
103
121
  console.log(`WARN: base already contains source '${args.source}' — is this the right base?`)
104
122
  }
105
123
 
106
- const reroot = (p: string): string => {
107
- const i = p.indexOf("/corpus/versioned/")
108
-
109
- return i !== -1 ? "/data" + p.slice(i) : p
110
- }
111
-
112
- const kept = base.shards.map((s) => ({ ...s, path: reroot(s.path) }))
124
+ const kept = base.shards.map((s) => ({ ...s, path: rerootBaseShardPath(s.path, args.base) }))
113
125
 
114
126
  const newTrain = await descriptor(
115
127
  join(args.newDir, "train", args.shardParquet),
@@ -0,0 +1,375 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Extract `(postcode, locality, region, country)` tuples for the `trailing-region` shard.
7
+ *
8
+ * This step used to be a one-off. Its output survived — 17,908 rows at
9
+ * `$MAILWOMAN_DATA_ROOT/corpus/tuples/trailing-region-structured-tuples.jsonl` — but the code that produced it did
10
+ * not, so a note describing the join was the only record and it did not match what the databases contain. That is the
11
+ * reason this file exists: the shard is unbuildable for a new country without it.
12
+ *
13
+ * ## Two sources, because one route does not reach every country
14
+ *
15
+ * - **`postalcode-intl.db`** carries a real `parent_id` that resolves in the admin gazetteer. Measured share of rows
16
+ * with a parent: NL 97.5%, FR 90.7%, DE 66.1%, ES 34.9%, IT 27.4%; of those, 93.8–100% land on a `locality` or
17
+ * `localadmin`, and the region comes from that place's own ancestry. It is the ONLY shard with this — every
18
+ * `postalcode-geonames-*` and `postalcode-<cc>-overture.db` row reads `parent_id = 0`.
19
+ * - **GeoNames postal exports** carry the place and admin1 NAMES in columns 3 and 4, so there is nothing to join.
20
+ * `mailwoman corpus fetch geonames-postal` puts them on disk.
21
+ *
22
+ * A nearest-locality-centroid join was measured as the general fallback and REJECTED: scored against the `parent_id`
23
+ * truth, it agreed NL 81.1% / DE 44.7% / ES 35.1% / FR 29.0% / IT 13.5%. A locality's centroid sits at its middle, so
24
+ * an edge postcode is routinely nearer a neighbouring town's centroid. Do not reach for it again.
25
+ *
26
+ * ## The hub cap is PER COUNTRY, because a pooled one is a mixture
27
+ *
28
+ * A few localities act as catch-all parents — `Schwedt/Oder` claims 9,222 DE postcodes against a DE median of 1. Left
29
+ * in, a handful of places dominate the shard. But the distribution differs so much by country that one threshold is
30
+ * not one rule: a p99 pooled across the five countries lands at 522, which keeps 100% of ES and IT, 83% of FR, 49% of
31
+ * NL and 19% of DE.
32
+ *
33
+ * So the bound is a QUOTA and not a threshold. A threshold DELETES a locality that exceeds it, which removes exactly
34
+ * the largest cities — the places a parser most needs to have seen. A quota keeps every locality and bounds how many
35
+ * of its postcodes ride along, which is the balance the cap was reaching for without the deletion.
36
+ *
37
+ * ## Placement is data, not a formatting choice
38
+ *
39
+ * Each tuple is stamped with its country's {@link PostcodePlacement}. The same digits change tag with position, so a
40
+ * tuple that does not carry its placement teaches whichever convention the recipe happens to default to — see the
41
+ * recipe's header for the measurement.
42
+ */
43
+
44
+ import { existsSync } from "node:fs"
45
+ import { join } from "node:path"
46
+ import { DatabaseSync } from "node:sqlite"
47
+
48
+ import { dataRootPath } from "@mailwoman/core/utils"
49
+ import { TSVSpliterator } from "spliterator"
50
+
51
+ import type { PostcodePlacement } from "../shard-recipes/scaffold.ts"
52
+
53
+ /**
54
+ * One extracted tuple, in the shape `readTuples` yields and the recipe consumes.
55
+ */
56
+ export interface PostcodeTriple {
57
+ postcode: string
58
+ /**
59
+ * The segment BEFORE the locality, when the source has one. Load-bearing rather than decorative: a shard whose every
60
+ * row begins with the locality teaches that the first named segment IS the locality, and that flips the model's
61
+ * default. Measured on the v4.8.0 candidate, which had no such segment — `Ye Three Lords, 27 Minories, London EC3N
62
+ * 1DE` came back `locality: "Ye Three Lords"` with the venue and the street both gone, and 11 of its 25 regressions
63
+ * were venue-led rows across seven countries.
64
+ */
65
+ dependentLocality?: string
66
+ locality: string
67
+ region: string
68
+ country: string
69
+ cc: string
70
+ locale: string
71
+ postcodePlacement: PostcodePlacement
72
+ }
73
+
74
+ /**
75
+ * Where a country writes the postcode, and the locale tag its rows carry.
76
+ *
77
+ * A country is in this table only when a gauntlet board row ATTESTS its surface. An absent country is not an oversight
78
+ * to be filled in by guessing: extracting it with the wrong placement teaches a convention that country does not use,
79
+ * which is worse than not teaching it at all. `LEADING_POSTCODE_COUNTRIES` in `@mailwoman/neural`'s
80
+ * `placetype-pair-prior.ts` draws the same line for the same reason.
81
+ *
82
+ * AU and ZA are the worked examples of the bar. Both look like obvious additions and neither qualifies: the board's AU
83
+ * rows are bare-city (`Melbourne`, `Sydney, Australia`) and carry no postcode at all, so nothing here says where AU
84
+ * writes it; and ZA's `14 Long St, Green Point, Cape Town, 8001` carries no REGION, which this shard requires — a fact
85
+ * its GeoNames export agrees with, at 100% place and 0% admin1.
86
+ */
87
+ export const POSTCODE_CONVENTIONS: ReadonlyMap<string, { placement: PostcodePlacement; locale: string }> = new Map([
88
+ // `Rue de l'Église, 3, 29217 Plougonvelin, Bretagne, France` and its siblings — `fr_structured`, `de_structured`,
89
+ // `es_structured`, `it_structured`, `pt_structured`, `mx_supermanzana`, `nl-op4-p-r-sloterdijk`.
90
+ ["FR", { placement: "leading", locale: "fr-FR" }],
91
+ ["DE", { placement: "leading", locale: "de-DE" }],
92
+ ["ES", { placement: "leading", locale: "es-ES" }],
93
+ ["IT", { placement: "leading", locale: "it-IT" }],
94
+ ["NL", { placement: "leading", locale: "nl-NL" }],
95
+ ["PT", { placement: "leading", locale: "pt-PT" }],
96
+ ["MX", { placement: "leading", locale: "es-MX" }],
97
+ // `…, Barcelona 6001, Anzoátegui, Venezuela` — the four `ve_city_postcode_trailing_state` rows. No postcode source
98
+ // on disk and GeoNames does not publish VE, so this entry currently yields nothing; it is here because the
99
+ // placement is what makes the absence legible.
100
+ ["VE", { placement: "after_locality", locale: "es-VE" }],
101
+ // `12 MG Road, Indiranagar, Bengaluru, Karnataka 560038, India` — three `in_*` rows, and `AGENTS.md` says the same
102
+ // ("en-IN is absent BECAUSE the PIN goes last"). The one trailing placement with real data behind it.
103
+ ["IN", { placement: "after_region", locale: "en-IN" }],
104
+ ])
105
+
106
+ /**
107
+ * How many postcodes one locality may contribute.
108
+ *
109
+ * A quota rather than a cut-off — see the header. It bounds repetition without deleting a locality: a city with 9,222
110
+ * postcodes contributes this many and stays in the shard.
111
+ */
112
+ export const DEFAULT_LOCALITY_QUOTA = 24
113
+
114
+ /**
115
+ * Take at most `quota` tuples per locality, in the order they arrive.
116
+ *
117
+ * Order matters and is the caller's to choose: both readers below walk their source in id / file order, which is stable
118
+ * across runs, so the same quota selects the same rows.
119
+ */
120
+ export function applyLocalityQuota(
121
+ triples: readonly PostcodeTriple[],
122
+ quota: number = DEFAULT_LOCALITY_QUOTA
123
+ ): PostcodeTriple[] {
124
+ const seen = new Map<string, number>()
125
+ const kept: PostcodeTriple[] = []
126
+
127
+ for (const triple of triples) {
128
+ const key = `${triple.cc} ${triple.locality}`
129
+ const n = seen.get(key) ?? 0
130
+
131
+ if (n >= quota) continue
132
+
133
+ seen.set(key, n + 1)
134
+ kept.push(triple)
135
+ }
136
+
137
+ return kept
138
+ }
139
+
140
+ /**
141
+ * Take at most `budget` tuples per COUNTRY, in the order they arrive.
142
+ *
143
+ * A per-locality quota bounds how often one place repeats; it cannot bound a country. IN has 128,152 distinct
144
+ * localities, so even at a quota of ONE it contributes 63,533 rows against 39,790 from the other seven combined — the
145
+ * shard would teach the trailing surface as an Indian fact rather than a general one, and at 103,323 rows it would take
146
+ * 30% of an 8,000-step run's sample budget at three reps per row.
147
+ *
148
+ * Applied AFTER {@link applyLocalityQuota}, so a country's budget is spent on breadth (many localities) rather than on
149
+ * one city's postcode list.
150
+ */
151
+ export function applyCountryBudget(
152
+ triples: readonly PostcodeTriple[],
153
+ budget: number | ReadonlyMap<string, number>
154
+ ): PostcodeTriple[] {
155
+ const spent = new Map<string, number>()
156
+ const kept: PostcodeTriple[] = []
157
+
158
+ for (const triple of triples) {
159
+ const cap = typeof budget === "number" ? budget : budget.get(triple.cc)
160
+
161
+ if (cap === undefined) continue
162
+
163
+ const n = spent.get(triple.cc) ?? 0
164
+
165
+ if (n >= cap) continue
166
+
167
+ spent.set(triple.cc, n + 1)
168
+ kept.push(triple)
169
+ }
170
+
171
+ return kept
172
+ }
173
+
174
+ /**
175
+ * Read triples out of `postalcode-intl.db` by following each postcode's `parent_id` into the admin gazetteer and that
176
+ * place's ancestry to a region.
177
+ *
178
+ * A postcode whose parent does not resolve, or whose parent has no region ancestor, is DROPPED rather than emitted with
179
+ * a blank — the recipe already skips a tuple with no region, and a blank here would hide how much of the source is
180
+ * actually reachable.
181
+ */
182
+ export function readTriplesFromParentJoin(
183
+ countries: readonly string[],
184
+ options: { postcodeDB?: string; adminDB?: string } = {}
185
+ ): PostcodeTriple[] {
186
+ const postcodeDB = options.postcodeDB ?? String(dataRootPath("wof", "postalcode-intl.db"))
187
+ const adminDB = options.adminDB ?? String(dataRootPath("wof", "admin-global-priority-importance.db"))
188
+
189
+ if (!existsSync(postcodeDB) || !existsSync(adminDB)) return []
190
+
191
+ const db = new DatabaseSync(adminDB, { readOnly: true })
192
+
193
+ try {
194
+ db.exec(`ATTACH DATABASE '${postcodeDB.replaceAll("'", "''")}' AS pc`)
195
+
196
+ const statement = db.prepare(`
197
+ SELECT p.name AS postcode, a.name AS locality, r.name AS region, c.name AS country, p.country AS cc
198
+ FROM pc.spr p
199
+ JOIN spr a ON a.id = p.parent_id AND a.placetype IN ('locality', 'localadmin')
200
+ JOIN ancestors anc ON anc.id = a.id AND anc.ancestor_placetype = 'region'
201
+ JOIN spr r ON r.id = anc.ancestor_id
202
+ JOIN ancestors cnc ON cnc.id = a.id AND cnc.ancestor_placetype = 'country'
203
+ JOIN spr c ON c.id = cnc.ancestor_id
204
+ WHERE p.country = ? AND p.parent_id > 0
205
+ ORDER BY p.id
206
+ `)
207
+
208
+ const out: PostcodeTriple[] = []
209
+
210
+ for (const cc of countries) {
211
+ const convention = POSTCODE_CONVENTIONS.get(cc)
212
+
213
+ if (!convention) continue
214
+
215
+ for (const row of statement.all(cc) as Array<Record<string, string>>) {
216
+ if (!row["postcode"] || !row["locality"] || !row["region"]) continue
217
+
218
+ out.push({
219
+ postcode: row["postcode"],
220
+ locality: row["locality"],
221
+ region: row["region"],
222
+ country: row["country"] ?? "",
223
+ cc,
224
+ locale: convention.locale,
225
+ postcodePlacement: convention.placement,
226
+ })
227
+ }
228
+ }
229
+
230
+ return out
231
+ } finally {
232
+ db.close()
233
+ }
234
+ }
235
+
236
+ /**
237
+ * GeoNames postal columns (0-based), matching `@mailwoman/corpus`'s `geonames-postal` adapter.
238
+ */
239
+ const GEONAMES_COL = { country: 0, postcode: 1, place: 2, admin1: 3, admin2: 5 } as const
240
+
241
+ /**
242
+ * A predicate answering whether a name is a LOCALITY the admin gazetteer knows, for one country.
243
+ *
244
+ * The parent-join reader gets this for free — its query restricts the parent to `locality`/`localadmin`, so every name
245
+ * it emits is one by construction. The GeoNames reader has no such guarantee, and the gap is large enough to matter:
246
+ * sampling 400 rows per country against the gazetteer, the share of GeoNames place names that name a locality we know
247
+ * is PT 75%, IN 62%, **MX 41%**. The Mexican misses are colonias — `Zona Centro`, `San Fernando INFONAVIT`, `FOVISSSTE
248
+ * 3a Sección` — and a row teaching one of those as `locality` trains the locality/dependent_locality boundary in the
249
+ * wrong direction. Dropping the row instead costs coverage and teaches nothing false, which is the better of the two.
250
+ *
251
+ * Returns a predicate that answers `true` for everything when the gazetteer is not on disk, so a checkout without it
252
+ * builds the same rows it did before rather than silently emitting none.
253
+ */
254
+ export function createKnownLocalityGate(country: string, adminDB?: string): (name: string) => boolean {
255
+ const path = adminDB ?? String(dataRootPath("wof", "admin-global-priority-importance.db"))
256
+
257
+ if (!existsSync(path)) return () => true
258
+
259
+ const db = new DatabaseSync(path, { readOnly: true })
260
+
261
+ try {
262
+ const names = new Set<string>()
263
+
264
+ for (const row of db
265
+ .prepare("SELECT name FROM spr WHERE country = ? AND placetype IN ('locality', 'localadmin')")
266
+ .all(country) as Array<{ name: string | null }>) {
267
+ if (row.name) {
268
+ names.add(row.name.toLowerCase())
269
+ }
270
+ }
271
+
272
+ // An empty set means the gazetteer has no localities for this country at all, which is a coverage fact about the
273
+ // gazetteer rather than a verdict on the source — so gate nothing rather than drop everything.
274
+ if (!names.size) return () => true
275
+
276
+ return (name: string) => names.has(name.toLowerCase())
277
+ } finally {
278
+ db.close()
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Read triples straight out of a GeoNames `<CC>.txt` export — no join, the names are columns 3 and 4.
284
+ *
285
+ * COLUMN 3 IS NOT THE LOCALITY. It is the finest-grained named place for the code, and for `560001` that is `Mahatma
286
+ * Gandhi Road` — a STREET — while the city, `Bengaluru`, is column 5 (admin2). MX is the same shape (`Roma Norte` is a
287
+ * colonia inside `Cuauhtémoc`) and so is PT (`Abrigada` inside `Alenquer`). Reading column 3 as the locality is how the
288
+ * v4.8.0 shard came to teach street names as cities. So `admin2` is the locality, `admin1` the region, and column 3 the
289
+ * DEPENDENT locality — which is also the left context the shard needs.
290
+ *
291
+ * WHICH COUNTRIES THIS READER CAN SERVE. It needs `admin2` (the city) and `admin1` (the region), and a country can
292
+ * publish one without the other. Measured 2026-08-23 across the exports on disk:
293
+ *
294
+ * | country | rows | admin1 | admin2 | usable here |
295
+ * | ---------- | --------: | -----: | -----: | ------------------------------- |
296
+ * | PT, MX, IN | 145k–207k | 100% | 100% | yes |
297
+ * | BD, LK | 1.3k–1.8k | 100% | 100% | yes |
298
+ * | PH | 2,317 | 88% | 88% | yes, 88% of rows |
299
+ * | PK | 2,563 | 100% | **0%** | NO — no city column |
300
+ * | TH | 903 | 100% | **1%** | NO — effectively no city column |
301
+ * | ID | 81,058 | **0%** | 0% | NO — no region either |
302
+ * | ZA | 3,920 | **0%** | — | NO |
303
+ *
304
+ * A country in the NO rows yields zero from this reader, and that is the correct outcome rather than a gap to route
305
+ * around: taking column 3 as the locality is what made the v4.8.0 shard train `Mahatma Gandhi Road` as a city. If one
306
+ * of them is wanted, it needs a city column from somewhere else, not a relaxed mapping.
307
+ *
308
+ * NOT PUBLISHED AT ALL by GeoNames, checked the same day: VE, VN, NP, MM, KH. Those are acquisition questions, and for
309
+ * VE specifically OpenAddresses 404s too — see the arc retrospective.
310
+ *
311
+ * Three source properties a caller cannot see from a row count, all handled here. Hyphen-format countries publish each
312
+ * code TWICE (`3750-000` and `3750000`, exactly 2.00× for PT and PL), so the first surface of a code wins and its twin
313
+ * is dropped. Some countries populate the place but not admin1 — ZA is 100% place, 0% region — which yields nothing
314
+ * this shard can use, so those rows are dropped rather than emitted with a blank region. And the "place name" is often
315
+ * a SUB-locality, which {@link createKnownLocalityGate} filters.
316
+ */
317
+ export async function readTriplesFromGeonames(
318
+ country: string,
319
+ path: string,
320
+ countryName: string,
321
+ options: { isKnownLocality?: (name: string) => boolean } = {}
322
+ ): Promise<PostcodeTriple[]> {
323
+ const convention = POSTCODE_CONVENTIONS.get(country)
324
+
325
+ if (!convention || !existsSync(path)) return []
326
+
327
+ const isKnownLocality = options.isKnownLocality ?? createKnownLocalityGate(country)
328
+ const out: PostcodeTriple[] = []
329
+ const seen = new Set<string>()
330
+
331
+ for await (const cells of TSVSpliterator.fromAsync(path, { header: false }) as AsyncIterable<string[]>) {
332
+ const postcode = (cells[GEONAMES_COL.postcode] ?? "").trim()
333
+ const dependentLocality = (cells[GEONAMES_COL.place] ?? "").trim()
334
+ const locality = (cells[GEONAMES_COL.admin2] ?? "").trim()
335
+ const region = (cells[GEONAMES_COL.admin1] ?? "").trim()
336
+
337
+ if (!postcode || !locality || !region) continue
338
+
339
+ // The gate applies to the LOCALITY — admin2 — not to the fine-grained name, which is expected to be a street or
340
+ // a colonia and is emitted as the dependent locality rather than dropped.
341
+ if (!isKnownLocality(locality)) continue
342
+
343
+ // A dependent locality that merely repeats its parent teaches a doubled segment, not a boundary.
344
+ const dep = dependentLocality && dependentLocality !== locality ? dependentLocality : ""
345
+
346
+ // The bare twin of a punctuated code carries no new fact, and keeping both doubles the country's weight.
347
+ const key = `${postcode.replaceAll("-", "")} ${locality} ${dep}`
348
+
349
+ if (seen.has(key)) continue
350
+
351
+ seen.add(key)
352
+
353
+ out.push({
354
+ postcode,
355
+ ...(dep ? { dependentLocality: dep } : {}),
356
+ locality,
357
+ region,
358
+ country: countryName,
359
+ cc: country,
360
+ locale: convention.locale,
361
+ postcodePlacement: convention.placement,
362
+ })
363
+ }
364
+
365
+ return out
366
+ }
367
+
368
+ /**
369
+ * Resolve a GeoNames export path under the standard fetch out-root.
370
+ */
371
+ export function geonamesPostalPath(country: string, sourcesRoot?: string): string {
372
+ const root = sourcesRoot ?? String(dataRootPath("corpus", "sources"))
373
+
374
+ return join(root, "geonames-postal", `${country.toUpperCase()}.txt`)
375
+ }
@@ -23,10 +23,9 @@ import { join } from "node:path"
23
23
  import { parseJSONStrict } from "@mailwoman/core/objects"
24
24
  import { JSONSpliterator } from "spliterator"
25
25
 
26
- import { alignRow } from "../align.ts"
27
- import type { ShardManifest } from "../parquet.ts"
28
- import { PARQUET_COLUMNS, ROW_GROUP_SIZE, SHARD_COMPRESSION, writeShards } from "../parquet.ts"
29
- import type { CanonicalRow, LabeledRow } from "../types.ts"
26
+ import type { CanonicalRow, LabeledRow } from "#types"
27
+ import { alignRow, PARQUET_COLUMNS, ROW_GROUP_SIZE, SHARD_COMPRESSION, writeShards } from "#utils"
28
+ import type { ShardManifest } from "#utils"
30
29
 
31
30
  export interface ShardKryptoniteOptions {
32
31
  jsonl: string
@@ -108,7 +107,7 @@ export async function buildKryptoniteShard(
108
107
  // first_source_id-prefix inference). Without this, deepseek-kryptonite IDs would have
109
108
  // to match a prefix in KNOWN_SOURCE_PREFIXES — we add it there too as a belt-and-braces.
110
109
  for (const sh of newManifest.shards) {
111
- ;(sh as unknown as { source: string }).source = source
110
+ sh.source = source
112
111
  }
113
112
 
114
113
  // Compose the final corpus-v0.4.0 manifest: every shard from base + the new shard(s).
@@ -36,11 +36,19 @@ import { parseJSONStrict } from "@mailwoman/core/objects"
36
36
  import { mailwomanDataRoot, sha256File } from "@mailwoman/core/utils"
37
37
  import { JSONSpliterator } from "spliterator"
38
38
 
39
- import { alignRow } from "../align.ts"
39
+ import type { CanonicalRow, LabeledRow } from "#types"
40
+ import {
41
+ alignRow,
42
+ appendShape,
43
+ LABELED_ROW_SCHEMA,
44
+ PARQUET_COLUMNS,
45
+ ROW_GROUP_SIZE,
46
+ rowToParquet,
47
+ SHARD_COMPRESSION,
48
+ } from "#utils"
49
+ import type { ParquetRow, ShardDescriptor, ShardManifest } from "#utils"
50
+
40
51
  import { ParquetWriter } from "../parquet-wrapper/index.ts"
41
- import type { ParquetRow, ShardDescriptor, ShardManifest } from "../parquet.ts"
42
- import { LABELED_ROW_SCHEMA, PARQUET_COLUMNS, ROW_GROUP_SIZE, rowToParquet, SHARD_COMPRESSION } from "../parquet.ts"
43
- import type { CanonicalRow, LabeledRow } from "../types.ts"
44
52
 
45
53
  export interface ShardTranslitOptions {
46
54
  jsonl: string
@@ -77,33 +85,6 @@ function toCanonicalRow(raw: Record<string, unknown>, corpusVersion: string): Ca
77
85
  }
78
86
  }
79
87
 
80
- function appendShape(row: ParquetRow): Record<string, unknown> {
81
- const out: Record<string, unknown> = {
82
- raw: row.raw,
83
- tokens: row.tokens,
84
- labels: row.labels,
85
- country: row.country,
86
- source: row.source,
87
- source_id: row.source_id,
88
- corpus_version: row.corpus_version,
89
- license: row.license,
90
- }
91
-
92
- if (row.locale !== null) {
93
- out.locale = row.locale
94
- }
95
-
96
- if (row.synth_method !== null) {
97
- out.synth_method = row.synth_method
98
- }
99
-
100
- if (row.synth_base_id !== null) {
101
- out.synth_base_id = row.synth_base_id
102
- }
103
-
104
- return out
105
- }
106
-
107
88
  /**
108
89
  * Write one shard for a single source slug. Returns the populated ShardDescriptor + a list of quarantine reasons for
109
90
  * rows that failed alignment.
@@ -127,7 +108,7 @@ async function writeOneShard(
127
108
 
128
109
  for (const row of rows) {
129
110
  const pq = rowToParquet(row)
130
- await writer.appendRow(appendShape(pq) as unknown as ParquetRow)
111
+ await writer.appendRow(appendShape(pq))
131
112
 
132
113
  if (firstSourceID === "") {
133
114
  firstSourceID = row.source_id
@@ -151,9 +132,8 @@ async function writeOneShard(
151
132
  sha256,
152
133
  first_source_id: firstSourceID,
153
134
  last_source_id: lastSourceID,
154
- // Stamp source so audit.ts attributes the shard without falling back to filename-prefix
155
- // inference. Cast widens ShardDescriptor; the field is read by audit.ts.
156
- ...({ source } as Record<string, string>),
135
+ // Stamp source so audit.ts attributes the shard without falling back to filename-prefix inference.
136
+ source,
157
137
  }
158
138
  }
159
139
 
@@ -168,6 +148,7 @@ export async function buildTranslitShard(
168
148
  report?: (line: string) => void
169
149
  ): Promise<void> {
170
150
  const corpusVersion = options.corpusVersion ?? "0.4.0"
151
+ // This is a portable manifest namespace, not a host filesystem default.
171
152
  const canonicalPathPrefix = options.canonicalPathPrefix ?? "/data/"
172
153
  const legacyPathPrefix = options.legacyPathPrefix ?? `${mailwomanDataRoot()}/`
173
154