@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
@@ -18,11 +18,9 @@
18
18
  * License: stamped `"Public Domain"` per IMLS federal government distribution terms.
19
19
  */
20
20
 
21
- import { createReadStream } from "node:fs"
21
+ import { CSVSpliterator } from "spliterator"
22
22
 
23
- import { parse as csvParse } from "csv-parse"
24
-
25
- import { stableSourceID } from "../../adapter.ts"
23
+ import { splitStreetLine, stableSourceID } from "../../adapter.ts"
26
24
  import { lookupStateAbbreviation } from "../../codex/us-fips-state.ts"
27
25
  import { reconcileComponents } from "../../format.ts"
28
26
  import type { AdapterOptions, CanonicalRow, CorpusAdapter } from "../../types.ts"
@@ -38,8 +36,6 @@ export const USGOV_IMLS_PLS_ADAPTER_ID = "usgov-imls-pls"
38
36
  */
39
37
  export const USGOV_IMLS_PLS_DEFAULT_LICENSE = "Public Domain"
40
38
 
41
- const HOUSE_NUMBER_PREFIX = /^(\d+(?:-\d+)?[A-Za-z]?)\s+(.+)$/
42
-
43
39
  interface ImlsOutletRow {
44
40
  LIBNAME: string
45
41
  ADDRESS: string
@@ -50,17 +46,6 @@ interface ImlsOutletRow {
50
46
  FSCSKEY: string
51
47
  }
52
48
 
53
- function splitAddress(address: string): { house_number?: string; street: string } | null {
54
- const trimmed = address.trim()
55
-
56
- if (!trimmed) return null
57
- const m = HOUSE_NUMBER_PREFIX.exec(trimmed)
58
-
59
- if (m) return { house_number: m[1], street: m[2]!.trim() }
60
-
61
- return { street: trimmed }
62
- }
63
-
64
49
  export function createUsgovImlsPlsAdapter(): CorpusAdapter {
65
50
  return {
66
51
  id: USGOV_IMLS_PLS_ADAPTER_ID,
@@ -72,90 +57,77 @@ export function createUsgovImlsPlsAdapter(): CorpusAdapter {
72
57
  throw new Error(`usgov-imls-pls 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<ImlsOutletRow>) {
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<ImlsOutletRow>) {
69
+ if (opts.signal?.aborted) break
93
70
 
94
- const libName = (record.LIBNAME ?? "").trim()
95
- const address = (record.ADDRESS ?? "").trim()
96
- const city = (record.CITY ?? "").trim()
97
- const zip = (record.ZIP ?? "").trim()
98
- const stateAbbr = (record.STABR ?? "").trim()
99
- const county = (record.CNTY ?? "").trim()
71
+ if (opts.limit !== undefined && emitted >= opts.limit) break
100
72
 
101
- if (!libName || !city || !zip) continue
73
+ const libName = (record.LIBNAME ?? "").trim()
74
+ const address = (record.ADDRESS ?? "").trim()
75
+ const city = (record.CITY ?? "").trim()
76
+ const zip = (record.ZIP ?? "").trim()
77
+ const stateAbbr = (record.STABR ?? "").trim()
78
+ const county = (record.CNTY ?? "").trim()
102
79
 
103
- const state = lookupStateAbbreviation(stateAbbr)
80
+ if (!libName || !city || !zip) continue
104
81
 
105
- if (!state) continue
82
+ const state = lookupStateAbbreviation(stateAbbr)
106
83
 
107
- const split = splitAddress(address)
84
+ if (!state) continue
108
85
 
109
- if (!split) continue
86
+ const split = splitStreetLine(address)
110
87
 
111
- const components: CanonicalRow["components"] = {
112
- venue: libName,
113
- ...(split.house_number ? { house_number: split.house_number } : {}),
114
- street: split.street,
115
- locality: city,
116
- region: state.abbreviation,
117
- postcode: zip,
118
- // #552: no subregion — US postal addresses don't surface the county, so emitting
119
- // subregion creates a phantom component with no raw-span to align to, quarantining
120
- // ~21% of rows. The county is still available in the source CSV; it just isn't
121
- // a postal-surface component here.
122
- }
88
+ if (!split) continue
123
89
 
124
- const streetPart = [split.house_number, split.street].filter(Boolean).join(" ").trim()
90
+ const components: CanonicalRow["components"] = {
91
+ venue: libName,
92
+ ...(split.house_number ? { house_number: split.house_number } : {}),
93
+ street: split.street,
94
+ locality: city,
95
+ region: state.abbreviation,
96
+ postcode: zip,
97
+ // #552: no subregion — US postal addresses don't surface the county, so emitting
98
+ // subregion creates a phantom component with no raw-span to align to, quarantining
99
+ // ~21% of rows. The county is still available in the source CSV; it just isn't
100
+ // a postal-surface component here.
101
+ }
125
102
 
126
- const raw = [
127
- libName,
128
- streetPart,
129
- [city, [stateAbbr, zip].filter(Boolean).join(" ")].filter(Boolean).join(", "),
130
- ]
131
- .filter(Boolean)
132
- .join(", ")
103
+ const streetPart = [split.house_number, split.street].filter(Boolean).join(" ").trim()
133
104
 
134
- const aligned = reconcileComponents(components, raw)
105
+ const raw = [libName, streetPart, [city, [stateAbbr, zip].filter(Boolean).join(" ")].filter(Boolean).join(", ")]
106
+ .filter(Boolean)
107
+ .join(", ")
135
108
 
136
- if (Object.keys(aligned).length <= 2) continue
109
+ const aligned = reconcileComponents(components, raw)
137
110
 
138
- const fscsKey = (record.FSCSKEY ?? "").trim()
111
+ if (Object.keys(aligned).length <= 2) continue
139
112
 
140
- const sourceID = fscsKey
141
- ? `${USGOV_IMLS_PLS_ADAPTER_ID}-${fscsKey}`
142
- : stableSourceID(USGOV_IMLS_PLS_ADAPTER_ID, aligned)
113
+ const fscsKey = (record.FSCSKEY ?? "").trim()
143
114
 
144
- yield {
145
- raw,
146
- components: aligned,
147
- country: "US",
148
- locale: "en-US",
149
- source: USGOV_IMLS_PLS_ADAPTER_ID,
150
- source_id: sourceID,
151
- corpus_version: "",
152
- license: USGOV_IMLS_PLS_DEFAULT_LICENSE,
153
- }
115
+ const sourceID = fscsKey
116
+ ? `${USGOV_IMLS_PLS_ADAPTER_ID}-${fscsKey}`
117
+ : stableSourceID(USGOV_IMLS_PLS_ADAPTER_ID, aligned)
154
118
 
155
- emitted++
119
+ yield {
120
+ raw,
121
+ components: aligned,
122
+ country: "US",
123
+ locale: "en-US",
124
+ source: USGOV_IMLS_PLS_ADAPTER_ID,
125
+ source_id: sourceID,
126
+ corpus_version: "",
127
+ license: USGOV_IMLS_PLS_DEFAULT_LICENSE,
156
128
  }
157
- } finally {
158
- stream.destroy()
129
+
130
+ emitted++
159
131
  }
160
132
  },
161
133
  }
@@ -19,11 +19,9 @@
19
19
  * federal).
20
20
  */
21
21
 
22
- import { createReadStream } from "node:fs"
22
+ import { CSVSpliterator } from "spliterator"
23
23
 
24
- import { parse as csvParse } from "csv-parse"
25
-
26
- import { stableSourceID } from "../../adapter.ts"
24
+ import { splitStreetLine, stableSourceID } from "../../adapter.ts"
27
25
  import { reconcileComponents } from "../../format.ts"
28
26
  import type { AdapterOptions, CanonicalRow, CorpusAdapter } from "../../types.ts"
29
27
 
@@ -38,7 +36,6 @@ export const USGOV_IRS_BMF_ADAPTER_ID = "usgov-irs-bmf"
38
36
  */
39
37
  export const USGOV_IRS_BMF_DEFAULT_LICENSE = "Public Domain"
40
38
 
41
- const HOUSE_NUMBER_PREFIX = /^(\d+(?:-\d+)?[A-Za-z]?)\s+(.+)$/
42
39
  /**
43
40
  * PO box in its many written forms: "PO BOX 12", "P.O. BOX 12", "P O BOX 12", "POB 12", "BOX 12".
44
41
  */
@@ -55,18 +52,17 @@ interface IrsBmfRow {
55
52
 
56
53
  /**
57
54
  * Classify the street line into a `po_box` or a `{house_number?, street}` split.
55
+ *
56
+ * BMF mixes street addresses and PO boxes in one `STREET` column, so the PO-box shapes have to be claimed BEFORE the
57
+ * shared house-number split runs — otherwise `splitStreetLine` would hand back `"PO Box 1234"` as a plain street, which
58
+ * is correct for every other US adapter and wrong here.
58
59
  */
59
- function splitStreetLine(street: string): { po_box: string } | { house_number?: string; street: string } | null {
60
+ function splitStreetLineOrPOBox(street: string): { po_box: string } | { house_number?: string; street: string } | null {
60
61
  const trimmed = street.trim()
61
62
 
62
- if (!trimmed) return null
63
-
64
63
  if (PO_BOX.test(trimmed)) return { po_box: trimmed }
65
- const m = HOUSE_NUMBER_PREFIX.exec(trimmed)
66
-
67
- if (m) return { house_number: m[1], street: m[2]!.trim() }
68
64
 
69
- return { street: trimmed }
65
+ return splitStreetLine(trimmed)
70
66
  }
71
67
 
72
68
  function composeRaw(
@@ -93,74 +89,68 @@ export function createUsgovIrsBmfAdapter(): CorpusAdapter {
93
89
  throw new Error(`usgov-irs-bmf adapter: only US supported, got country=${opts.country}`)
94
90
  }
95
91
 
96
- const stream = createReadStream(opts.inputPath, { encoding: "utf8" })
97
-
98
- const parser = stream.pipe(
99
- csvParse({ columns: true, skip_empty_lines: true, relax_quotes: true, relax_column_count: true, trim: true })
100
- )
92
+ const rows = CSVSpliterator.fromAsync(opts.inputPath, {
93
+ mode: "object",
94
+ normalizeKeys: false,
95
+ enableQuoteHandling: true,
96
+ })
101
97
 
102
98
  let emitted = 0
103
99
 
104
- try {
105
- for await (const record of parser as AsyncIterable<IrsBmfRow>) {
106
- if (opts.signal?.aborted) break
107
-
108
- if (opts.limit !== undefined && emitted >= opts.limit) break
100
+ for await (const record of rows as AsyncIterable<IrsBmfRow>) {
101
+ if (opts.signal?.aborted) break
109
102
 
110
- const ein = (record.EIN ?? "").trim()
111
- const venue = (record.NAME ?? "").trim() || undefined
112
- const street = (record.STREET ?? "").trim()
113
- const city = (record.CITY ?? "").trim()
114
- const state = (record.STATE ?? "").trim()
115
- const zipRaw = (record.ZIP ?? "").trim()
103
+ if (opts.limit !== undefined && emitted >= opts.limit) break
116
104
 
117
- if (!city || !zipRaw) continue
118
- const postcode = zipRaw.split("-")[0]!.trim() // 5-digit; drop the optional +4
105
+ const ein = (record.EIN ?? "").trim()
106
+ const venue = (record.NAME ?? "").trim() || undefined
107
+ const street = (record.STREET ?? "").trim()
108
+ const city = (record.CITY ?? "").trim()
109
+ const state = (record.STATE ?? "").trim()
110
+ const zipRaw = (record.ZIP ?? "").trim()
119
111
 
120
- const split = splitStreetLine(street)
112
+ if (!city || !zipRaw) continue
113
+ const postcode = zipRaw.split("-")[0]!.trim() // 5-digit; drop the optional +4
121
114
 
122
- if (!split) continue
115
+ const split = splitStreetLineOrPOBox(street)
123
116
 
124
- const streetPart =
125
- "po_box" in split ? split.po_box : [split.house_number, split.street].filter(Boolean).join(" ")
117
+ if (!split) continue
126
118
 
127
- const components: CanonicalRow["components"] = {
128
- ...(venue ? { venue } : {}),
129
- ...("po_box" in split
130
- ? { po_box: split.po_box }
131
- : { ...(split.house_number ? { house_number: split.house_number } : {}), street: split.street }),
132
- locality: city,
133
- ...(state ? { region: state } : {}),
134
- postcode,
135
- }
119
+ const streetPart =
120
+ "po_box" in split ? split.po_box : [split.house_number, split.street].filter(Boolean).join(" ")
136
121
 
137
- const raw = composeRaw(venue, streetPart, city, state, postcode)
122
+ const components: CanonicalRow["components"] = {
123
+ ...(venue ? { venue } : {}),
124
+ ...("po_box" in split
125
+ ? { po_box: split.po_box }
126
+ : { ...(split.house_number ? { house_number: split.house_number } : {}), street: split.street }),
127
+ locality: city,
128
+ ...(state ? { region: state } : {}),
129
+ postcode,
130
+ }
138
131
 
139
- if (!raw) continue
132
+ const raw = composeRaw(venue, streetPart, city, state, postcode)
140
133
 
141
- const aligned = reconcileComponents(components, raw)
134
+ if (!raw) continue
142
135
 
143
- if (Object.keys(aligned).length <= 2) continue
136
+ const aligned = reconcileComponents(components, raw)
144
137
 
145
- const sourceID = ein
146
- ? `${USGOV_IRS_BMF_ADAPTER_ID}-${ein}`
147
- : stableSourceID(USGOV_IRS_BMF_ADAPTER_ID, aligned)
138
+ if (Object.keys(aligned).length <= 2) continue
148
139
 
149
- yield {
150
- raw,
151
- components: aligned,
152
- country: "US",
153
- locale: "en-US",
154
- source: USGOV_IRS_BMF_ADAPTER_ID,
155
- source_id: sourceID,
156
- corpus_version: "",
157
- license: USGOV_IRS_BMF_DEFAULT_LICENSE,
158
- }
140
+ const sourceID = ein ? `${USGOV_IRS_BMF_ADAPTER_ID}-${ein}` : stableSourceID(USGOV_IRS_BMF_ADAPTER_ID, aligned)
159
141
 
160
- emitted++
142
+ yield {
143
+ raw,
144
+ components: aligned,
145
+ country: "US",
146
+ locale: "en-US",
147
+ source: USGOV_IRS_BMF_ADAPTER_ID,
148
+ source_id: sourceID,
149
+ corpus_version: "",
150
+ license: USGOV_IRS_BMF_DEFAULT_LICENSE,
161
151
  }
162
- } finally {
163
- stream.destroy()
152
+
153
+ emitted++
164
154
  }
165
155
  },
166
156
  }
@@ -33,6 +33,7 @@
33
33
  import { readdir } from "node:fs/promises"
34
34
  import { join } from "node:path"
35
35
 
36
+ import { tryParsingJSON } from "@mailwoman/core/objects"
36
37
  import { TextSpliterator } from "spliterator"
37
38
 
38
39
  import { reconcileComponents } from "../../format.ts"
@@ -257,8 +258,8 @@ export function createUsgovNADAdapter(): CorpusAdapter {
257
258
  let emitted = 0
258
259
  outer: for (const shard of shards) {
259
260
  if (opts.signal?.aborted) break
260
- // TextSpliterator streams string lines; the per-line try/catch below keeps the reader
261
- // tolerant of malformed rows (skip silently), so TextSpliterator + explicit JSON.parse —
261
+ // TextSpliterator streams string lines; the per-line tryParsingJSON below keeps the reader
262
+ // tolerant of malformed rows (skip silently), so TextSpliterator + a non-throwing parse —
262
263
  // not JSONSpliterator, which would throw. The path string lets the lib own + dispose each
263
264
  // shard's file handle, including on the `break outer` early exit.
264
265
  const lines = TextSpliterator.fromAsync(join(opts.inputPath, shard))
@@ -270,13 +271,9 @@ export function createUsgovNADAdapter(): CorpusAdapter {
270
271
 
271
272
  if (!line) continue
272
273
 
273
- let record: NADRecord
274
+ const record = tryParsingJSON<NADRecord>(line)
274
275
 
275
- try {
276
- record = JSON.parse(line) as NADRecord
277
- } catch {
278
- continue // malformed line — skip silently
279
- }
276
+ if (record === null) continue // malformed line — skip silently
280
277
 
281
278
  const state = (record.State ?? "").toString().trim().toUpperCase()
282
279
 
@@ -21,11 +21,9 @@
21
21
  * License: stamped `"Public Domain"` per CMS's federal government distribution terms.
22
22
  */
23
23
 
24
- import { createReadStream } from "node:fs"
24
+ import { CSVSpliterator } from "spliterator"
25
25
 
26
- import { parse as csvParse } from "csv-parse"
27
-
28
- import { stableSourceID } from "../../adapter.ts"
26
+ import { splitStreetLine, stableSourceID } from "../../adapter.ts"
29
27
  import { lookupStateAbbreviation } from "../../codex/us-fips-state.ts"
30
28
  import { reconcileComponents } from "../../format.ts"
31
29
  import type { AdapterOptions, CanonicalRow, CorpusAdapter } from "../../types.ts"
@@ -41,8 +39,6 @@ export const USGOV_NPPES_ADAPTER_ID = "usgov-nppes"
41
39
  */
42
40
  export const USGOV_NPPES_DEFAULT_LICENSE = "Public Domain"
43
41
 
44
- const HOUSE_NUMBER_PREFIX = /^(\d+(?:-\d+)?[A-Za-z]?)\s+(.+)$/
45
-
46
42
  interface NPPESRow {
47
43
  NPI: string
48
44
  "Entity Type Code": string
@@ -56,17 +52,6 @@ interface NPPESRow {
56
52
  "Provider Business Practice Location Address Postal Code": string
57
53
  }
58
54
 
59
- function splitAddress(address: string): { house_number?: string; street: string } | null {
60
- const trimmed = address.trim()
61
-
62
- if (!trimmed) return null
63
- const m = HOUSE_NUMBER_PREFIX.exec(trimmed)
64
-
65
- if (m) return { house_number: m[1], street: m[2]!.trim() }
66
-
67
- return { street: trimmed }
68
- }
69
-
70
55
  function composeRaw(
71
56
  venue: string | undefined,
72
57
  house: string | undefined,
@@ -93,84 +78,75 @@ export function createUsgovNPPESAdapter(): CorpusAdapter {
93
78
  throw new Error(`usgov-nppes adapter: only US supported, got country=${opts.country}`)
94
79
  }
95
80
 
96
- const stream = createReadStream(opts.inputPath, { encoding: "utf8" })
97
-
98
- const parser = stream.pipe(
99
- csvParse({
100
- columns: true,
101
- skip_empty_lines: true,
102
- relax_quotes: true,
103
- relax_column_count: true,
104
- })
105
- )
81
+ const rows = CSVSpliterator.fromAsync(opts.inputPath, {
82
+ mode: "object",
83
+ normalizeKeys: false,
84
+ enableQuoteHandling: true,
85
+ })
106
86
 
107
87
  let emitted = 0
108
88
 
109
- try {
110
- for await (const record of parser as AsyncIterable<NPPESRow>) {
111
- if (opts.signal?.aborted) break
112
-
113
- if (opts.limit !== undefined && emitted >= opts.limit) break
89
+ for await (const record of rows as AsyncIterable<NPPESRow>) {
90
+ if (opts.signal?.aborted) break
114
91
 
115
- const npi = (record.NPI ?? "").trim()
116
- const entityType = (record["Entity Type Code"] ?? "").trim()
117
- const orgName = (record["Provider Organization Name (Legal Business Name)"] ?? "").trim()
118
- const lastName = (record["Provider Last Name (Legal Name)"] ?? "").trim()
119
- const firstName = (record["Provider First Name"] ?? "").trim()
92
+ if (opts.limit !== undefined && emitted >= opts.limit) break
120
93
 
121
- const address1 = (record["Provider First Line Business Practice Location Address"] ?? "").trim()
122
- const address2 = (record["Provider Second Line Business Practice Location Address"] ?? "").trim()
123
- const city = (record["Provider Business Practice Location Address City Name"] ?? "").trim()
124
- const stateRaw = (record["Provider Business Practice Location Address State Name"] ?? "").trim()
125
- const postcode = (record["Provider Business Practice Location Address Postal Code"] ?? "").trim()
94
+ const npi = (record.NPI ?? "").trim()
95
+ const entityType = (record["Entity Type Code"] ?? "").trim()
96
+ const orgName = (record["Provider Organization Name (Legal Business Name)"] ?? "").trim()
97
+ const lastName = (record["Provider Last Name (Legal Name)"] ?? "").trim()
98
+ const firstName = (record["Provider First Name"] ?? "").trim()
126
99
 
127
- if (!city || !postcode) continue
100
+ const address1 = (record["Provider First Line Business Practice Location Address"] ?? "").trim()
101
+ const address2 = (record["Provider Second Line Business Practice Location Address"] ?? "").trim()
102
+ const city = (record["Provider Business Practice Location Address City Name"] ?? "").trim()
103
+ const stateRaw = (record["Provider Business Practice Location Address State Name"] ?? "").trim()
104
+ const postcode = (record["Provider Business Practice Location Address Postal Code"] ?? "").trim()
128
105
 
129
- const state = lookupStateAbbreviation(stateRaw)
106
+ if (!city || !postcode) continue
130
107
 
131
- if (!state) continue
108
+ const state = lookupStateAbbreviation(stateRaw)
132
109
 
133
- const fullStreet = [address1, address2].filter(Boolean).join(" ")
134
- const split = splitAddress(fullStreet)
110
+ if (!state) continue
135
111
 
136
- if (!split) continue
112
+ const fullStreet = [address1, address2].filter(Boolean).join(" ")
113
+ const split = splitStreetLine(fullStreet)
137
114
 
138
- const venue = orgName || [firstName, lastName].filter(Boolean).join(" ") || undefined
115
+ if (!split) continue
139
116
 
140
- const components: CanonicalRow["components"] = {
141
- ...(venue ? { venue } : {}),
142
- ...(split.house_number ? { house_number: split.house_number } : {}),
143
- street: split.street,
144
- locality: city,
145
- region: state.abbreviation,
146
- postcode,
147
- }
117
+ const venue = orgName || [firstName, lastName].filter(Boolean).join(" ") || undefined
148
118
 
149
- const raw = composeRaw(venue, split.house_number, split.street, city, state.abbreviation, postcode)
119
+ const components: CanonicalRow["components"] = {
120
+ ...(venue ? { venue } : {}),
121
+ ...(split.house_number ? { house_number: split.house_number } : {}),
122
+ street: split.street,
123
+ locality: city,
124
+ region: state.abbreviation,
125
+ postcode,
126
+ }
150
127
 
151
- if (!raw) continue
128
+ const raw = composeRaw(venue, split.house_number, split.street, city, state.abbreviation, postcode)
152
129
 
153
- const aligned = reconcileComponents(components, raw)
130
+ if (!raw) continue
154
131
 
155
- if (Object.keys(aligned).length <= 2) continue
132
+ const aligned = reconcileComponents(components, raw)
156
133
 
157
- const sourceID = npi ? `${USGOV_NPPES_ADAPTER_ID}-${npi}` : stableSourceID(USGOV_NPPES_ADAPTER_ID, aligned)
134
+ if (Object.keys(aligned).length <= 2) continue
158
135
 
159
- yield {
160
- raw,
161
- components: aligned,
162
- country: "US",
163
- locale: "en-US",
164
- source: USGOV_NPPES_ADAPTER_ID,
165
- source_id: sourceID,
166
- corpus_version: "",
167
- license: USGOV_NPPES_DEFAULT_LICENSE,
168
- }
136
+ const sourceID = npi ? `${USGOV_NPPES_ADAPTER_ID}-${npi}` : stableSourceID(USGOV_NPPES_ADAPTER_ID, aligned)
169
137
 
170
- emitted++
138
+ yield {
139
+ raw,
140
+ components: aligned,
141
+ country: "US",
142
+ locale: "en-US",
143
+ source: USGOV_NPPES_ADAPTER_ID,
144
+ source_id: sourceID,
145
+ corpus_version: "",
146
+ license: USGOV_NPPES_DEFAULT_LICENSE,
171
147
  }
172
- } finally {
173
- stream.destroy()
148
+
149
+ emitted++
174
150
  }
175
151
  },
176
152
  }