@mailwoman/corpus 8.6.0 → 9.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 (310) hide show
  1. package/README.md +1 -1
  2. package/out/src/adapter.d.ts +41 -0
  3. package/out/src/adapter.d.ts.map +1 -1
  4. package/out/src/adapter.js +46 -1
  5. package/out/src/adapter.js.map +1 -1
  6. package/out/src/adapters/ban/adapter.d.ts +4 -3
  7. package/out/src/adapters/ban/adapter.d.ts.map +1 -1
  8. package/out/src/adapters/ban/adapter.js +60 -67
  9. package/out/src/adapters/ban/adapter.js.map +1 -1
  10. package/out/src/adapters/ban/street-decompose.d.ts.map +1 -1
  11. package/out/src/adapters/ban/street-decompose.js +18 -25
  12. package/out/src/adapters/ban/street-decompose.js.map +1 -1
  13. package/out/src/adapters/fcc-bdc/adapter.d.ts +0 -6
  14. package/out/src/adapters/fcc-bdc/adapter.d.ts.map +1 -1
  15. package/out/src/adapters/fcc-bdc/adapter.js +2 -24
  16. package/out/src/adapters/fcc-bdc/adapter.js.map +1 -1
  17. package/out/src/adapters/geonames/adapter.d.ts.map +1 -1
  18. package/out/src/adapters/geonames/adapter.js +70 -76
  19. package/out/src/adapters/geonames/adapter.js.map +1 -1
  20. package/out/src/adapters/geonames-postal/adapter.d.ts.map +1 -1
  21. package/out/src/adapters/geonames-postal/adapter.js +44 -49
  22. package/out/src/adapters/geonames-postal/adapter.js.map +1 -1
  23. package/out/src/adapters/gnaf/adapter.d.ts.map +1 -1
  24. package/out/src/adapters/gnaf/adapter.js +4 -7
  25. package/out/src/adapters/gnaf/adapter.js.map +1 -1
  26. package/out/src/adapters/gnaf/assemble.d.ts.map +1 -1
  27. package/out/src/adapters/gnaf/assemble.js +6 -11
  28. package/out/src/adapters/gnaf/assemble.js.map +1 -1
  29. package/out/src/adapters/openaddresses/adapter.d.ts.map +1 -1
  30. package/out/src/adapters/openaddresses/adapter.js +2 -7
  31. package/out/src/adapters/openaddresses/adapter.js.map +1 -1
  32. package/out/src/adapters/overture/adapter.d.ts.map +1 -1
  33. package/out/src/adapters/overture/adapter.js +3 -7
  34. package/out/src/adapters/overture/adapter.js.map +1 -1
  35. package/out/src/adapters/state-hi-schools/adapter.d.ts.map +1 -1
  36. package/out/src/adapters/state-hi-schools/adapter.js +55 -73
  37. package/out/src/adapters/state-hi-schools/adapter.js.map +1 -1
  38. package/out/src/adapters/state-ia-contractors/adapter.d.ts.map +1 -1
  39. package/out/src/adapters/state-ia-contractors/adapter.js +58 -76
  40. package/out/src/adapters/state-ia-contractors/adapter.js.map +1 -1
  41. package/out/src/adapters/state-ny-notaries/adapter.d.ts.map +1 -1
  42. package/out/src/adapters/state-ny-notaries/adapter.js +67 -85
  43. package/out/src/adapters/state-ny-notaries/adapter.js.map +1 -1
  44. package/out/src/adapters/state-tx-notaries/adapter.d.ts.map +1 -1
  45. package/out/src/adapters/state-tx-notaries/adapter.js +69 -87
  46. package/out/src/adapters/state-tx-notaries/adapter.js.map +1 -1
  47. package/out/src/adapters/synth-po-box/adapter.d.ts.map +1 -1
  48. package/out/src/adapters/synth-po-box/adapter.js +7 -15
  49. package/out/src/adapters/synth-po-box/adapter.js.map +1 -1
  50. package/out/src/adapters/tiger/street-decompose.d.ts.map +1 -1
  51. package/out/src/adapters/tiger/street-decompose.js +18 -27
  52. package/out/src/adapters/tiger/street-decompose.js.map +1 -1
  53. package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts +3 -3
  54. package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts.map +1 -1
  55. package/out/src/adapters/usgov-hrsa-fqhc/adapter.js +62 -80
  56. package/out/src/adapters/usgov-hrsa-fqhc/adapter.js.map +1 -1
  57. package/out/src/adapters/usgov-imls-pls/adapter.d.ts.map +1 -1
  58. package/out/src/adapters/usgov-imls-pls/adapter.js +60 -82
  59. package/out/src/adapters/usgov-imls-pls/adapter.js.map +1 -1
  60. package/out/src/adapters/usgov-irs-bmf/adapter.d.ts.map +1 -1
  61. package/out/src/adapters/usgov-irs-bmf/adapter.js +58 -65
  62. package/out/src/adapters/usgov-irs-bmf/adapter.js.map +1 -1
  63. package/out/src/adapters/usgov-nad/adapter.d.ts.map +1 -1
  64. package/out/src/adapters/usgov-nad/adapter.js +5 -8
  65. package/out/src/adapters/usgov-nad/adapter.js.map +1 -1
  66. package/out/src/adapters/usgov-nppes/adapter.d.ts.map +1 -1
  67. package/out/src/adapters/usgov-nppes/adapter.js +58 -76
  68. package/out/src/adapters/usgov-nppes/adapter.js.map +1 -1
  69. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.d.ts.map +1 -1
  70. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js +51 -71
  71. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js.map +1 -1
  72. package/out/src/adapters/wof-admin-jp/adapter.d.ts.map +1 -1
  73. package/out/src/adapters/wof-admin-jp/adapter.js +24 -6
  74. package/out/src/adapters/wof-admin-jp/adapter.js.map +1 -1
  75. package/out/src/build.d.ts.map +1 -1
  76. package/out/src/build.js +2 -1
  77. package/out/src/build.js.map +1 -1
  78. package/out/src/golden.d.ts +5 -0
  79. package/out/src/golden.d.ts.map +1 -1
  80. package/out/src/golden.js +23 -9
  81. package/out/src/golden.js.map +1 -1
  82. package/out/src/parquet-wrapper/reader.d.ts +8 -0
  83. package/out/src/parquet-wrapper/reader.d.ts.map +1 -1
  84. package/out/src/parquet-wrapper/reader.js +16 -0
  85. package/out/src/parquet-wrapper/reader.js.map +1 -1
  86. package/out/src/parquet-wrapper/schema.d.ts +4 -0
  87. package/out/src/parquet-wrapper/schema.d.ts.map +1 -1
  88. package/out/src/parquet-wrapper/schema.js.map +1 -1
  89. package/out/src/parquet.d.ts.map +1 -1
  90. package/out/src/parquet.js +2 -13
  91. package/out/src/parquet.js.map +1 -1
  92. package/out/src/shard-recipes/anchor-absorption.d.ts.map +1 -1
  93. package/out/src/shard-recipes/anchor-absorption.js +2 -1
  94. package/out/src/shard-recipes/anchor-absorption.js.map +1 -1
  95. package/out/src/shard-recipes/country-balanced.d.ts.map +1 -1
  96. package/out/src/shard-recipes/country-balanced.js +68 -60
  97. package/out/src/shard-recipes/country-balanced.js.map +1 -1
  98. package/out/src/shard-recipes/fr-fragment.d.ts.map +1 -1
  99. package/out/src/shard-recipes/fr-fragment.js +8 -5
  100. package/out/src/shard-recipes/fr-fragment.js.map +1 -1
  101. package/out/src/shard-recipes/fr-order.d.ts.map +1 -1
  102. package/out/src/shard-recipes/fr-order.js +14 -58
  103. package/out/src/shard-recipes/fr-order.js.map +1 -1
  104. package/out/src/shard-recipes/german.d.ts.map +1 -1
  105. package/out/src/shard-recipes/german.js +11 -58
  106. package/out/src/shard-recipes/german.js.map +1 -1
  107. package/out/src/shard-recipes/index.d.ts.map +1 -1
  108. package/out/src/shard-recipes/index.js +2 -0
  109. package/out/src/shard-recipes/index.js.map +1 -1
  110. package/out/src/shard-recipes/intersection.d.ts +2 -2
  111. package/out/src/shard-recipes/intersection.d.ts.map +1 -1
  112. package/out/src/shard-recipes/intersection.js +24 -62
  113. package/out/src/shard-recipes/intersection.js.map +1 -1
  114. package/out/src/shard-recipes/locale.d.ts.map +1 -1
  115. package/out/src/shard-recipes/locale.js +14 -16
  116. package/out/src/shard-recipes/locale.js.map +1 -1
  117. package/out/src/shard-recipes/no-fragment.d.ts.map +1 -1
  118. package/out/src/shard-recipes/no-fragment.js +8 -5
  119. package/out/src/shard-recipes/no-fragment.js.map +1 -1
  120. package/out/src/shard-recipes/no-street-led.d.ts.map +1 -1
  121. package/out/src/shard-recipes/no-street-led.js +8 -5
  122. package/out/src/shard-recipes/no-street-led.js.map +1 -1
  123. package/out/src/shard-recipes/po-box-cedex.d.ts +3 -3
  124. package/out/src/shard-recipes/po-box-cedex.d.ts.map +1 -1
  125. package/out/src/shard-recipes/po-box-cedex.js +51 -94
  126. package/out/src/shard-recipes/po-box-cedex.js.map +1 -1
  127. package/out/src/shard-recipes/scaffold.d.ts +59 -9
  128. package/out/src/shard-recipes/scaffold.d.ts.map +1 -1
  129. package/out/src/shard-recipes/scaffold.js +51 -30
  130. package/out/src/shard-recipes/scaffold.js.map +1 -1
  131. package/out/src/shard-recipes/street-affix.d.ts.map +1 -1
  132. package/out/src/shard-recipes/street-affix.js +56 -82
  133. package/out/src/shard-recipes/street-affix.js.map +1 -1
  134. package/out/src/shard-recipes/sub-venue-sources.d.ts +231 -0
  135. package/out/src/shard-recipes/sub-venue-sources.d.ts.map +1 -0
  136. package/out/src/shard-recipes/sub-venue-sources.js +669 -0
  137. package/out/src/shard-recipes/sub-venue-sources.js.map +1 -0
  138. package/out/src/shard-recipes/sub-venue.d.ts +269 -0
  139. package/out/src/shard-recipes/sub-venue.d.ts.map +1 -0
  140. package/out/src/shard-recipes/sub-venue.js +709 -0
  141. package/out/src/shard-recipes/sub-venue.js.map +1 -0
  142. package/out/src/shard-recipes/unit.d.ts +1 -1
  143. package/out/src/shard-recipes/unit.d.ts.map +1 -1
  144. package/out/src/shard-recipes/unit.js +22 -64
  145. package/out/src/shard-recipes/unit.js.map +1 -1
  146. package/out/src/split.d.ts +7 -0
  147. package/out/src/split.d.ts.map +1 -1
  148. package/out/src/split.js +7 -0
  149. package/out/src/split.js.map +1 -1
  150. package/out/src/synthesize.d.ts.map +1 -1
  151. package/out/src/synthesize.js +1 -12
  152. package/out/src/synthesize.js.map +1 -1
  153. package/out/src/tools/align-shard.d.ts.map +1 -1
  154. package/out/src/tools/align-shard.js +3 -7
  155. package/out/src/tools/align-shard.js.map +1 -1
  156. package/out/src/tools/audit.d.ts.map +1 -1
  157. package/out/src/tools/audit.js +4 -4
  158. package/out/src/tools/audit.js.map +1 -1
  159. package/out/src/tools/corpus-stats.d.ts +2 -2
  160. package/out/src/tools/corpus-stats.d.ts.map +1 -1
  161. package/out/src/tools/corpus-stats.js +18 -31
  162. package/out/src/tools/corpus-stats.js.map +1 -1
  163. package/out/src/tools/fetch/download.d.ts.map +1 -1
  164. package/out/src/tools/fetch/download.js +5 -6
  165. package/out/src/tools/fetch/download.js.map +1 -1
  166. package/out/src/tools/fetch/imls-pls.d.ts.map +1 -1
  167. package/out/src/tools/fetch/imls-pls.js +2 -2
  168. package/out/src/tools/fetch/imls-pls.js.map +1 -1
  169. package/out/src/tools/fetch/index.d.ts +14 -1
  170. package/out/src/tools/fetch/index.d.ts.map +1 -1
  171. package/out/src/tools/fetch/index.js +14 -1
  172. package/out/src/tools/fetch/index.js.map +1 -1
  173. package/out/src/tools/fetch/nad.d.ts +1 -1
  174. package/out/src/tools/fetch/nad.js +1 -1
  175. package/out/src/tools/fetch/nppes.d.ts.map +1 -1
  176. package/out/src/tools/fetch/nppes.js +2 -1
  177. package/out/src/tools/fetch/nppes.js.map +1 -1
  178. package/out/src/tools/fetch/openaddresses.d.ts +1 -1
  179. package/out/src/tools/fetch/openaddresses.js +1 -1
  180. package/out/src/tools/fetch/ourairports.d.ts +45 -0
  181. package/out/src/tools/fetch/ourairports.d.ts.map +1 -0
  182. package/out/src/tools/fetch/ourairports.js +123 -0
  183. package/out/src/tools/fetch/ourairports.js.map +1 -0
  184. package/out/src/tools/fetch/wikidata-subvenue.d.ts +171 -0
  185. package/out/src/tools/fetch/wikidata-subvenue.d.ts.map +1 -0
  186. package/out/src/tools/fetch/wikidata-subvenue.js +275 -0
  187. package/out/src/tools/fetch/wikidata-subvenue.js.map +1 -0
  188. package/out/src/tools/golden-expand.d.ts.map +1 -1
  189. package/out/src/tools/golden-expand.js +18 -16
  190. package/out/src/tools/golden-expand.js.map +1 -1
  191. package/out/src/tools/golden-promote.d.ts.map +1 -1
  192. package/out/src/tools/golden-promote.js +12 -6
  193. package/out/src/tools/golden-promote.js.map +1 -1
  194. package/out/src/tools/golden-relabel-street.d.ts +196 -0
  195. package/out/src/tools/golden-relabel-street.d.ts.map +1 -0
  196. package/out/src/tools/golden-relabel-street.js +513 -0
  197. package/out/src/tools/golden-relabel-street.js.map +1 -0
  198. package/out/src/tools/index.d.ts +4 -0
  199. package/out/src/tools/index.d.ts.map +1 -1
  200. package/out/src/tools/index.js +4 -0
  201. package/out/src/tools/index.js.map +1 -1
  202. package/out/src/tools/jsonl-to-parquet.d.ts.map +1 -1
  203. package/out/src/tools/jsonl-to-parquet.js +3 -2
  204. package/out/src/tools/jsonl-to-parquet.js.map +1 -1
  205. package/out/src/tools/lint-shard-vocab.d.ts.map +1 -1
  206. package/out/src/tools/lint-shard-vocab.js +3 -2
  207. package/out/src/tools/lint-shard-vocab.js.map +1 -1
  208. package/out/src/tools/lint-shard.d.ts +3 -3
  209. package/out/src/tools/lint-shard.d.ts.map +1 -1
  210. package/out/src/tools/lint-shard.js +23 -32
  211. package/out/src/tools/lint-shard.js.map +1 -1
  212. package/out/src/tools/overlay-manifest.d.ts.map +1 -1
  213. package/out/src/tools/overlay-manifest.js +2 -1
  214. package/out/src/tools/overlay-manifest.js.map +1 -1
  215. package/out/src/tools/overture-subvenue.d.ts +112 -0
  216. package/out/src/tools/overture-subvenue.d.ts.map +1 -0
  217. package/out/src/tools/overture-subvenue.js +144 -0
  218. package/out/src/tools/overture-subvenue.js.map +1 -0
  219. package/out/src/tools/shard-kryptonite.d.ts +1 -1
  220. package/out/src/tools/shard-kryptonite.d.ts.map +1 -1
  221. package/out/src/tools/shard-kryptonite.js +5 -4
  222. package/out/src/tools/shard-kryptonite.js.map +1 -1
  223. package/out/src/tools/shard-translit.d.ts +6 -3
  224. package/out/src/tools/shard-translit.d.ts.map +1 -1
  225. package/out/src/tools/shard-translit.js +8 -6
  226. package/out/src/tools/shard-translit.js.map +1 -1
  227. package/out/src/tools/sub-venue-lexicon.d.ts +507 -0
  228. package/out/src/tools/sub-venue-lexicon.d.ts.map +1 -0
  229. package/out/src/tools/sub-venue-lexicon.js +817 -0
  230. package/out/src/tools/sub-venue-lexicon.js.map +1 -0
  231. package/out/src/tools/sub-venue-promotions.d.ts +94 -0
  232. package/out/src/tools/sub-venue-promotions.d.ts.map +1 -0
  233. package/out/src/tools/sub-venue-promotions.js +266 -0
  234. package/out/src/tools/sub-venue-promotions.js.map +1 -0
  235. package/out/src/wof-json.d.ts +2 -2
  236. package/out/src/wof-json.d.ts.map +1 -1
  237. package/out/src/wof-json.js +5 -8
  238. package/out/src/wof-json.js.map +1 -1
  239. package/package.json +8 -8
  240. package/src/adapter.ts +58 -1
  241. package/src/adapters/ban/adapter.ts +55 -65
  242. package/src/adapters/ban/street-decompose.ts +17 -25
  243. package/src/adapters/fcc-bdc/adapter.ts +2 -33
  244. package/src/adapters/geonames/adapter.ts +64 -72
  245. package/src/adapters/geonames-postal/adapter.ts +42 -50
  246. package/src/adapters/gnaf/adapter.ts +4 -7
  247. package/src/adapters/gnaf/assemble.ts +6 -10
  248. package/src/adapters/openaddresses/adapter.ts +2 -7
  249. package/src/adapters/overture/adapter.ts +3 -6
  250. package/src/adapters/state-hi-schools/adapter.ts +50 -73
  251. package/src/adapters/state-ia-contractors/adapter.ts +52 -76
  252. package/src/adapters/state-ny-notaries/adapter.ts +61 -85
  253. package/src/adapters/state-tx-notaries/adapter.ts +60 -84
  254. package/src/adapters/synth-po-box/adapter.ts +7 -17
  255. package/src/adapters/tiger/street-decompose.ts +21 -31
  256. package/src/adapters/usgov-hrsa-fqhc/adapter.ts +61 -84
  257. package/src/adapters/usgov-imls-pls/adapter.ts +54 -82
  258. package/src/adapters/usgov-irs-bmf/adapter.ts +53 -63
  259. package/src/adapters/usgov-nad/adapter.ts +5 -8
  260. package/src/adapters/usgov-nppes/adapter.ts +51 -75
  261. package/src/adapters/usgov-samhsa-treatment-locator/adapter.ts +45 -71
  262. package/src/adapters/wof-admin-jp/adapter.ts +34 -8
  263. package/src/build.ts +2 -1
  264. package/src/golden.ts +24 -9
  265. package/src/parquet-wrapper/reader.ts +19 -0
  266. package/src/parquet-wrapper/schema.ts +4 -0
  267. package/src/parquet.ts +3 -16
  268. package/src/shard-recipes/anchor-absorption.ts +2 -1
  269. package/src/shard-recipes/country-balanced.ts +69 -70
  270. package/src/shard-recipes/fr-fragment.ts +10 -7
  271. package/src/shard-recipes/fr-order.ts +13 -63
  272. package/src/shard-recipes/german.ts +12 -65
  273. package/src/shard-recipes/index.ts +2 -0
  274. package/src/shard-recipes/intersection.ts +32 -60
  275. package/src/shard-recipes/locale.ts +14 -16
  276. package/src/shard-recipes/no-fragment.ts +10 -7
  277. package/src/shard-recipes/no-street-led.ts +10 -7
  278. package/src/shard-recipes/po-box-cedex.ts +68 -119
  279. package/src/shard-recipes/scaffold.ts +82 -28
  280. package/src/shard-recipes/street-affix.ts +58 -95
  281. package/src/shard-recipes/sub-venue-sources.ts +895 -0
  282. package/src/shard-recipes/sub-venue.ts +982 -0
  283. package/src/shard-recipes/unit.ts +22 -70
  284. package/src/split.ts +7 -0
  285. package/src/synthesize.ts +1 -15
  286. package/src/tools/align-shard.ts +3 -6
  287. package/src/tools/audit.ts +6 -5
  288. package/src/tools/corpus-stats.ts +25 -33
  289. package/src/tools/fetch/download.ts +7 -5
  290. package/src/tools/fetch/imls-pls.ts +2 -2
  291. package/src/tools/fetch/index.ts +14 -1
  292. package/src/tools/fetch/nad.ts +1 -1
  293. package/src/tools/fetch/nppes.ts +2 -1
  294. package/src/tools/fetch/openaddresses.ts +1 -1
  295. package/src/tools/fetch/ourairports.ts +166 -0
  296. package/src/tools/fetch/wikidata-subvenue.ts +386 -0
  297. package/src/tools/golden-expand.ts +18 -13
  298. package/src/tools/golden-promote.ts +15 -6
  299. package/src/tools/golden-relabel-street.ts +748 -0
  300. package/src/tools/index.ts +4 -0
  301. package/src/tools/jsonl-to-parquet.ts +3 -2
  302. package/src/tools/lint-shard-vocab.ts +3 -2
  303. package/src/tools/lint-shard.ts +33 -36
  304. package/src/tools/overlay-manifest.ts +2 -1
  305. package/src/tools/overture-subvenue.ts +215 -0
  306. package/src/tools/shard-kryptonite.ts +5 -4
  307. package/src/tools/shard-translit.ts +12 -7
  308. package/src/tools/sub-venue-lexicon.ts +1250 -0
  309. package/src/tools/sub-venue-promotions.ts +330 -0
  310. package/src/wof-json.ts +5 -8
@@ -15,11 +15,9 @@
15
15
  * License: stamped `"Public Domain"` per Texas state government open-data terms.
16
16
  */
17
17
 
18
- import { createReadStream } from "node:fs"
18
+ import { CSVSpliterator } from "spliterator"
19
19
 
20
- import { parse as csvParse } from "csv-parse"
21
-
22
- import { stableSourceID } from "../../adapter.ts"
20
+ import { splitStreetLine, stableSourceID } from "../../adapter.ts"
23
21
  import { lookupStateAbbreviation } from "../../codex/us-fips-state.ts"
24
22
  import { reconcileComponents } from "../../format.ts"
25
23
  import type { AdapterOptions, CanonicalRow, CorpusAdapter } from "../../types.ts"
@@ -35,8 +33,6 @@ export const STATE_TX_NOTARIES_ADAPTER_ID = "state-tx-notaries"
35
33
  */
36
34
  export const STATE_TX_NOTARIES_DEFAULT_LICENSE = "Public Domain"
37
35
 
38
- const HOUSE_NUMBER_PREFIX = /^(\d+(?:-\d+)?[A-Za-z]?)\s+(.+)$/
39
-
40
36
  /**
41
37
  * Match trailing "CITY, ST ZIP" or "CITY, ST" at the end of an address line.
42
38
  */
@@ -49,17 +45,6 @@ interface TxNotaryRow {
49
45
  Address: string
50
46
  }
51
47
 
52
- function splitAddress(address: string): { house_number?: string; street: string } | null {
53
- const trimmed = address.trim()
54
-
55
- if (!trimmed) return null
56
- const m = HOUSE_NUMBER_PREFIX.exec(trimmed)
57
-
58
- if (m) return { house_number: m[1], street: m[2]!.trim() }
59
-
60
- return { street: trimmed }
61
- }
62
-
63
48
  export function createStateTxNotariesAdapter(): CorpusAdapter {
64
49
  return {
65
50
  id: STATE_TX_NOTARIES_ADAPTER_ID,
@@ -72,99 +57,90 @@ export function createStateTxNotariesAdapter(): CorpusAdapter {
72
57
  throw new Error(`state-tx-notaries adapter: only US supported, got country=${opts.country}`)
73
58
  }
74
59
 
75
- const stream = createReadStream(opts.inputPath, { encoding: "utf8" })
76
-
77
- const parser = stream.pipe(
78
- csvParse({
79
- columns: true,
80
- skip_empty_lines: true,
81
- relax_quotes: true,
82
- relax_column_count: true,
83
- })
84
- )
60
+ const rows = CSVSpliterator.fromAsync(opts.inputPath, {
61
+ mode: "object",
62
+ normalizeKeys: false,
63
+ enableQuoteHandling: true,
64
+ })
85
65
 
86
66
  let emitted = 0
87
67
 
88
- try {
89
- for await (const record of parser as AsyncIterable<TxNotaryRow>) {
90
- if (opts.signal?.aborted) break
91
-
92
- if (opts.limit !== undefined && emitted >= opts.limit) break
68
+ for await (const record of rows as AsyncIterable<TxNotaryRow>) {
69
+ if (opts.signal?.aborted) break
93
70
 
94
- const rawAddress = (record.Address ?? "").trim()
71
+ if (opts.limit !== undefined && emitted >= opts.limit) break
95
72
 
96
- if (!rawAddress) continue
73
+ const rawAddress = (record.Address ?? "").trim()
97
74
 
98
- const firstName = (record["First Name"] ?? "").trim()
99
- const lastName = (record["Last Name"] ?? "").trim()
100
- const notaryID = (record["Notary ID"] ?? "").trim()
75
+ if (!rawAddress) continue
101
76
 
102
- // Parse embedded city/state/zip from the trailing portion of the address.
103
- // Addresses look like: "1215 MCMILLAN DR\nCEDAR HILL, TX 75104"
104
- const addrSingleLine = rawAddress.replaceAll("\n", ", ")
105
- const cszMatch = CITY_STATE_ZIP_SUFFIX.exec(addrSingleLine)
77
+ const firstName = (record["First Name"] ?? "").trim()
78
+ const lastName = (record["Last Name"] ?? "").trim()
79
+ const notaryID = (record["Notary ID"] ?? "").trim()
106
80
 
107
- if (!cszMatch) continue
81
+ // Parse embedded city/state/zip from the trailing portion of the address.
82
+ // Addresses look like: "1215 MCMILLAN DR\nCEDAR HILL, TX 75104"
83
+ const addrSingleLine = rawAddress.replaceAll("\n", ", ")
84
+ const cszMatch = CITY_STATE_ZIP_SUFFIX.exec(addrSingleLine)
108
85
 
109
- const city = (cszMatch[1] ?? "").trim()
110
- const stateAbbr = (cszMatch[2] ?? "").trim()
111
- const zip = (cszMatch[3] ?? "").trim()
86
+ if (!cszMatch) continue
112
87
 
113
- if (!city || !stateAbbr) continue
88
+ const city = (cszMatch[1] ?? "").trim()
89
+ const stateAbbr = (cszMatch[2] ?? "").trim()
90
+ const zip = (cszMatch[3] ?? "").trim()
114
91
 
115
- const state = lookupStateAbbreviation(stateAbbr)
92
+ if (!city || !stateAbbr) continue
116
93
 
117
- if (!state) continue
94
+ const state = lookupStateAbbreviation(stateAbbr)
118
95
 
119
- // Extract the street portion (everything before the city/state/zip)
120
- const streetPortion = addrSingleLine.slice(0, cszMatch.index).replace(/,\s*$/, "").trim()
96
+ if (!state) continue
121
97
 
122
- if (!streetPortion) continue
98
+ // Extract the street portion (everything before the city/state/zip)
99
+ const streetPortion = addrSingleLine.slice(0, cszMatch.index).replace(/,\s*$/, "").trim()
123
100
 
124
- const split = splitAddress(streetPortion)
101
+ if (!streetPortion) continue
125
102
 
126
- if (!split) continue
103
+ const split = splitStreetLine(streetPortion)
127
104
 
128
- const venue = [firstName, lastName].filter(Boolean).join(" ") || undefined
105
+ if (!split) continue
129
106
 
130
- const components: CanonicalRow["components"] = {
131
- ...(venue ? { venue } : {}),
132
- ...(split.house_number ? { house_number: split.house_number } : {}),
133
- street: split.street,
134
- locality: city,
135
- region: state.abbreviation,
136
- ...(zip ? { postcode: zip } : {}),
137
- }
107
+ const venue = [firstName, lastName].filter(Boolean).join(" ") || undefined
138
108
 
139
- const streetPart = [split.house_number, split.street].filter(Boolean).join(" ").trim()
109
+ const components: CanonicalRow["components"] = {
110
+ ...(venue ? { venue } : {}),
111
+ ...(split.house_number ? { house_number: split.house_number } : {}),
112
+ street: split.street,
113
+ locality: city,
114
+ region: state.abbreviation,
115
+ ...(zip ? { postcode: zip } : {}),
116
+ }
140
117
 
141
- const raw = [venue, streetPart, [city, [stateAbbr, zip].filter(Boolean).join(" ")].filter(Boolean).join(", ")]
142
- .filter(Boolean)
143
- .join(", ")
118
+ const streetPart = [split.house_number, split.street].filter(Boolean).join(" ").trim()
144
119
 
145
- const aligned = reconcileComponents(components, raw)
120
+ const raw = [venue, streetPart, [city, [stateAbbr, zip].filter(Boolean).join(" ")].filter(Boolean).join(", ")]
121
+ .filter(Boolean)
122
+ .join(", ")
146
123
 
147
- if (Object.keys(aligned).length <= 2) continue
124
+ const aligned = reconcileComponents(components, raw)
148
125
 
149
- const sourceID = notaryID
150
- ? `${STATE_TX_NOTARIES_ADAPTER_ID}-${notaryID}`
151
- : stableSourceID(STATE_TX_NOTARIES_ADAPTER_ID, aligned)
126
+ if (Object.keys(aligned).length <= 2) continue
152
127
 
153
- yield {
154
- raw,
155
- components: aligned,
156
- country: "US",
157
- locale: "en-US",
158
- source: STATE_TX_NOTARIES_ADAPTER_ID,
159
- source_id: sourceID,
160
- corpus_version: "",
161
- license: STATE_TX_NOTARIES_DEFAULT_LICENSE,
162
- }
128
+ const sourceID = notaryID
129
+ ? `${STATE_TX_NOTARIES_ADAPTER_ID}-${notaryID}`
130
+ : stableSourceID(STATE_TX_NOTARIES_ADAPTER_ID, aligned)
163
131
 
164
- emitted++
132
+ yield {
133
+ raw,
134
+ components: aligned,
135
+ country: "US",
136
+ locale: "en-US",
137
+ source: STATE_TX_NOTARIES_ADAPTER_ID,
138
+ source_id: sourceID,
139
+ corpus_version: "",
140
+ license: STATE_TX_NOTARIES_DEFAULT_LICENSE,
165
141
  }
166
- } finally {
167
- stream.destroy()
142
+
143
+ emitted++
168
144
  }
169
145
  },
170
146
  }
@@ -20,6 +20,8 @@
20
20
  * carries a `street` field.
21
21
  */
22
22
 
23
+ import { tryParsingJSON } from "@mailwoman/core/objects"
24
+ import { makeLcg } from "@mailwoman/core/utils"
23
25
  import { TextSpliterator } from "spliterator"
24
26
 
25
27
  import { stableSourceID } from "../../adapter.ts"
@@ -73,16 +75,6 @@ export interface SynthPoBoxAdapterOptions {
73
75
  militaryRatio?: number
74
76
  }
75
77
 
76
- function makeRandom(seed: number): () => number {
77
- let s = seed
78
-
79
- return () => {
80
- s = (s * 1_664_525 + 1_013_904_223) % 4_294_967_296
81
-
82
- return s / 4_294_967_296
83
- }
84
- }
85
-
86
78
  export function createSynthPoBoxAdapter(opts: SynthPoBoxAdapterOptions = {}): CorpusAdapter {
87
79
  const variantsPerInput = opts.variantsPerInput ?? 1
88
80
  const pmbRatio = opts.pmbRatio ?? 0.15
@@ -95,10 +87,10 @@ export function createSynthPoBoxAdapter(opts: SynthPoBoxAdapterOptions = {}): Co
95
87
  "Synthetic PO box / PMB / Apartado / Boîte Postale rows. Consumes JSONL of (locality, region, postcode, country) tuples and emits locale-appropriate PO box variants.",
96
88
 
97
89
  async *rows(options: AdapterOptions): AsyncIterable<CanonicalRow> {
98
- const random = makeRandom(opts.seed ?? Date.now())
90
+ const random = makeLcg(opts.seed ?? Date.now())
99
91
 
100
- // TextSpliterator streams string lines; the per-line try/catch below keeps this reader
101
- // tolerant of malformed rows (skipped++), so TextSpliterator + explicit JSON.parse — not
92
+ // TextSpliterator streams string lines; the per-line tryParsingJSON below keeps this reader
93
+ // tolerant of malformed rows (skipped++), so TextSpliterator + a non-throwing parse — not
102
94
  // JSONSpliterator, which would throw on the first bad line.
103
95
  const lines = TextSpliterator.fromAsync(options.inputPath)
104
96
 
@@ -115,11 +107,9 @@ export function createSynthPoBoxAdapter(opts: SynthPoBoxAdapterOptions = {}): Co
115
107
 
116
108
  if (!trimmed) continue
117
109
 
118
- let input: PoBoxInputRow
110
+ const input = tryParsingJSON<PoBoxInputRow>(trimmed)
119
111
 
120
- try {
121
- input = JSON.parse(trimmed) as PoBoxInputRow
122
- } catch {
112
+ if (input === null) {
123
113
  skipped++
124
114
 
125
115
  continue
@@ -17,45 +17,35 @@
17
17
  */
18
18
 
19
19
  import { readFileSync } from "node:fs"
20
- import { resolve } from "node:path"
21
20
 
22
21
  import { resourceDictionaryPath } from "@mailwoman/core/utils"
22
+ import { TextSpliterator } from "spliterator"
23
23
 
24
24
  function loadDictionary(filename: string): Set<string> {
25
- // Resolve via the @mailwoman/core data directory.
26
- const candidates = [
27
- resourceDictionaryPath("libpostal", "en", filename),
28
- resourceDictionaryPath("libpostal", "en", filename),
29
- resolve(process.cwd(), "core/data/libpostal/dictionaries/en", filename),
30
- ]
31
-
32
- for (const path of candidates) {
33
- try {
34
- const text = readFileSync(path, "utf8")
35
- const set = new Set<string>()
36
-
37
- for (const line of text.split("\n")) {
38
- const trimmed = line.trim()
39
-
40
- if (!trimmed || trimmed.startsWith("#")) continue
41
-
42
- // libpostal format: canonical|abbr|abbr|... — index all forms
43
- for (const form of trimmed.split("|")) {
44
- const f = form.trim().toLowerCase()
45
-
46
- if (f) {
47
- set.add(f)
48
- }
49
- }
50
- }
25
+ // `resourceDictionaryPath` already resolves both layouts — `core/data/...` from source and from the
26
+ // packaged `out/` tree. The candidate list this replaced named it TWICE and then guessed a third path
27
+ // off `process.cwd()`, and swallowed every error while probing, so a corrupt dictionary reported as a
28
+ // missing one.
29
+ const text = readFileSync(resourceDictionaryPath("libpostal", "en", filename), "utf8")
30
+ const set = new Set<string>()
31
+
32
+ // The largest libpostal dictionary is 8.4 KB, and this runs once per process at module load.
33
+ for (const line of TextSpliterator.from(text)) {
34
+ const trimmed = line.trim()
35
+
36
+ if (!trimmed || trimmed.startsWith("#")) continue
51
37
 
52
- return set
53
- } catch {
54
- // try next candidate
38
+ // libpostal format: canonical|abbr|abbr|... — index all forms
39
+ for (const form of trimmed.split("|")) {
40
+ const f = form.trim().toLowerCase()
41
+
42
+ if (f) {
43
+ set.add(f)
44
+ }
55
45
  }
56
46
  }
57
47
 
58
- throw new Error(`Could not load libpostal dictionary: ${filename}`)
48
+ return set
59
49
  }
60
50
 
61
51
  const DIRECTIONALS = loadDictionary("directionals.txt")
@@ -8,9 +8,9 @@
8
8
  * Federally Qualified Health Centers (FQHCs) are HRSA-funded community health programs that
9
9
  * self-report site addresses to the HRSA Data Warehouse. The published CSV (`data.hrsa.gov`)
10
10
  * carries the site name, the postal-formatted street address, and the locality/region/postcode
11
- * quad. Phase 1.6 §1.2 (#22) selects this source for its adversarial-value-per-row: every
12
- * facility name is a human-typed venue string and the addresses pass through enough hands to
13
- * accumulate the abbreviation drift + suite designator chaos that pure gazetteer data does not.
11
+ * quad. The source earns its place on adversarial-value-per-row (#22): every facility name is a
12
+ * human-typed venue string and the addresses pass through enough hands to accumulate the
13
+ * abbreviation drift + suite designator chaos that pure gazetteer data does not.
14
14
  *
15
15
  * The adapter consumes a CSV file the operator pre-downloads. The HRSA data is published as a
16
16
  * single national CSV (~10K rows), small enough that the operator can re-fetch on every corpus
@@ -29,11 +29,9 @@
29
29
  * terms.
30
30
  */
31
31
 
32
- import { createReadStream } from "node:fs"
32
+ import { CSVSpliterator } from "spliterator"
33
33
 
34
- import { parse as csvParse } from "csv-parse"
35
-
36
- import { stableSourceID } from "../../adapter.ts"
34
+ import { splitStreetLine, stableSourceID } from "../../adapter.ts"
37
35
  import { lookupStateAbbreviation } from "../../codex/us-fips-state.ts"
38
36
  import { reconcileComponents } from "../../format.ts"
39
37
  import type { AdapterOptions, CanonicalRow, CorpusAdapter } from "../../types.ts"
@@ -70,23 +68,11 @@ interface HrsaSiteRow {
70
68
  * Split a "123 Main St Suite 4" surface form into `(house_number, street)`. The regex tolerates one trailing letter on
71
69
  * the number (`"123A Main St"`) and a hyphenated form (`"40-12 Bell Blvd"`); anything else falls back to street-only.
72
70
  *
73
- * Suite / Apt / Unit designators stay on `street` for Phase 1 — Mailwoman's `unit` component exists but the
74
- * address-formatter does not have a clean slot for it, and HRSA addresses do not separate the suite into its own
75
- * column. Leaving the surface form intact in `street` preserves the adversarial training signal (the model learns that
76
- * a trailing "Suite 4" is part of the road line in this distribution).
71
+ * Suite / Apt / Unit designators stay on `street` — Mailwoman's `unit` component exists but the address-formatter does
72
+ * not have a clean slot for it, and HRSA addresses do not separate the suite into its own column. Leaving the surface
73
+ * form intact in `street` preserves the adversarial training signal (the model learns that a trailing "Suite 4" is part
74
+ * of the road line in this distribution).
77
75
  */
78
- const HOUSE_NUMBER_PREFIX = /^(\d+(?:-\d+)?[A-Za-z]?)\s+(.+)$/
79
-
80
- function splitAddress(address: string): { house_number?: string; street: string } | null {
81
- const trimmed = address.trim()
82
-
83
- if (!trimmed) return null
84
- const m = HOUSE_NUMBER_PREFIX.exec(trimmed)
85
-
86
- if (m) return { house_number: m[1], street: m[2]!.trim() }
87
-
88
- return { street: trimmed }
89
- }
90
76
 
91
77
  /**
92
78
  * Compose the raw envelope-style address line. Format:
@@ -122,78 +108,69 @@ export function createUsgovHrsaFqhcAdapter(): CorpusAdapter {
122
108
  throw new Error(`usgov-hrsa-fqhc adapter: only US supported, got country=${opts.country}`)
123
109
  }
124
110
 
125
- const stream = createReadStream(opts.inputPath, { encoding: "utf8" })
126
-
127
- const parser = stream.pipe(
128
- csvParse({
129
- columns: true,
130
- skip_empty_lines: true,
131
- relax_quotes: true,
132
- relax_column_count: true,
133
- })
134
- )
111
+ const rows = CSVSpliterator.fromAsync(opts.inputPath, {
112
+ mode: "object",
113
+ normalizeKeys: false,
114
+ enableQuoteHandling: true,
115
+ })
135
116
 
136
117
  let emitted = 0
137
118
 
138
- try {
139
- for await (const record of parser as AsyncIterable<HrsaSiteRow>) {
140
- if (opts.signal?.aborted) break
141
-
142
- if (opts.limit !== undefined && emitted >= opts.limit) break
143
-
144
- const venue = (record["Site Name"] ?? "").trim()
145
- const split = splitAddress(record["Site Address"] ?? "")
146
- const city = (record["Site City"] ?? "").trim()
147
- const stateAbbr = (record["Site State Abbreviation"] ?? "").trim()
148
- const postcode = (record["Site Postal Code"] ?? "").trim()
149
-
150
- if (!venue || !split || !city || !postcode) continue
151
- const state = lookupStateAbbreviation(stateAbbr)
152
-
153
- if (!state) continue
154
-
155
- // Insertion order matters here. `venue` first so alignment claims its span
156
- // (which may contain a token like "Buffalo") before `locality` runs its
157
- // search — the kryptonite case `Buffalo Health Clinic, Buffalo NY`
158
- // otherwise mis-labels the venue's "Buffalo" as locality.
159
- const components: CanonicalRow["components"] = {
160
- venue,
161
- ...(split.house_number ? { house_number: split.house_number } : {}),
162
- street: split.street,
163
- locality: city,
164
- region: state.abbreviation,
165
- postcode,
166
- }
167
-
168
- const raw = composeRaw(venue, split.house_number, split.street, city, state.abbreviation, postcode)
119
+ for await (const record of rows as AsyncIterable<HrsaSiteRow>) {
120
+ if (opts.signal?.aborted) break
121
+
122
+ if (opts.limit !== undefined && emitted >= opts.limit) break
123
+
124
+ const venue = (record["Site Name"] ?? "").trim()
125
+ const split = splitStreetLine(record["Site Address"] ?? "")
126
+ const city = (record["Site City"] ?? "").trim()
127
+ const stateAbbr = (record["Site State Abbreviation"] ?? "").trim()
128
+ const postcode = (record["Site Postal Code"] ?? "").trim()
129
+
130
+ if (!venue || !split || !city || !postcode) continue
131
+ const state = lookupStateAbbreviation(stateAbbr)
132
+
133
+ if (!state) continue
134
+
135
+ // Insertion order matters here. `venue` first so alignment claims its span
136
+ // (which may contain a token like "Buffalo") before `locality` runs its
137
+ // search — the kryptonite case `Buffalo Health Clinic, Buffalo NY`
138
+ // otherwise mis-labels the venue's "Buffalo" as locality.
139
+ const components: CanonicalRow["components"] = {
140
+ venue,
141
+ ...(split.house_number ? { house_number: split.house_number } : {}),
142
+ street: split.street,
143
+ locality: city,
144
+ region: state.abbreviation,
145
+ postcode,
146
+ }
169
147
 
170
- if (!raw) continue
148
+ const raw = composeRaw(venue, split.house_number, split.street, city, state.abbreviation, postcode)
171
149
 
172
- const aligned = reconcileComponents(components, raw)
150
+ if (!raw) continue
173
151
 
174
- if (!Object.keys(aligned).length) continue
152
+ const aligned = reconcileComponents(components, raw)
175
153
 
176
- const siteID = (record["Site ID"] ?? "").trim()
154
+ if (!Object.keys(aligned).length) continue
177
155
 
178
- const sourceID = siteID
179
- ? `${USGOV_HRSA_FQHC_ADAPTER_ID}-${siteID}`
180
- : stableSourceID(USGOV_HRSA_FQHC_ADAPTER_ID, aligned)
156
+ const siteID = (record["Site ID"] ?? "").trim()
181
157
 
182
- yield {
183
- raw,
184
- components: aligned,
185
- country: "US",
186
- locale: "en-US",
187
- source: USGOV_HRSA_FQHC_ADAPTER_ID,
188
- source_id: sourceID,
189
- corpus_version: "",
190
- license: USGOV_HRSA_FQHC_DEFAULT_LICENSE,
191
- }
158
+ const sourceID = siteID
159
+ ? `${USGOV_HRSA_FQHC_ADAPTER_ID}-${siteID}`
160
+ : stableSourceID(USGOV_HRSA_FQHC_ADAPTER_ID, aligned)
192
161
 
193
- emitted++
162
+ yield {
163
+ raw,
164
+ components: aligned,
165
+ country: "US",
166
+ locale: "en-US",
167
+ source: USGOV_HRSA_FQHC_ADAPTER_ID,
168
+ source_id: sourceID,
169
+ corpus_version: "",
170
+ license: USGOV_HRSA_FQHC_DEFAULT_LICENSE,
194
171
  }
195
- } finally {
196
- stream.destroy()
172
+
173
+ emitted++
197
174
  }
198
175
  },
199
176
  }