@mailwoman/corpus 8.0.0 → 8.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 (483) hide show
  1. package/out/src/adapter.d.ts +15 -5
  2. package/out/src/adapter.d.ts.map +1 -1
  3. package/out/src/adapter.js +11 -9
  4. package/out/src/adapter.js.map +1 -1
  5. package/out/src/adapters/ban/adapter.d.ts +7 -0
  6. package/out/src/adapters/ban/adapter.d.ts.map +1 -1
  7. package/out/src/adapters/ban/adapter.js +9 -2
  8. package/out/src/adapters/ban/adapter.js.map +1 -1
  9. package/out/src/adapters/ban/street-decompose.d.ts.map +1 -1
  10. package/out/src/adapters/ban/street-decompose.js +5 -1
  11. package/out/src/adapters/ban/street-decompose.js.map +1 -1
  12. package/out/src/adapters/fcc-bdc/adapter.d.ts +14 -1
  13. package/out/src/adapters/fcc-bdc/adapter.d.ts.map +1 -1
  14. package/out/src/adapters/fcc-bdc/adapter.js +15 -2
  15. package/out/src/adapters/fcc-bdc/adapter.js.map +1 -1
  16. package/out/src/adapters/geonames/adapter.d.ts +11 -0
  17. package/out/src/adapters/geonames/adapter.d.ts.map +1 -1
  18. package/out/src/adapters/geonames/adapter.js +24 -5
  19. package/out/src/adapters/geonames/adapter.js.map +1 -1
  20. package/out/src/adapters/geonames-postal/adapter.d.ts +11 -0
  21. package/out/src/adapters/geonames-postal/adapter.d.ts.map +1 -1
  22. package/out/src/adapters/geonames-postal/adapter.js +14 -1
  23. package/out/src/adapters/geonames-postal/adapter.js.map +1 -1
  24. package/out/src/adapters/gnaf/adapter.d.ts +10 -1
  25. package/out/src/adapters/gnaf/adapter.d.ts.map +1 -1
  26. package/out/src/adapters/gnaf/adapter.js +11 -2
  27. package/out/src/adapters/gnaf/adapter.js.map +1 -1
  28. package/out/src/adapters/gnaf/assemble.d.ts +18 -6
  29. package/out/src/adapters/gnaf/assemble.d.ts.map +1 -1
  30. package/out/src/adapters/gnaf/assemble.js +16 -6
  31. package/out/src/adapters/gnaf/assemble.js.map +1 -1
  32. package/out/src/adapters/index.d.ts.map +1 -1
  33. package/out/src/adapters/index.js.map +1 -1
  34. package/out/src/adapters/openaddresses/adapter.d.ts +11 -0
  35. package/out/src/adapters/openaddresses/adapter.d.ts.map +1 -1
  36. package/out/src/adapters/openaddresses/adapter.js +18 -3
  37. package/out/src/adapters/openaddresses/adapter.js.map +1 -1
  38. package/out/src/adapters/overture/adapter.d.ts +11 -0
  39. package/out/src/adapters/overture/adapter.d.ts.map +1 -1
  40. package/out/src/adapters/overture/adapter.js +12 -1
  41. package/out/src/adapters/overture/adapter.js.map +1 -1
  42. package/out/src/adapters/state-hi-schools/adapter.d.ts +11 -0
  43. package/out/src/adapters/state-hi-schools/adapter.d.ts.map +1 -1
  44. package/out/src/adapters/state-hi-schools/adapter.js +11 -0
  45. package/out/src/adapters/state-hi-schools/adapter.js.map +1 -1
  46. package/out/src/adapters/state-ia-contractors/adapter.d.ts +11 -0
  47. package/out/src/adapters/state-ia-contractors/adapter.d.ts.map +1 -1
  48. package/out/src/adapters/state-ia-contractors/adapter.js +11 -0
  49. package/out/src/adapters/state-ia-contractors/adapter.js.map +1 -1
  50. package/out/src/adapters/state-ny-notaries/adapter.d.ts +11 -0
  51. package/out/src/adapters/state-ny-notaries/adapter.d.ts.map +1 -1
  52. package/out/src/adapters/state-ny-notaries/adapter.js +11 -0
  53. package/out/src/adapters/state-ny-notaries/adapter.js.map +1 -1
  54. package/out/src/adapters/state-tx-notaries/adapter.d.ts +11 -0
  55. package/out/src/adapters/state-tx-notaries/adapter.d.ts.map +1 -1
  56. package/out/src/adapters/state-tx-notaries/adapter.js +15 -2
  57. package/out/src/adapters/state-tx-notaries/adapter.js.map +1 -1
  58. package/out/src/adapters/synth-po-box/adapter.d.ts +11 -0
  59. package/out/src/adapters/synth-po-box/adapter.d.ts.map +1 -1
  60. package/out/src/adapters/synth-po-box/adapter.js +13 -2
  61. package/out/src/adapters/synth-po-box/adapter.js.map +1 -1
  62. package/out/src/adapters/tiger/adapter.d.ts +14 -1
  63. package/out/src/adapters/tiger/adapter.d.ts.map +1 -1
  64. package/out/src/adapters/tiger/adapter.js +19 -4
  65. package/out/src/adapters/tiger/adapter.js.map +1 -1
  66. package/out/src/adapters/tiger/street-decompose.d.ts.map +1 -1
  67. package/out/src/adapters/tiger/street-decompose.js.map +1 -1
  68. package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts +11 -0
  69. package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts.map +1 -1
  70. package/out/src/adapters/usgov-hrsa-fqhc/adapter.js +12 -1
  71. package/out/src/adapters/usgov-hrsa-fqhc/adapter.js.map +1 -1
  72. package/out/src/adapters/usgov-imls-pls/adapter.d.ts +11 -0
  73. package/out/src/adapters/usgov-imls-pls/adapter.d.ts.map +1 -1
  74. package/out/src/adapters/usgov-imls-pls/adapter.js +11 -0
  75. package/out/src/adapters/usgov-imls-pls/adapter.js.map +1 -1
  76. package/out/src/adapters/usgov-irs-bmf/adapter.d.ts +11 -0
  77. package/out/src/adapters/usgov-irs-bmf/adapter.d.ts.map +1 -1
  78. package/out/src/adapters/usgov-irs-bmf/adapter.js +17 -2
  79. package/out/src/adapters/usgov-irs-bmf/adapter.js.map +1 -1
  80. package/out/src/adapters/usgov-nad/adapter.d.ts +11 -0
  81. package/out/src/adapters/usgov-nad/adapter.d.ts.map +1 -1
  82. package/out/src/adapters/usgov-nad/adapter.js +12 -1
  83. package/out/src/adapters/usgov-nad/adapter.js.map +1 -1
  84. package/out/src/adapters/usgov-nppes/adapter.d.ts +11 -0
  85. package/out/src/adapters/usgov-nppes/adapter.d.ts.map +1 -1
  86. package/out/src/adapters/usgov-nppes/adapter.js +11 -0
  87. package/out/src/adapters/usgov-nppes/adapter.js.map +1 -1
  88. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.d.ts +11 -0
  89. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.d.ts.map +1 -1
  90. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js +15 -2
  91. package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js.map +1 -1
  92. package/out/src/adapters/wof-admin-jp/adapter.d.ts +7 -0
  93. package/out/src/adapters/wof-admin-jp/adapter.d.ts.map +1 -1
  94. package/out/src/adapters/wof-admin-jp/adapter.js +18 -3
  95. package/out/src/adapters/wof-admin-jp/adapter.js.map +1 -1
  96. package/out/src/adapters/wof-admin-json/adapter.d.ts +13 -3
  97. package/out/src/adapters/wof-admin-json/adapter.d.ts.map +1 -1
  98. package/out/src/adapters/wof-admin-json/adapter.js +15 -5
  99. package/out/src/adapters/wof-admin-json/adapter.js.map +1 -1
  100. package/out/src/adapters/wof-postalcode-json/adapter.d.ts +7 -0
  101. package/out/src/adapters/wof-postalcode-json/adapter.d.ts.map +1 -1
  102. package/out/src/adapters/wof-postalcode-json/adapter.js +9 -2
  103. package/out/src/adapters/wof-postalcode-json/adapter.js.map +1 -1
  104. package/out/src/align.d.ts +12 -4
  105. package/out/src/align.d.ts.map +1 -1
  106. package/out/src/align.js +9 -5
  107. package/out/src/align.js.map +1 -1
  108. package/out/src/build.d.ts +24 -8
  109. package/out/src/build.d.ts.map +1 -1
  110. package/out/src/build.js +3 -3
  111. package/out/src/build.js.map +1 -1
  112. package/out/src/codex/us-fips-state.d.ts +6 -2
  113. package/out/src/codex/us-fips-state.d.ts.map +1 -1
  114. package/out/src/codex/us-fips-state.js +3 -1
  115. package/out/src/codex/us-fips-state.js.map +1 -1
  116. package/out/src/golden.d.ts +21 -7
  117. package/out/src/golden.d.ts.map +1 -1
  118. package/out/src/golden.js +18 -10
  119. package/out/src/golden.js.map +1 -1
  120. package/out/src/license.d.ts +3 -1
  121. package/out/src/license.d.ts.map +1 -1
  122. package/out/src/license.js +4 -2
  123. package/out/src/license.js.map +1 -1
  124. package/out/src/parquet-wrapper/reader.d.ts +4 -2
  125. package/out/src/parquet-wrapper/reader.d.ts.map +1 -1
  126. package/out/src/parquet-wrapper/reader.js +5 -4
  127. package/out/src/parquet-wrapper/reader.js.map +1 -1
  128. package/out/src/parquet-wrapper/schema.d.ts +14 -10
  129. package/out/src/parquet-wrapper/schema.d.ts.map +1 -1
  130. package/out/src/parquet-wrapper/schema.js +6 -2
  131. package/out/src/parquet-wrapper/schema.js.map +1 -1
  132. package/out/src/parquet-wrapper/writer.d.ts +12 -4
  133. package/out/src/parquet-wrapper/writer.d.ts.map +1 -1
  134. package/out/src/parquet-wrapper/writer.js +12 -4
  135. package/out/src/parquet-wrapper/writer.js.map +1 -1
  136. package/out/src/parquet.d.ts +21 -7
  137. package/out/src/parquet.d.ts.map +1 -1
  138. package/out/src/parquet.js +15 -4
  139. package/out/src/parquet.js.map +1 -1
  140. package/out/src/runner.d.ts +39 -13
  141. package/out/src/runner.d.ts.map +1 -1
  142. package/out/src/runner.js +7 -3
  143. package/out/src/runner.js.map +1 -1
  144. package/out/src/shard-recipes/anchor-absorption.d.ts +4 -0
  145. package/out/src/shard-recipes/anchor-absorption.d.ts.map +1 -1
  146. package/out/src/shard-recipes/anchor-absorption.js +14 -5
  147. package/out/src/shard-recipes/anchor-absorption.js.map +1 -1
  148. package/out/src/shard-recipes/boundary-stress.d.ts +4 -0
  149. package/out/src/shard-recipes/boundary-stress.d.ts.map +1 -1
  150. package/out/src/shard-recipes/boundary-stress.js +13 -7
  151. package/out/src/shard-recipes/boundary-stress.js.map +1 -1
  152. package/out/src/shard-recipes/country-balanced.d.ts +4 -0
  153. package/out/src/shard-recipes/country-balanced.d.ts.map +1 -1
  154. package/out/src/shard-recipes/country-balanced.js +39 -11
  155. package/out/src/shard-recipes/country-balanced.js.map +1 -1
  156. package/out/src/shard-recipes/cz-pcfirst-preposition.d.ts +4 -0
  157. package/out/src/shard-recipes/cz-pcfirst-preposition.d.ts.map +1 -1
  158. package/out/src/shard-recipes/cz-pcfirst-preposition.js +4 -0
  159. package/out/src/shard-recipes/cz-pcfirst-preposition.js.map +1 -1
  160. package/out/src/shard-recipes/fr-admin-split.d.ts +4 -0
  161. package/out/src/shard-recipes/fr-admin-split.d.ts.map +1 -1
  162. package/out/src/shard-recipes/fr-admin-split.js +14 -3
  163. package/out/src/shard-recipes/fr-admin-split.js.map +1 -1
  164. package/out/src/shard-recipes/fr-bare-street.d.ts +4 -0
  165. package/out/src/shard-recipes/fr-bare-street.d.ts.map +1 -1
  166. package/out/src/shard-recipes/fr-bare-street.js +4 -0
  167. package/out/src/shard-recipes/fr-bare-street.js.map +1 -1
  168. package/out/src/shard-recipes/fr-fragment.d.ts +4 -0
  169. package/out/src/shard-recipes/fr-fragment.d.ts.map +1 -1
  170. package/out/src/shard-recipes/fr-fragment.js +25 -8
  171. package/out/src/shard-recipes/fr-fragment.js.map +1 -1
  172. package/out/src/shard-recipes/fr-lieudit.d.ts +4 -0
  173. package/out/src/shard-recipes/fr-lieudit.d.ts.map +1 -1
  174. package/out/src/shard-recipes/fr-lieudit.js +21 -8
  175. package/out/src/shard-recipes/fr-lieudit.js.map +1 -1
  176. package/out/src/shard-recipes/fr-order.d.ts +4 -0
  177. package/out/src/shard-recipes/fr-order.d.ts.map +1 -1
  178. package/out/src/shard-recipes/fr-order.js +27 -8
  179. package/out/src/shard-recipes/fr-order.js.map +1 -1
  180. package/out/src/shard-recipes/german.d.ts +4 -0
  181. package/out/src/shard-recipes/german.d.ts.map +1 -1
  182. package/out/src/shard-recipes/german.js +16 -6
  183. package/out/src/shard-recipes/german.js.map +1 -1
  184. package/out/src/shard-recipes/house-venue.d.ts +4 -0
  185. package/out/src/shard-recipes/house-venue.d.ts.map +1 -1
  186. package/out/src/shard-recipes/house-venue.js +4 -0
  187. package/out/src/shard-recipes/house-venue.js.map +1 -1
  188. package/out/src/shard-recipes/index.d.ts +9 -3
  189. package/out/src/shard-recipes/index.d.ts.map +1 -1
  190. package/out/src/shard-recipes/index.js +12 -4
  191. package/out/src/shard-recipes/index.js.map +1 -1
  192. package/out/src/shard-recipes/intersection.d.ts +4 -0
  193. package/out/src/shard-recipes/intersection.d.ts.map +1 -1
  194. package/out/src/shard-recipes/intersection.js +27 -13
  195. package/out/src/shard-recipes/intersection.js.map +1 -1
  196. package/out/src/shard-recipes/locale.d.ts +4 -0
  197. package/out/src/shard-recipes/locale.d.ts.map +1 -1
  198. package/out/src/shard-recipes/locale.js +9 -4
  199. package/out/src/shard-recipes/locale.js.map +1 -1
  200. package/out/src/shard-recipes/nl-postcode.d.ts +4 -0
  201. package/out/src/shard-recipes/nl-postcode.d.ts.map +1 -1
  202. package/out/src/shard-recipes/nl-postcode.js +8 -2
  203. package/out/src/shard-recipes/nl-postcode.js.map +1 -1
  204. package/out/src/shard-recipes/no-fragment.d.ts +4 -0
  205. package/out/src/shard-recipes/no-fragment.d.ts.map +1 -1
  206. package/out/src/shard-recipes/no-fragment.js +8 -2
  207. package/out/src/shard-recipes/no-fragment.js.map +1 -1
  208. package/out/src/shard-recipes/no-street-led.d.ts +4 -0
  209. package/out/src/shard-recipes/no-street-led.d.ts.map +1 -1
  210. package/out/src/shard-recipes/no-street-led.js +5 -1
  211. package/out/src/shard-recipes/no-street-led.js.map +1 -1
  212. package/out/src/shard-recipes/no-street.d.ts +4 -0
  213. package/out/src/shard-recipes/no-street.d.ts.map +1 -1
  214. package/out/src/shard-recipes/no-street.js +4 -0
  215. package/out/src/shard-recipes/no-street.js.map +1 -1
  216. package/out/src/shard-recipes/po-box-cedex.d.ts +4 -0
  217. package/out/src/shard-recipes/po-box-cedex.d.ts.map +1 -1
  218. package/out/src/shard-recipes/po-box-cedex.js +99 -43
  219. package/out/src/shard-recipes/po-box-cedex.js.map +1 -1
  220. package/out/src/shard-recipes/po-box.d.ts +4 -0
  221. package/out/src/shard-recipes/po-box.d.ts.map +1 -1
  222. package/out/src/shard-recipes/po-box.js +4 -0
  223. package/out/src/shard-recipes/po-box.js.map +1 -1
  224. package/out/src/shard-recipes/scaffold.d.ts +48 -16
  225. package/out/src/shard-recipes/scaffold.d.ts.map +1 -1
  226. package/out/src/shard-recipes/scaffold.js +10 -6
  227. package/out/src/shard-recipes/scaffold.js.map +1 -1
  228. package/out/src/shard-recipes/si-bare-village.d.ts +4 -0
  229. package/out/src/shard-recipes/si-bare-village.d.ts.map +1 -1
  230. package/out/src/shard-recipes/si-bare-village.js +4 -0
  231. package/out/src/shard-recipes/si-bare-village.js.map +1 -1
  232. package/out/src/shard-recipes/street-affix.d.ts +4 -0
  233. package/out/src/shard-recipes/street-affix.d.ts.map +1 -1
  234. package/out/src/shard-recipes/street-affix.js +29 -10
  235. package/out/src/shard-recipes/street-affix.js.map +1 -1
  236. package/out/src/shard-recipes/street-bare.d.ts +4 -0
  237. package/out/src/shard-recipes/street-bare.d.ts.map +1 -1
  238. package/out/src/shard-recipes/street-bare.js +4 -0
  239. package/out/src/shard-recipes/street-bare.js.map +1 -1
  240. package/out/src/shard-recipes/street.d.ts +4 -0
  241. package/out/src/shard-recipes/street.d.ts.map +1 -1
  242. package/out/src/shard-recipes/street.js +4 -0
  243. package/out/src/shard-recipes/street.js.map +1 -1
  244. package/out/src/shard-recipes/unit.d.ts +4 -0
  245. package/out/src/shard-recipes/unit.d.ts.map +1 -1
  246. package/out/src/shard-recipes/unit.js +45 -14
  247. package/out/src/shard-recipes/unit.js.map +1 -1
  248. package/out/src/split.d.ts +18 -6
  249. package/out/src/split.d.ts.map +1 -1
  250. package/out/src/split.js +4 -2
  251. package/out/src/split.js.map +1 -1
  252. package/out/src/synthesize-anchor-absorption.d.ts +7 -1
  253. package/out/src/synthesize-anchor-absorption.d.ts.map +1 -1
  254. package/out/src/synthesize-anchor-absorption.js +28 -14
  255. package/out/src/synthesize-anchor-absorption.js.map +1 -1
  256. package/out/src/synthesize-boundary-stress.d.ts +3 -1
  257. package/out/src/synthesize-boundary-stress.d.ts.map +1 -1
  258. package/out/src/synthesize-boundary-stress.js +35 -28
  259. package/out/src/synthesize-boundary-stress.js.map +1 -1
  260. package/out/src/synthesize-german.d.ts +15 -5
  261. package/out/src/synthesize-german.d.ts.map +1 -1
  262. package/out/src/synthesize-german.js +9 -3
  263. package/out/src/synthesize-german.js.map +1 -1
  264. package/out/src/synthesize-house-venue.d.ts.map +1 -1
  265. package/out/src/synthesize-house-venue.js +15 -15
  266. package/out/src/synthesize-house-venue.js.map +1 -1
  267. package/out/src/synthesize-intersection.d.ts +9 -3
  268. package/out/src/synthesize-intersection.d.ts.map +1 -1
  269. package/out/src/synthesize-intersection.js +25 -6
  270. package/out/src/synthesize-intersection.js.map +1 -1
  271. package/out/src/synthesize-no-street.d.ts +3 -1
  272. package/out/src/synthesize-no-street.d.ts.map +1 -1
  273. package/out/src/synthesize-no-street.js +5 -6
  274. package/out/src/synthesize-no-street.js.map +1 -1
  275. package/out/src/synthesize-po-box.d.ts +15 -5
  276. package/out/src/synthesize-po-box.d.ts.map +1 -1
  277. package/out/src/synthesize-po-box.js +21 -7
  278. package/out/src/synthesize-po-box.js.map +1 -1
  279. package/out/src/synthesize-street.d.ts +3 -1
  280. package/out/src/synthesize-street.d.ts.map +1 -1
  281. package/out/src/synthesize-street.js +6 -1
  282. package/out/src/synthesize-street.js.map +1 -1
  283. package/out/src/synthesize.d.ts +39 -13
  284. package/out/src/synthesize.d.ts.map +1 -1
  285. package/out/src/synthesize.js +66 -36
  286. package/out/src/synthesize.js.map +1 -1
  287. package/out/src/tokenize.d.ts +15 -5
  288. package/out/src/tokenize.d.ts.map +1 -1
  289. package/out/src/tokenize.js.map +1 -1
  290. package/out/src/tools/align-shard.d.ts.map +1 -1
  291. package/out/src/tools/align-shard.js +3 -1
  292. package/out/src/tools/align-shard.js.map +1 -1
  293. package/out/src/tools/audit.d.ts.map +1 -1
  294. package/out/src/tools/audit.js +20 -8
  295. package/out/src/tools/audit.js.map +1 -1
  296. package/out/src/tools/corpus-stats.d.ts.map +1 -1
  297. package/out/src/tools/corpus-stats.js.map +1 -1
  298. package/out/src/tools/fetch/ban.d.ts.map +1 -1
  299. package/out/src/tools/fetch/ban.js +17 -10
  300. package/out/src/tools/fetch/ban.js.map +1 -1
  301. package/out/src/tools/fetch/download.d.ts +30 -10
  302. package/out/src/tools/fetch/download.d.ts.map +1 -1
  303. package/out/src/tools/fetch/download.js +26 -6
  304. package/out/src/tools/fetch/download.js.map +1 -1
  305. package/out/src/tools/fetch/hrsa.d.ts.map +1 -1
  306. package/out/src/tools/fetch/hrsa.js.map +1 -1
  307. package/out/src/tools/fetch/imls-pls.d.ts.map +1 -1
  308. package/out/src/tools/fetch/imls-pls.js +18 -18
  309. package/out/src/tools/fetch/imls-pls.js.map +1 -1
  310. package/out/src/tools/fetch/index.d.ts +3 -1
  311. package/out/src/tools/fetch/index.d.ts.map +1 -1
  312. package/out/src/tools/fetch/index.js +3 -1
  313. package/out/src/tools/fetch/index.js.map +1 -1
  314. package/out/src/tools/fetch/nad.d.ts +21 -7
  315. package/out/src/tools/fetch/nad.d.ts.map +1 -1
  316. package/out/src/tools/fetch/nad.js +5 -5
  317. package/out/src/tools/fetch/nad.js.map +1 -1
  318. package/out/src/tools/fetch/nppes.d.ts.map +1 -1
  319. package/out/src/tools/fetch/nppes.js +9 -15
  320. package/out/src/tools/fetch/nppes.js.map +1 -1
  321. package/out/src/tools/fetch/openaddresses.d.ts +3 -1
  322. package/out/src/tools/fetch/openaddresses.d.ts.map +1 -1
  323. package/out/src/tools/fetch/openaddresses.js +32 -23
  324. package/out/src/tools/fetch/openaddresses.js.map +1 -1
  325. package/out/src/tools/fetch/ppd.d.ts +3 -1
  326. package/out/src/tools/fetch/ppd.d.ts.map +1 -1
  327. package/out/src/tools/fetch/ppd.js +14 -3
  328. package/out/src/tools/fetch/ppd.js.map +1 -1
  329. package/out/src/tools/fetch/state-hi-schools.d.ts.map +1 -1
  330. package/out/src/tools/fetch/state-hi-schools.js +13 -6
  331. package/out/src/tools/fetch/state-hi-schools.js.map +1 -1
  332. package/out/src/tools/fetch/state-sources.d.ts.map +1 -1
  333. package/out/src/tools/fetch/state-sources.js +8 -3
  334. package/out/src/tools/fetch/state-sources.js.map +1 -1
  335. package/out/src/tools/fetch/tiger-full.d.ts +9 -3
  336. package/out/src/tools/fetch/tiger-full.d.ts.map +1 -1
  337. package/out/src/tools/fetch/tiger-full.js +35 -20
  338. package/out/src/tools/fetch/tiger-full.js.map +1 -1
  339. package/out/src/tools/gb-title-case.d.ts.map +1 -1
  340. package/out/src/tools/gb-title-case.js.map +1 -1
  341. package/out/src/tools/golden-expand.d.ts +24 -8
  342. package/out/src/tools/golden-expand.d.ts.map +1 -1
  343. package/out/src/tools/golden-expand.js +20 -12
  344. package/out/src/tools/golden-expand.js.map +1 -1
  345. package/out/src/tools/golden-promote.d.ts +18 -6
  346. package/out/src/tools/golden-promote.d.ts.map +1 -1
  347. package/out/src/tools/golden-promote.js +22 -7
  348. package/out/src/tools/golden-promote.js.map +1 -1
  349. package/out/src/tools/ingest-csv.d.ts +3 -1
  350. package/out/src/tools/ingest-csv.d.ts.map +1 -1
  351. package/out/src/tools/ingest-csv.js +17 -19
  352. package/out/src/tools/ingest-csv.js.map +1 -1
  353. package/out/src/tools/jsonl-to-parquet.d.ts +18 -6
  354. package/out/src/tools/jsonl-to-parquet.d.ts.map +1 -1
  355. package/out/src/tools/jsonl-to-parquet.js +11 -5
  356. package/out/src/tools/jsonl-to-parquet.js.map +1 -1
  357. package/out/src/tools/lint-shard-vocab.d.ts +36 -12
  358. package/out/src/tools/lint-shard-vocab.d.ts.map +1 -1
  359. package/out/src/tools/lint-shard-vocab.js +25 -11
  360. package/out/src/tools/lint-shard-vocab.js.map +1 -1
  361. package/out/src/tools/lint-shard.d.ts +30 -10
  362. package/out/src/tools/lint-shard.d.ts.map +1 -1
  363. package/out/src/tools/lint-shard.js +33 -20
  364. package/out/src/tools/lint-shard.js.map +1 -1
  365. package/out/src/tools/overlay-manifest.d.ts.map +1 -1
  366. package/out/src/tools/overlay-manifest.js +6 -4
  367. package/out/src/tools/overlay-manifest.js.map +1 -1
  368. package/out/src/tools/shard-kryptonite.d.ts +6 -2
  369. package/out/src/tools/shard-kryptonite.d.ts.map +1 -1
  370. package/out/src/tools/shard-kryptonite.js +1 -1
  371. package/out/src/tools/shard-kryptonite.js.map +1 -1
  372. package/out/src/tools/shard-translit.d.ts +9 -3
  373. package/out/src/tools/shard-translit.d.ts.map +1 -1
  374. package/out/src/tools/shard-translit.js +2 -2
  375. package/out/src/tools/shard-translit.js.map +1 -1
  376. package/out/src/types.d.ts +51 -17
  377. package/out/src/types.d.ts.map +1 -1
  378. package/out/src/wof-json.d.ts +9 -3
  379. package/out/src/wof-json.d.ts.map +1 -1
  380. package/out/src/wof-json.js +6 -4
  381. package/out/src/wof-json.js.map +1 -1
  382. package/package.json +7 -6
  383. package/src/adapter.ts +24 -13
  384. package/src/adapters/ban/adapter.ts +12 -2
  385. package/src/adapters/ban/street-decompose.ts +7 -1
  386. package/src/adapters/fcc-bdc/adapter.ts +19 -3
  387. package/src/adapters/geonames/adapter.ts +28 -5
  388. package/src/adapters/geonames-postal/adapter.ts +17 -1
  389. package/src/adapters/gnaf/adapter.ts +15 -2
  390. package/src/adapters/gnaf/assemble.ts +40 -10
  391. package/src/adapters/index.ts +13 -0
  392. package/src/adapters/openaddresses/adapter.ts +22 -3
  393. package/src/adapters/overture/adapter.ts +18 -2
  394. package/src/adapters/state-hi-schools/adapter.ts +15 -0
  395. package/src/adapters/state-ia-contractors/adapter.ts +15 -0
  396. package/src/adapters/state-ny-notaries/adapter.ts +15 -0
  397. package/src/adapters/state-tx-notaries/adapter.ts +18 -2
  398. package/src/adapters/synth-po-box/adapter.ts +19 -2
  399. package/src/adapters/tiger/adapter.ts +23 -4
  400. package/src/adapters/tiger/street-decompose.ts +1 -0
  401. package/src/adapters/usgov-hrsa-fqhc/adapter.ts +18 -2
  402. package/src/adapters/usgov-imls-pls/adapter.ts +15 -0
  403. package/src/adapters/usgov-irs-bmf/adapter.ts +19 -2
  404. package/src/adapters/usgov-nad/adapter.ts +14 -1
  405. package/src/adapters/usgov-nppes/adapter.ts +13 -0
  406. package/src/adapters/usgov-samhsa-treatment-locator/adapter.ts +21 -3
  407. package/src/adapters/wof-admin-jp/adapter.ts +20 -3
  408. package/src/adapters/wof-admin-json/adapter.ts +22 -7
  409. package/src/adapters/wof-postalcode-json/adapter.ts +10 -2
  410. package/src/align.ts +21 -8
  411. package/src/build.ts +48 -11
  412. package/src/codex/us-fips-state.ts +6 -2
  413. package/src/golden.ts +28 -13
  414. package/src/license.ts +4 -2
  415. package/src/parquet-wrapper/reader.ts +6 -4
  416. package/src/parquet-wrapper/schema.ts +14 -10
  417. package/src/parquet-wrapper/writer.ts +12 -4
  418. package/src/parquet.ts +39 -8
  419. package/src/runner.ts +49 -15
  420. package/src/shard-recipes/anchor-absorption.ts +21 -5
  421. package/src/shard-recipes/boundary-stress.ts +20 -7
  422. package/src/shard-recipes/country-balanced.ts +78 -13
  423. package/src/shard-recipes/cz-pcfirst-preposition.ts +9 -0
  424. package/src/shard-recipes/fr-admin-split.ts +30 -5
  425. package/src/shard-recipes/fr-bare-street.ts +10 -0
  426. package/src/shard-recipes/fr-fragment.ts +33 -8
  427. package/src/shard-recipes/fr-lieudit.ts +35 -9
  428. package/src/shard-recipes/fr-order.ts +47 -9
  429. package/src/shard-recipes/german.ts +32 -6
  430. package/src/shard-recipes/house-venue.ts +7 -0
  431. package/src/shard-recipes/index.ts +12 -4
  432. package/src/shard-recipes/intersection.ts +63 -14
  433. package/src/shard-recipes/locale.ts +37 -4
  434. package/src/shard-recipes/nl-postcode.ts +12 -2
  435. package/src/shard-recipes/no-fragment.ts +12 -2
  436. package/src/shard-recipes/no-street-led.ts +11 -1
  437. package/src/shard-recipes/no-street.ts +7 -0
  438. package/src/shard-recipes/po-box-cedex.ts +145 -45
  439. package/src/shard-recipes/po-box.ts +7 -0
  440. package/src/shard-recipes/scaffold.ts +52 -20
  441. package/src/shard-recipes/si-bare-village.ts +9 -0
  442. package/src/shard-recipes/street-affix.ts +88 -13
  443. package/src/shard-recipes/street-bare.ts +10 -0
  444. package/src/shard-recipes/street.ts +7 -0
  445. package/src/shard-recipes/unit.ts +79 -16
  446. package/src/split.ts +28 -7
  447. package/src/synthesize-anchor-absorption.ts +41 -14
  448. package/src/synthesize-boundary-stress.ts +57 -30
  449. package/src/synthesize-german.ts +30 -7
  450. package/src/synthesize-house-venue.ts +22 -16
  451. package/src/synthesize-intersection.ts +34 -7
  452. package/src/synthesize-no-street.ts +18 -10
  453. package/src/synthesize-po-box.ts +42 -10
  454. package/src/synthesize-street.ts +10 -2
  455. package/src/synthesize.ts +81 -38
  456. package/src/tokenize.ts +15 -5
  457. package/src/tools/align-shard.ts +6 -1
  458. package/src/tools/audit.ts +57 -11
  459. package/src/tools/corpus-stats.ts +9 -0
  460. package/src/tools/fetch/ban.ts +32 -10
  461. package/src/tools/fetch/download.ts +49 -12
  462. package/src/tools/fetch/hrsa.ts +3 -0
  463. package/src/tools/fetch/imls-pls.ts +28 -18
  464. package/src/tools/fetch/index.ts +3 -1
  465. package/src/tools/fetch/nad.ts +39 -12
  466. package/src/tools/fetch/nppes.ts +17 -15
  467. package/src/tools/fetch/openaddresses.ts +52 -24
  468. package/src/tools/fetch/ppd.ts +22 -3
  469. package/src/tools/fetch/state-hi-schools.ts +17 -6
  470. package/src/tools/fetch/state-sources.ts +16 -3
  471. package/src/tools/fetch/tiger-full.ts +66 -23
  472. package/src/tools/gb-title-case.ts +1 -0
  473. package/src/tools/golden-expand.ts +64 -20
  474. package/src/tools/golden-promote.ts +53 -13
  475. package/src/tools/ingest-csv.ts +41 -26
  476. package/src/tools/jsonl-to-parquet.ts +30 -10
  477. package/src/tools/lint-shard-vocab.ts +75 -24
  478. package/src/tools/lint-shard.ts +75 -31
  479. package/src/tools/overlay-manifest.ts +7 -4
  480. package/src/tools/shard-kryptonite.ts +11 -3
  481. package/src/tools/shard-translit.ts +20 -5
  482. package/src/types.ts +51 -17
  483. package/src/wof-json.ts +18 -7
@@ -18,6 +18,17 @@
18
18
  import { existsSync, readFileSync, readdirSync } from "node:fs"
19
19
  import { basename, join } from "node:path"
20
20
 
21
+ /**
22
+ * Share of a shard one source may hold before the mix is flagged as dominated by it.
23
+ */
24
+ const DOMINANT_SOURCE_SHARE = 0.4
25
+
26
+ /**
27
+ * How far the leading source may outweigh the runner-up before the mix is flagged. Catches the case where no single
28
+ * source clears {@link DOMINANT_SOURCE_SHARE} but the distribution is still lopsided.
29
+ */
30
+ const MAX_TOP_TO_RUNNER_UP_RATIO = 1.5
31
+
21
32
  export interface AuditOpts {
22
33
  corpusDir: string
23
34
  configPath?: string
@@ -30,11 +41,17 @@ export interface AuditOpts {
30
41
  }
31
42
 
32
43
  interface ShardStats {
33
- /** Shards per source per split */
44
+ /**
45
+ * Shards per source per split
46
+ */
34
47
  bySplit: Record<string, Record<string, number>>
35
- /** Total shards counted (may be less than file count if sampleShardCount caps reads) */
48
+ /**
49
+ * Total shards counted (may be less than file count if sampleShardCount caps reads)
50
+ */
36
51
  totalShards: number
37
- /** Total shards on disk (file count) — equals totalShards unless capped */
52
+ /**
53
+ * Total shards on disk (file count) — equals totalShards unless capped
54
+ */
38
55
  totalFiles: number
39
56
  }
40
57
 
@@ -61,6 +78,7 @@ function parseConfig(configPath: string): ParsedConfig | null {
61
78
  if (sourceWeightsMatch) {
62
79
  inBlock = true
63
80
  blockIndent = sourceWeightsMatch[1]!.length
81
+
64
82
  continue
65
83
  }
66
84
 
@@ -73,12 +91,14 @@ function parseConfig(configPath: string): ParsedConfig | null {
73
91
 
74
92
  if (indent <= blockIndent) {
75
93
  inBlock = false
94
+
76
95
  continue
77
96
  }
97
+
78
98
  const m = raw.match(/^[\t ]+([\w-]+):\s*([\d.]+)/)
79
99
 
80
100
  if (m) {
81
- weights[m[1]!] = parseFloat(m[2]!)
101
+ weights[m[1]!] = Number.parseFloat(m[2]!)
82
102
  }
83
103
  }
84
104
 
@@ -96,9 +116,11 @@ function scanShards(corpusDir: string, sampleCount: number): ShardStats {
96
116
  const splitDir = join(corpusDir, split)
97
117
 
98
118
  if (!existsSync(splitDir)) continue
119
+
99
120
  const files = readdirSync(splitDir)
100
121
  .filter((f) => f.endsWith(".parquet"))
101
- .sort()
122
+ .toSorted()
123
+
102
124
  stats.totalFiles += files.length
103
125
  const sampleEvery = Math.max(1, Math.floor(files.length / sampleCount))
104
126
  const sampled = files.filter((_, i) => i % sampleEvery === 0).slice(0, sampleCount)
@@ -112,12 +134,14 @@ function scanShards(corpusDir: string, sampleCount: number): ShardStats {
112
134
  const inferred = inferSourceFromFilename(f)
113
135
  splitMap[inferred] = (splitMap[inferred] ?? 0) + 1
114
136
  }
137
+
115
138
  // Scale to estimated full-shard counts.
116
139
  const scale = files.length / Math.max(sampled.length, 1)
117
140
 
118
141
  for (const k of Object.keys(splitMap)) {
119
142
  splitMap[k] = Math.round(splitMap[k]! * scale)
120
143
  }
144
+
121
145
  stats.bySplit[split] = splitMap
122
146
  stats.totalShards += files.length
123
147
  }
@@ -165,10 +189,12 @@ const KNOWN_SOURCE_PREFIXES: ReadonlyArray<string> = [
165
189
  "deepseek-translit-armn",
166
190
  ]
167
191
 
168
- /** Extract the source-name prefix from a `first_source_id` value. */
192
+ /**
193
+ * Extract the source-name prefix from a `first_source_id` value.
194
+ */
169
195
  function sourceFromID(sourceID: string, knownPrefixes: readonly string[]): string {
170
196
  // Sort longest-first so usgov-nad beats usgov, wof-admin beats wof.
171
- const sorted = [...knownPrefixes].sort((a, b) => b.length - a.length)
197
+ const sorted = [...knownPrefixes].toSorted((a, b) => b.length - a.length)
172
198
 
173
199
  for (const prefix of sorted) {
174
200
  if (sourceID.startsWith(prefix + "-") || sourceID === prefix) return prefix
@@ -190,6 +216,7 @@ function manifestScan(corpusDir: string, knownPrefixes: readonly string[]): Shar
190
216
  const manifestPath = join(corpusDir, "MANIFEST.json")
191
217
 
192
218
  if (!existsSync(manifestPath)) return null
219
+
193
220
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as {
194
221
  shards?: Array<{ split: string; source?: string | null; first_source_id?: string | null }>
195
222
  }
@@ -203,6 +230,7 @@ function manifestScan(corpusDir: string, knownPrefixes: readonly string[]): Shar
203
230
  bySplit[split] ??= {}
204
231
  bySplit[split][src] = (bySplit[split][src] ?? 0) + 1
205
232
  }
233
+
206
234
  const total = Object.values(bySplit).reduce((sum, m) => sum + Object.values(m).reduce((a, b) => a + b, 0), 0)
207
235
 
208
236
  return { bySplit, totalShards: total, totalFiles: total }
@@ -231,12 +259,14 @@ function buildAuditRows(stats: Record<string, number>, weights: Record<string, n
231
259
  const effective = weight !== undefined ? shards * weight : 0
232
260
  sampleWeights.push([src, effective])
233
261
  }
262
+
234
263
  const totalSampleWeight = sampleWeights.reduce((a, [, w]) => a + w, 0)
235
264
 
236
265
  for (const src of allSources) {
237
266
  const shards = stats[src] ?? 0
238
267
  const weight = weights[src] ?? "—"
239
268
  const effective = typeof weight === "number" ? (shards * weight) / Math.max(totalSampleWeight, 1) : "—"
269
+
240
270
  rows.push({
241
271
  source: src,
242
272
  shards,
@@ -245,6 +275,7 @@ function buildAuditRows(stats: Record<string, number>, weights: Record<string, n
245
275
  effectiveSamplePct: typeof effective === "number" ? effective : "—",
246
276
  })
247
277
  }
278
+
248
279
  // Flag the dominator: empirically calibrated against the v0.3.0 → v0.4.0 retrospective.
249
280
  // v0.3.0 had usgov-nad at 52% effective sample (1.9× ban); the resulting label-space dilution
250
281
  // was responsible for the coarse-F1 regression. So flag a source as "concentration warning"
@@ -252,16 +283,21 @@ function buildAuditRows(stats: Record<string, number>, weights: Record<string, n
252
283
  const numeric = rows.filter((r) => typeof r.effectiveSamplePct === "number") as Array<
253
284
  AuditRow & { effectiveSamplePct: number }
254
285
  >
286
+
255
287
  numeric.sort((a, b) => b.effectiveSamplePct - a.effectiveSamplePct)
256
288
 
257
- if (numeric.length >= 1) {
289
+ if (numeric.length) {
258
290
  const top = numeric[0]!
259
291
  const next = numeric[1]?.effectiveSamplePct ?? 0
260
292
 
261
- if (top.effectiveSamplePct > 0.4 || (next > 0 && top.effectiveSamplePct / next > 1.5)) {
293
+ if (
294
+ top.effectiveSamplePct > DOMINANT_SOURCE_SHARE ||
295
+ (next > 0 && top.effectiveSamplePct / next > MAX_TOP_TO_RUNNER_UP_RATIO)
296
+ ) {
262
297
  top.overweightFactor = next > 0 ? top.effectiveSamplePct / next : Infinity
263
298
  }
264
299
  }
300
+
265
301
  rows.sort((a, b) => b.shards - a.shards)
266
302
 
267
303
  return rows
@@ -279,19 +315,24 @@ function printReport(corpusDir: string, configPath: string | undefined, stats: S
279
315
  if (configPath) {
280
316
  console.log(`Config: ${configPath}`)
281
317
  }
318
+
282
319
  console.log(
283
320
  `Total shards: ${stats.totalShards}${stats.totalFiles !== stats.totalShards ? ` (${stats.totalFiles} files on disk)` : ""}`
284
321
  )
285
322
  console.log("")
323
+
286
324
  const trainStats = stats.bySplit["train"]
287
325
 
288
326
  if (trainStats) {
289
327
  const total = Object.values(trainStats).reduce((a, b) => a + b, 0)
328
+
290
329
  console.log(`Train split: ${total} shards`)
291
330
  console.log("")
331
+
292
332
  const headers = ["source", "shards", "shard %", "weight", "eff. sample %"]
293
333
  const widths = [22, 8, 10, 8, 14]
294
334
  const fmtRow = (cells: string[]) => cells.map((c, i) => c.padEnd(widths[i]!)).join(" ")
335
+
295
336
  console.log(fmtRow(headers))
296
337
  console.log(fmtRow(widths.map((w) => "─".repeat(w))))
297
338
 
@@ -306,12 +347,15 @@ function printReport(corpusDir: string, configPath: string | undefined, stats: S
306
347
  ])
307
348
  )
308
349
  }
350
+
309
351
  console.log("")
352
+
310
353
  const dominator = rows.find((r) => r.overweightFactor !== undefined)
311
354
 
312
355
  if (dominator) {
313
356
  const factor = dominator.overweightFactor
314
357
  const factorStr = factor === Infinity ? "∞" : factor?.toFixed(1)
358
+
315
359
  console.error(
316
360
  `⚠ Concentration: ${dominator.source} would sample ${formatPct(dominator.effectiveSamplePct)} ` +
317
361
  `of training rows (${factorStr}× the next-highest). ` +
@@ -321,17 +365,19 @@ function printReport(corpusDir: string, configPath: string | undefined, stats: S
321
365
  } else {
322
366
  console.log("✓ No single-source concentration (top source < 40% effective sample AND < 1.5× next).")
323
367
  }
368
+
324
369
  const missingWeights = rows.filter((r) => r.weight === "—" && r.shards > 0)
325
370
 
326
- if (missingWeights.length > 0 && configPath) {
371
+ if (missingWeights.length && configPath) {
327
372
  console.error(
328
373
  `⚠ Sources present in corpus but absent from config.source_weights ` +
329
374
  `(loader will skip them): ${missingWeights.map((r) => r.source).join(", ")}`
330
375
  )
331
376
  }
377
+
332
378
  const orphanWeights = rows.filter((r) => typeof r.weight === "number" && r.shards === 0)
333
379
 
334
- if (orphanWeights.length > 0) {
380
+ if (orphanWeights.length) {
335
381
  console.error(
336
382
  `⚠ Sources weighted in config but no shards found in corpus ` +
337
383
  `(no-op weights): ${orphanWeights.map((r) => r.source).join(", ")}`
@@ -82,6 +82,7 @@ n = min(len(tokens_col), ${limit ?? "len(tokens_col)"})
82
82
  for i in range(n):
83
83
  sys.stdout.write(json.dumps({"tokens": tokens_col[i], "labels": labels_col[i]}) + "\\n")
84
84
  `
85
+
85
86
  const buf = execSync(`python3`, { input: py, maxBuffer: 1024 * 1024 * 1024 })
86
87
  const rows: Array<{ tokens: string[]; labels: string[] }> = []
87
88
 
@@ -95,6 +96,7 @@ for i in range(n):
95
96
 
96
97
  export function buildCorpusStats(args: CorpusStatsOptions): void {
97
98
  const shardPaths = discoverShards(args.shardsArg)
99
+
98
100
  console.error(`Discovered ${shardPaths.length} parquet shard(s)`)
99
101
 
100
102
  const tokenStats = new Map<string, Map<string, number>>()
@@ -103,6 +105,7 @@ export function buildCorpusStats(args: CorpusStatsOptions): void {
103
105
 
104
106
  for (const path of shardPaths) {
105
107
  console.error(`Reading ${path}...`)
108
+
106
109
  const rows = streamShardRows(path, args.limitPerShard)
107
110
  totalRows += rows.length
108
111
 
@@ -121,6 +124,7 @@ export function buildCorpusStats(args: CorpusStatsOptions): void {
121
124
  labelMap = new Map()
122
125
  tokenStats.set(tk, labelMap)
123
126
  }
127
+
124
128
  labelMap.set(lb, (labelMap.get(lb) ?? 0) + 1)
125
129
 
126
130
  if (i + 1 < tokens.length) {
@@ -132,10 +136,12 @@ export function buildCorpusStats(args: CorpusStatsOptions): void {
132
136
  bMap = new Map()
133
137
  bigramStats.set(bigramKey, bMap)
134
138
  }
139
+
135
140
  bMap.set(bigramLabel, (bMap.get(bigramLabel) ?? 0) + 1)
136
141
  }
137
142
  }
138
143
  }
144
+
139
145
  console.error(
140
146
  ` ${rows.length} rows; running totals: ${tokenStats.size} unique tokens, ${bigramStats.size} unique bigrams`
141
147
  )
@@ -155,9 +161,11 @@ export function buildCorpusStats(args: CorpusStatsOptions): void {
155
161
 
156
162
  if (total < MIN_BIGRAM_COUNT) {
157
163
  bigramStats.delete(k)
164
+
158
165
  prunedBigrams++
159
166
  }
160
167
  }
168
+
161
169
  console.error(`Pruned ${prunedBigrams} singleton bigrams; ${bigramStats.size} remain`)
162
170
 
163
171
  const out = {
@@ -177,6 +185,7 @@ export function buildCorpusStats(args: CorpusStatsOptions): void {
177
185
 
178
186
  writeFileSync(args.outputPath, JSON.stringify(out))
179
187
  const sizeMB = (Buffer.byteLength(JSON.stringify(out)) / 1024 / 1024).toFixed(1)
188
+
180
189
  console.error(
181
190
  `Wrote ${args.outputPath} (${sizeMB} MB) — ${totalRows} rows, ${tokenStats.size} tokens, ${bigramStats.size} bigrams`
182
191
  )
@@ -29,6 +29,12 @@ import { sha256File } from "@mailwoman/core/utils"
29
29
  import type { BaseFetchOptions, FetchSummary } from "./download.ts"
30
30
  import { downloadToFile, loadManifestEntries, writeManifest } from "./download.ts"
31
31
 
32
+ /**
33
+ * Bytes per KiB — the divisor for human-readable sizes, and the floor below which a "download" is an error page rather
34
+ * than data.
35
+ */
36
+ const BYTES_PER_KIB = 1024
37
+
32
38
  const BASE_URL = "https://adresse.data.gouv.fr/data/ban/adresses/latest/csv"
33
39
 
34
40
  /**
@@ -150,15 +156,18 @@ export interface BanManifestEntry {
150
156
 
151
157
  export type FetchBanOptions = BaseFetchOptions
152
158
 
153
- /** Mimic `numfmt --to=iec` for a friendly byte-size log line. */
159
+ /**
160
+ * Mimic `numfmt --to=iec` for a friendly byte-size log line.
161
+ */
154
162
  function iec(bytes: number): string {
155
- if (bytes < 1024) return String(bytes)
163
+ if (bytes < BYTES_PER_KIB) return String(bytes)
156
164
  const units = ["K", "M", "G", "T", "P"]
157
165
  let value = bytes / 1024
158
166
  let i = 0
159
167
 
160
- while (value >= 1024 && i < units.length - 1) {
168
+ while (value >= BYTES_PER_KIB && i < units.length - 1) {
161
169
  value /= 1024
170
+
162
171
  i++
163
172
  }
164
173
 
@@ -195,7 +204,9 @@ export async function fetchBan(options: FetchBanOptions, report?: (line: string)
195
204
 
196
205
  if (recordedSha && existingSha === recordedSha) {
197
206
  report?.(` → already present + sha matches — skipping`)
207
+
198
208
  skipped++
209
+
199
210
  continue
200
211
  }
201
212
 
@@ -212,40 +223,49 @@ export async function fetchBan(options: FetchBanOptions, report?: (line: string)
212
223
  headers: { "Accept-Encoding": "gzip, br" },
213
224
  report,
214
225
  })
215
- } catch (err) {
216
- report?.(` ✗ download failed: ${url} (${(err as Error).message})`)
226
+ } catch (error) {
227
+ report?.(` ✗ download failed: ${url} (${(error as Error).message})`)
228
+
217
229
  failed++
218
230
  failedCodes.push(code)
231
+
219
232
  continue
220
233
  }
221
234
 
222
235
  // Guard against truncated 404/error pages.
223
236
  const gzSize = statSync(gzFile).size
224
237
 
225
- if (gzSize < 1024) {
238
+ if (gzSize < BYTES_PER_KIB) {
226
239
  report?.(` ✗ response too small (${gzSize} bytes) — probable 404 / error page`)
227
240
  await unlink(gzFile)
241
+
228
242
  failed++
229
243
  failedCodes.push(code)
244
+
230
245
  continue
231
246
  }
232
247
 
233
248
  // Decompress in-place; delete the .gz.
234
249
  try {
235
250
  await writeFile(csvFile, gunzipSync(await readFile(gzFile)))
236
- } catch (err) {
237
- report?.(` ✗ decompress failed: ${(err as Error).message}`)
251
+ } catch (error) {
252
+ report?.(` ✗ decompress failed: ${(error as Error).message}`)
238
253
  await unlink(gzFile)
254
+
239
255
  failed++
240
256
  failedCodes.push(code)
257
+
241
258
  continue
242
259
  }
260
+
243
261
  await unlink(gzFile)
244
262
 
245
263
  if (!existsSync(csvFile)) {
246
264
  report?.(` ✗ decompressed file not found at ${csvFile}`)
265
+
247
266
  failed++
248
267
  failedCodes.push(code)
268
+
249
269
  continue
250
270
  }
251
271
 
@@ -262,6 +282,7 @@ export async function fetchBan(options: FetchBanOptions, report?: (line: string)
262
282
  })
263
283
 
264
284
  report?.(` ✓ ${iec(bytes)} sha256=${sha}`)
285
+
265
286
  fetched++
266
287
 
267
288
  // Be a polite citizen — short pause between requests.
@@ -269,9 +290,10 @@ export async function fetchBan(options: FetchBanOptions, report?: (line: string)
269
290
  }
270
291
 
271
292
  // Write the consolidated MANIFEST.json (entries sorted by dept_code, codepoint order).
272
- const sorted = [...entries.values()].sort((a, b) =>
293
+ const sorted = [...entries.values()].toSorted((a, b) =>
273
294
  a.dept_code < b.dept_code ? -1 : a.dept_code > b.dept_code ? 1 : 0
274
295
  )
296
+
275
297
  await writeManifest(manifestPath, sorted)
276
298
  report?.(`Wrote ${manifestPath} with ${sorted.length} entries.`)
277
299
 
@@ -280,7 +302,7 @@ export async function fetchBan(options: FetchBanOptions, report?: (line: string)
280
302
  report?.(`skipped: ${skipped} (already present + sha matched)`)
281
303
  report?.(`failed: ${failed}`)
282
304
 
283
- if (failedCodes.length > 0) {
305
+ if (failedCodes.length) {
284
306
  report?.(`failed codes: ${failedCodes.join(" ")}`)
285
307
  }
286
308
 
@@ -12,13 +12,34 @@ import { existsSync } from "node:fs"
12
12
  import { readFile, writeFile } from "node:fs/promises"
13
13
  import { setTimeout as sleep } from "node:timers/promises"
14
14
 
15
- /** The option base every `mailwoman corpus fetch <source>` module extends. */
15
+ /**
16
+ * Rate limited — retryable, the server is asking us to back off.
17
+ */
18
+ const HTTP_TOO_MANY_REQUESTS = 429
19
+
20
+ /**
21
+ * Lowest 5xx status. Server-side failures are retryable; 4xx are not.
22
+ */
23
+ const HTTP_SERVER_ERROR_MIN = 500
24
+
25
+ /**
26
+ * Highest 5xx status.
27
+ */
28
+ const HTTP_SERVER_ERROR_MAX = 599
29
+
30
+ /**
31
+ * The option base every `mailwoman corpus fetch <source>` module extends.
32
+ */
16
33
  export interface BaseFetchOptions {
17
- /** Destination root for downloaded source data. Each source writes its own subdirectory. */
34
+ /**
35
+ * Destination root for downloaded source data. Each source writes its own subdirectory.
36
+ */
18
37
  outRoot: string
19
38
  }
20
39
 
21
- /** The per-run result every fetch module returns; the command maps `failed > 0` to exit code 1. */
40
+ /**
41
+ * The per-run result every fetch module returns; the command maps `failed > 0` to exit code 1.
42
+ */
22
43
  export interface FetchSummary {
23
44
  fetched: number
24
45
  skipped: number
@@ -26,19 +47,27 @@ export interface FetchSummary {
26
47
  failedCodes: string[]
27
48
  }
28
49
 
29
- /** A status worth retrying: rate limiting or a server-side failure. */
50
+ /**
51
+ * A status worth retrying: rate limiting or a server-side failure.
52
+ */
30
53
  export function isTransientStatus(status: number): boolean {
31
- return status === 429 || (status >= 500 && status <= 599)
54
+ return status === HTTP_TOO_MANY_REQUESTS || (status >= HTTP_SERVER_ERROR_MIN && status <= HTTP_SERVER_ERROR_MAX)
32
55
  }
33
56
 
34
57
  export interface DownloadOptions {
35
58
  url: string
36
59
  dest: string
37
- /** Per-attempt timeout. Default 10 minutes — these are multi-GB government dumps. */
60
+ /**
61
+ * Per-attempt timeout. Default 10 minutes — these are multi-GB government dumps.
62
+ */
38
63
  timeoutMs?: number
39
- /** Extra attempts after the first, taken only on transient statuses or network errors. Default 0. */
64
+ /**
65
+ * Extra attempts after the first, taken only on transient statuses or network errors. Default 0.
66
+ */
40
67
  retries?: number
41
- /** Delay between attempts. Default 5s. */
68
+ /**
69
+ * Delay between attempts. Default 5s.
70
+ */
42
71
  retryDelayMs?: number
43
72
  headers?: Record<string, string>
44
73
  report?: (line: string) => void
@@ -49,7 +78,7 @@ export interface DownloadOptions {
49
78
  * or once retries are exhausted. Returns the byte count written.
50
79
  */
51
80
  export async function downloadToFile(options: DownloadOptions): Promise<{ bytes: number }> {
52
- const { url, dest, timeoutMs = 600_000, retries = 0, retryDelayMs = 5_000, headers, report } = options
81
+ const { url, dest, timeoutMs = 600_000, retries = 0, retryDelayMs = 5000, headers, report } = options
53
82
  let lastError: unknown
54
83
 
55
84
  for (let attempt = 0; attempt <= retries; attempt++) {
@@ -65,6 +94,7 @@ export async function downloadToFile(options: DownloadOptions): Promise<{ bytes:
65
94
  } catch (error) {
66
95
  // AbortSignal timeouts and network-level failures are retryable.
67
96
  lastError = error
97
+
68
98
  continue
69
99
  }
70
100
 
@@ -73,6 +103,7 @@ export async function downloadToFile(options: DownloadOptions): Promise<{ bytes:
73
103
 
74
104
  if (!isTransientStatus(res.status)) throw error
75
105
  lastError = error
106
+
76
107
  continue
77
108
  }
78
109
 
@@ -90,7 +121,9 @@ export async function downloadToFile(options: DownloadOptions): Promise<{ bytes:
90
121
  throw lastError instanceof Error ? lastError : new Error(String(lastError))
91
122
  }
92
123
 
93
- /** Read a MANIFEST.json; `null` when missing or corrupt (callers re-fetch from scratch). */
124
+ /**
125
+ * Read a MANIFEST.json; `null` when missing or corrupt (callers re-fetch from scratch).
126
+ */
94
127
  export async function readManifest<T>(path: string): Promise<T | null> {
95
128
  if (!existsSync(path)) return null
96
129
 
@@ -101,7 +134,9 @@ export async function readManifest<T>(path: string): Promise<T | null> {
101
134
  }
102
135
  }
103
136
 
104
- /** Load manifest entries into a map so untouched keys survive a partial re-fetch. */
137
+ /**
138
+ * Load manifest entries into a map so untouched keys survive a partial re-fetch.
139
+ */
105
140
  export async function loadManifestEntries<T>(path: string, key: (entry: T) => string): Promise<Map<string, T>> {
106
141
  const entries = new Map<string, T>()
107
142
  const parsed = await readManifest<T[]>(path)
@@ -113,7 +148,9 @@ export async function loadManifestEntries<T>(path: string, key: (entry: T) => st
113
148
  return entries
114
149
  }
115
150
 
116
- /** Write a MANIFEST.json in the house shape: pretty-printed, trailing newline. */
151
+ /**
152
+ * Write a MANIFEST.json in the house shape: pretty-printed, trailing newline.
153
+ */
117
154
  export async function writeManifest(path: string, manifest: unknown): Promise<void> {
118
155
  await writeFile(path, JSON.stringify(manifest, null, 2) + "\n")
119
156
  }
@@ -41,6 +41,7 @@ export async function fetchHRSA(options: FetchHRSAOptions, report?: (line: strin
41
41
  const dest = join(destDir, FILENAME)
42
42
 
43
43
  report?.(`=== ${SLUG} / ${FILENAME}`)
44
+
44
45
  const { bytes } = await downloadToFile({
45
46
  url: SOURCE_URL,
46
47
  dest,
@@ -48,6 +49,7 @@ export async function fetchHRSA(options: FetchHRSAOptions, report?: (line: strin
48
49
  headers: { "Accept-Encoding": "gzip, br" },
49
50
  report,
50
51
  })
52
+
51
53
  const sha = await sha256File(dest)
52
54
 
53
55
  const manifest: SourceManifest = {
@@ -57,6 +59,7 @@ export async function fetchHRSA(options: FetchHRSAOptions, report?: (line: strin
57
59
  sha256: sha,
58
60
  bytes,
59
61
  }
62
+
60
63
  await writeManifest(join(destDir, "MANIFEST.json"), manifest)
61
64
 
62
65
  report?.(` ✓ ${(bytes / 1024 / 1024).toFixed(1)} MB sha256=${sha}`)
@@ -20,6 +20,9 @@
20
20
  * and sha matches MANIFEST, skips download.
21
21
  */
22
22
 
23
+ /* oxlint-disable sister-software/prefer-region-over-marks -- these markers label steps inside one
24
+ procedure, not sections of declarations. A region there folds nothing a reader wants folded. */
25
+
23
26
  import { execFile } from "node:child_process"
24
27
  import { existsSync, mkdirSync, statSync } from "node:fs"
25
28
  import { rm } from "node:fs/promises"
@@ -31,10 +34,17 @@ import { sha256File } from "@mailwoman/core/utils"
31
34
  import type { BaseFetchOptions, FetchSummary } from "./download.ts"
32
35
  import { downloadToFile, readManifest, writeManifest } from "./download.ts"
33
36
 
37
+ /**
38
+ * Bytes per KiB — the divisor for human-readable sizes, and the floor below which a "download" is an error page rather
39
+ * than data.
40
+ */
41
+ const BYTES_PER_KIB = 1024
42
+
34
43
  const execFileAsync = promisify(execFile)
35
44
 
36
- // The PLS FY 2023 bulk CSV ZIP (most recent as of 2026-05).
37
- // If IMLS publishes a newer year, update this URL.
45
+ /**
46
+ * The PLS FY 2023 bulk CSV ZIP (most recent as of 2026-05). If IMLS publishes a newer year, update this URL.
47
+ */
38
48
  const ZIP_URL = "https://www.imls.gov/sites/default/files/2025-08/pls_fy2023_csv.zip"
39
49
  const SLUG = "usgov-imls-pls"
40
50
 
@@ -48,7 +58,9 @@ interface SourceManifest {
48
58
  bytes: number
49
59
  }
50
60
 
51
- /** Return the filenames listed inside a ZIP (the trailing column of each `unzip -l` row). */
61
+ /**
62
+ * Return the filenames listed inside a ZIP (the trailing column of each `unzip -l` row).
63
+ */
52
64
  async function listZipEntries(zipPath: string): Promise<string[]> {
53
65
  const listing = await execFileAsync("unzip", ["-l", zipPath])
54
66
 
@@ -70,9 +82,8 @@ export async function fetchIMLSPLS(
70
82
 
71
83
  report?.(`=== ${SLUG}`)
72
84
 
73
- // ------------------------------------------------------------------
74
- // Idempotency check: if outlet CSV already exists and sha matches, skip.
75
- // ------------------------------------------------------------------
85
+ // MARK: Idempotency check
86
+
76
87
  const recorded = await readManifest<Partial<SourceManifest>>(manifestPath)
77
88
 
78
89
  if (recorded?.sha256 && recorded.filename) {
@@ -85,10 +96,10 @@ export async function fetchIMLSPLS(
85
96
  }
86
97
  }
87
98
 
88
- // ------------------------------------------------------------------
89
- // Download ZIP
90
- // ------------------------------------------------------------------
99
+ // MARK: Download ZIP
100
+
91
101
  report?.(` Downloading ${ZIP_URL} ...`)
102
+
92
103
  const { bytes: zipSize } = await downloadToFile({
93
104
  url: ZIP_URL,
94
105
  dest: zipDest,
@@ -96,19 +107,18 @@ export async function fetchIMLSPLS(
96
107
  headers: { "Accept-Encoding": "gzip, br" },
97
108
  report,
98
109
  })
110
+
99
111
  report?.(` Downloaded: ${(zipSize / 1024 / 1024).toFixed(1)} MB`)
100
112
 
101
- if (zipSize < 1024) {
113
+ if (zipSize < BYTES_PER_KIB) {
102
114
  report?.(` ✗ Response too small (${zipSize} bytes) — probable error page`)
103
115
 
104
116
  return { fetched: 0, skipped: 0, failed: 1, failedCodes: [SLUG] }
105
117
  }
106
118
 
107
- // ------------------------------------------------------------------
108
119
  // Discover the outlet-level CSV inside the ZIP.
109
120
  // Outlet files match: pls_fy*outlet*.csv (case-insensitive)
110
121
  // Administrative-entity files match: pls_fy*ae*.csv — we skip those.
111
- // ------------------------------------------------------------------
112
122
  report?.(" Inspecting ZIP contents ...")
113
123
  const entries = await listZipEntries(zipDest)
114
124
 
@@ -125,6 +135,7 @@ export async function fetchIMLSPLS(
125
135
  for (const name of entries) {
126
136
  report?.(` ${name}`)
127
137
  }
138
+
128
139
  report?.(" ✗ Could not identify outlet CSV — inspect above listing and update this module")
129
140
 
130
141
  return { fetched: 0, skipped: 0, failed: 1, failedCodes: [SLUG] }
@@ -137,15 +148,13 @@ export async function fetchIMLSPLS(
137
148
  const csvSize = statSync(csvDest).size
138
149
  const csvSha = await sha256File(csvDest)
139
150
 
140
- // ------------------------------------------------------------------
141
- // Remove ZIP (small, but keep destDir clean)
142
- // ------------------------------------------------------------------
151
+ // MARK: Remove ZIP (small, but keep destDir clean)
152
+
143
153
  await rm(zipDest, { force: true })
144
154
  report?.(" Removed ZIP (CSV kept)")
145
155
 
146
- // ------------------------------------------------------------------
147
- // Write MANIFEST
148
- // ------------------------------------------------------------------
156
+ // MARK: Write MANIFEST
157
+
149
158
  const manifest: SourceManifest = {
150
159
  source_url: ZIP_URL,
151
160
  downloaded_at: new Date().toISOString(),
@@ -153,6 +162,7 @@ export async function fetchIMLSPLS(
153
162
  sha256: csvSha,
154
163
  bytes: csvSize,
155
164
  }
165
+
156
166
  await writeManifest(manifestPath, manifest)
157
167
 
158
168
  report?.(` ✓ ${(csvSize / 1024 / 1024).toFixed(1)} MB sha256=${csvSha}`)