@mailwoman/corpus 10.3.0 → 10.5.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 (361) hide show
  1. package/README.md +17 -17
  2. package/data/PROVENANCE.md +287 -273
  3. package/data/address-source-register.json +545 -23
  4. package/data/license-decisions.json +120 -0
  5. package/data/licenses/PROVENANCE.md +5 -5
  6. package/data/licenses/cdla-permissive-2.0.md +6 -6
  7. package/data/licenses/geonames-publication.md +5 -5
  8. package/data/licenses/licence-ouverte-2.0.md +3 -3
  9. package/data/licenses/national-address-database.md +12 -12
  10. package/data/licenses/nlod-2.0.md +3 -3
  11. package/data/licenses/sirene-publication.md +13 -6
  12. package/data/licenses/whosonfirst-licenses.md +4 -4
  13. package/data/source-resolutions.json +46 -0
  14. package/data/training-manifests/v0.7.2-address-systems.effective.json +1169 -0
  15. package/data/training-manifests/v0.7.2-address-systems.json +550 -0
  16. package/lib/adapters/cordis/adapter.ts +9 -9
  17. package/lib/adapters/gleif/adapter.ts +7 -8
  18. package/lib/adapters/po-box/adapter.ts +1 -1
  19. package/lib/adapters/source-id.ts +1 -1
  20. package/lib/adapters.ts +4 -2
  21. package/lib/au/adapters/gnaf/adapter.ts +1 -1
  22. package/lib/be/adapters/brussels/adapter.ts +3 -3
  23. package/lib/be/adapters/vlaanderen/adapter.ts +1 -1
  24. package/lib/be/adapters/wallonie/adapter.ts +1 -1
  25. package/lib/build/align.ts +1 -1
  26. package/lib/build.ts +1 -1
  27. package/lib/cz/adapters/cuzk/adapter.ts +2 -2
  28. package/lib/cz/tools/fetch/cuzk.ts +6 -6
  29. package/lib/de/recipes/locale.ts +1 -1
  30. package/lib/dk/adapters/inspire/adapter.ts +1 -1
  31. package/lib/ee/tools/fetch/ads.ts +2 -2
  32. package/lib/es/adapters/bizkaia/adapter.ts +1 -1
  33. package/lib/es/adapters/navarra/adapter.ts +2 -2
  34. package/lib/es/cadastre.ts +2 -2
  35. package/lib/es/tools/fetch/bizkaia.ts +8 -8
  36. package/lib/es/tools/fetch/catastro.ts +6 -6
  37. package/lib/es/tools/fetch/navarra.ts +4 -4
  38. package/lib/fingerprints.ts +3 -3
  39. package/lib/fr/adapters/finess/adapter.ts +6 -5
  40. package/lib/fr/adapters/finess/voie-type.ts +1 -1
  41. package/lib/fr/tools/fetch/annuaire-education.ts +3 -3
  42. package/lib/gb/adapters/os-open/adapter.ts +453 -0
  43. package/lib/gb/constituent-country.ts +52 -0
  44. package/lib/gb/os-open/nearest-link.ts +169 -0
  45. package/lib/gb/os-open/open-names.ts +180 -0
  46. package/lib/gb/os-open/open-roads.ts +145 -0
  47. package/lib/gb/tools/fetch/os-open.ts +251 -0
  48. package/lib/inspire/address.ts +3 -3
  49. package/lib/international/recipes/locale.ts +41 -7
  50. package/lib/it/adapters/anac/adapter.ts +4 -3
  51. package/lib/jp/adapters/wof-admin/adapter.ts +2 -2
  52. package/lib/kr/adapters/juso/label-rows.ts +1 -1
  53. package/lib/kr/adapters/localdata/key-index.ts +2 -1
  54. package/lib/lu/adapters/bd-adresses/adapter.ts +1 -1
  55. package/lib/nl/adapters/kadaster/adapter.ts +2 -2
  56. package/lib/nl/tools/fetch/kadaster.ts +2 -2
  57. package/lib/parquet/shuffle.ts +2 -2
  58. package/lib/parquet/span-sql.ts +8 -6
  59. package/lib/parquet/streams.ts +1 -1
  60. package/lib/parquet/writers.ts +2 -2
  61. package/lib/pl/tools/fetch/emuia.ts +2 -2
  62. package/lib/recipes/scaffold.ts +2 -2
  63. package/lib/recipes/sources.ts +3 -3
  64. package/lib/recipes/sub/venue/render.ts +1 -1
  65. package/lib/recipes/sub/venue/sources.ts +4 -3
  66. package/lib/recipes/sub/venue.ts +4 -3
  67. package/lib/recipes/venue.ts +11 -11
  68. package/lib/registers.ts +7 -0
  69. package/lib/ro/adapters/retea-scolara/adapter.ts +2 -2
  70. package/lib/runner.ts +3 -3
  71. package/lib/sk/tools/fetch/inspire.ts +3 -3
  72. package/lib/source-register/effective-manifest.ts +14 -8
  73. package/lib/source-register/training-manifest.ts +30 -2
  74. package/lib/source-register/types.ts +2 -2
  75. package/lib/source-register.ts +1 -0
  76. package/lib/subvenue/harvest.ts +6 -5
  77. package/lib/subvenue/promotions.ts +3 -3
  78. package/lib/subvenue/surfaces.ts +1 -1
  79. package/lib/subvenue/table.ts +6 -6
  80. package/lib/surfaces/locale.ts +1 -1
  81. package/lib/synthesizers/anchor-absorption.ts +1 -1
  82. package/lib/synthesizers/boundary-stress.ts +3 -2
  83. package/lib/synthesizers/no-street.ts +1 -1
  84. package/lib/synthesizers/po-box.ts +1 -1
  85. package/lib/synthesizers/tail.ts +2 -2
  86. package/lib/test-kit/corpus-recipe.ts +1 -1
  87. package/lib/tools/align-canonical.ts +3 -2
  88. package/lib/tools/audit.ts +1 -1
  89. package/lib/tools/corpus-stats.ts +1 -1
  90. package/lib/tools/fetch/download/network.ts +1 -1
  91. package/lib/tools/fetch/geonames/dump.ts +1 -1
  92. package/lib/tools/fetch/gleif.ts +2 -2
  93. package/lib/tools/fetch/header.ts +1 -1
  94. package/lib/tools/fetch/inspire-addresses.ts +1 -1
  95. package/lib/tools/fetch/vlaanderen-ad.ts +1 -1
  96. package/lib/tools/fetch/wfs-harvest.ts +6 -6
  97. package/lib/tools/fetch/zip-archive.ts +2 -2
  98. package/lib/tools/fetch.ts +2 -0
  99. package/lib/tools/golden/expand.ts +1 -1
  100. package/lib/tools/golden/promote.ts +3 -3
  101. package/lib/tools/golden/relabel-street/directory.ts +2 -1
  102. package/lib/tools/lint/recipe-output/vocab.ts +2 -2
  103. package/lib/tools/lint/recipe-output.ts +1 -1
  104. package/lib/tools/migrate/source-names.ts +1 -1
  105. package/lib/tools/overlay/manifest.ts +62 -10
  106. package/lib/tools/overlay/split-slice.ts +2 -10
  107. package/lib/tools/postcode-triples.ts +2 -2
  108. package/lib/tools/sub/venue/head-nouns.ts +4 -3
  109. package/lib/types.ts +3 -3
  110. package/lib/us/adapters/state/tx-notaries/adapter.ts +1 -1
  111. package/lib/us/adapters/usgov/irs-bmf/adapter.ts +2 -2
  112. package/lib/us/adapters/usgov/nad/adapter.ts +1 -1
  113. package/lib/us/adapters/usgov/nppes/adapter.ts +2 -2
  114. package/lib/us/synthesizers/intersection.ts +4 -4
  115. package/lib/us/synthesizers/street.ts +3 -3
  116. package/lib/us/tools/fetch/nad.ts +2 -2
  117. package/lib/utils/split.ts +59 -6
  118. package/lib/utils/wof-json.ts +1 -1
  119. package/out/adapters/cordis/adapter.d.ts +7 -7
  120. package/out/adapters/cordis/adapter.js +9 -9
  121. package/out/adapters/cordis/adapter.js.map +1 -1
  122. package/out/adapters/gleif/adapter.d.ts +7 -8
  123. package/out/adapters/gleif/adapter.d.ts.map +1 -1
  124. package/out/adapters/gleif/adapter.js +7 -8
  125. package/out/adapters/gleif/adapter.js.map +1 -1
  126. package/out/adapters/po-box/adapter.d.ts +1 -1
  127. package/out/adapters/source-id.d.ts +1 -1
  128. package/out/adapters/source-id.js +1 -1
  129. package/out/adapters.d.ts +2 -2
  130. package/out/adapters.d.ts.map +1 -1
  131. package/out/adapters.js +4 -2
  132. package/out/adapters.js.map +1 -1
  133. package/out/au/adapters/gnaf/adapter.js +1 -1
  134. package/out/be/adapters/brussels/adapter.d.ts +2 -2
  135. package/out/be/adapters/brussels/adapter.js +3 -3
  136. package/out/be/adapters/vlaanderen/adapter.d.ts +1 -1
  137. package/out/be/adapters/wallonie/adapter.d.ts +1 -1
  138. package/out/be/adapters/wallonie/adapter.js +1 -1
  139. package/out/build/align.d.ts +1 -1
  140. package/out/build.js +1 -1
  141. package/out/build.js.map +1 -1
  142. package/out/cz/adapters/cuzk/adapter.d.ts +2 -2
  143. package/out/cz/adapters/cuzk/adapter.js +2 -2
  144. package/out/cz/tools/fetch/cuzk.d.ts +2 -2
  145. package/out/cz/tools/fetch/cuzk.js +4 -4
  146. package/out/cz/tools/fetch/cuzk.js.map +1 -1
  147. package/out/de/recipes/locale.d.ts +1 -1
  148. package/out/de/recipes/locale.js +1 -1
  149. package/out/dk/adapters/inspire/adapter.js +1 -1
  150. package/out/ee/tools/fetch/ads.d.ts +2 -2
  151. package/out/ee/tools/fetch/ads.js +1 -1
  152. package/out/es/adapters/bizkaia/adapter.js +1 -1
  153. package/out/es/adapters/navarra/adapter.d.ts +2 -2
  154. package/out/es/adapters/navarra/adapter.js +2 -2
  155. package/out/es/cadastre.d.ts +2 -2
  156. package/out/es/cadastre.js +2 -2
  157. package/out/es/tools/fetch/bizkaia.d.ts +5 -5
  158. package/out/es/tools/fetch/bizkaia.js +6 -6
  159. package/out/es/tools/fetch/bizkaia.js.map +1 -1
  160. package/out/es/tools/fetch/catastro.d.ts +4 -4
  161. package/out/es/tools/fetch/catastro.js +2 -2
  162. package/out/es/tools/fetch/navarra.d.ts +2 -2
  163. package/out/es/tools/fetch/navarra.js +2 -2
  164. package/out/fingerprints.d.ts +3 -3
  165. package/out/fingerprints.js +3 -3
  166. package/out/fr/adapters/finess/adapter.d.ts +6 -5
  167. package/out/fr/adapters/finess/adapter.d.ts.map +1 -1
  168. package/out/fr/adapters/finess/adapter.js +6 -5
  169. package/out/fr/adapters/finess/adapter.js.map +1 -1
  170. package/out/fr/adapters/finess/voie-type.d.ts +1 -1
  171. package/out/fr/adapters/finess/voie-type.js +1 -1
  172. package/out/fr/tools/fetch/annuaire-education.d.ts +3 -3
  173. package/out/fr/tools/fetch/annuaire-education.js +3 -3
  174. package/out/gb/adapters/os-open/adapter.d.ts +165 -0
  175. package/out/gb/adapters/os-open/adapter.d.ts.map +1 -0
  176. package/out/gb/adapters/os-open/adapter.js +298 -0
  177. package/out/gb/adapters/os-open/adapter.js.map +1 -0
  178. package/out/gb/constituent-country.d.ts +36 -0
  179. package/out/gb/constituent-country.d.ts.map +1 -0
  180. package/out/gb/constituent-country.js +30 -0
  181. package/out/gb/constituent-country.js.map +1 -0
  182. package/out/gb/os-open/nearest-link.d.ts +73 -0
  183. package/out/gb/os-open/nearest-link.d.ts.map +1 -0
  184. package/out/gb/os-open/nearest-link.js +133 -0
  185. package/out/gb/os-open/nearest-link.js.map +1 -0
  186. package/out/gb/os-open/open-names.d.ts +92 -0
  187. package/out/gb/os-open/open-names.d.ts.map +1 -0
  188. package/out/gb/os-open/open-names.js +107 -0
  189. package/out/gb/os-open/open-names.js.map +1 -0
  190. package/out/gb/os-open/open-roads.d.ts +80 -0
  191. package/out/gb/os-open/open-roads.d.ts.map +1 -0
  192. package/out/gb/os-open/open-roads.js +79 -0
  193. package/out/gb/os-open/open-roads.js.map +1 -0
  194. package/out/gb/tools/fetch/os-open.d.ts +91 -0
  195. package/out/gb/tools/fetch/os-open.d.ts.map +1 -0
  196. package/out/gb/tools/fetch/os-open.js +166 -0
  197. package/out/gb/tools/fetch/os-open.js.map +1 -0
  198. package/out/inspire/address.d.ts +3 -3
  199. package/out/inspire/address.js +3 -3
  200. package/out/international/recipes/locale.d.ts +6 -0
  201. package/out/international/recipes/locale.d.ts.map +1 -1
  202. package/out/international/recipes/locale.js +29 -7
  203. package/out/international/recipes/locale.js.map +1 -1
  204. package/out/it/adapters/anac/adapter.d.ts +1 -1
  205. package/out/it/adapters/anac/adapter.d.ts.map +1 -1
  206. package/out/it/adapters/anac/adapter.js +4 -3
  207. package/out/it/adapters/anac/adapter.js.map +1 -1
  208. package/out/jp/adapters/wof-admin/adapter.d.ts +2 -2
  209. package/out/jp/adapters/wof-admin/adapter.js +2 -2
  210. package/out/kr/adapters/juso/label-rows.d.ts +1 -1
  211. package/out/kr/adapters/localdata/key-index.d.ts +2 -1
  212. package/out/kr/adapters/localdata/key-index.d.ts.map +1 -1
  213. package/out/kr/adapters/localdata/key-index.js +2 -1
  214. package/out/kr/adapters/localdata/key-index.js.map +1 -1
  215. package/out/lu/adapters/bd-adresses/adapter.d.ts +1 -1
  216. package/out/lu/adapters/bd-adresses/adapter.js +1 -1
  217. package/out/nl/adapters/kadaster/adapter.js +2 -2
  218. package/out/nl/tools/fetch/kadaster.d.ts +2 -2
  219. package/out/nl/tools/fetch/kadaster.js +2 -2
  220. package/out/parquet/shuffle.d.ts +2 -2
  221. package/out/parquet/shuffle.js +2 -2
  222. package/out/parquet/span-sql.d.ts +8 -6
  223. package/out/parquet/span-sql.d.ts.map +1 -1
  224. package/out/parquet/span-sql.js +8 -6
  225. package/out/parquet/span-sql.js.map +1 -1
  226. package/out/parquet/streams.d.ts +1 -1
  227. package/out/parquet/writers.d.ts +2 -2
  228. package/out/parquet/writers.js +1 -1
  229. package/out/pl/tools/fetch/emuia.d.ts +2 -2
  230. package/out/recipes/scaffold.d.ts +2 -2
  231. package/out/recipes/sources.d.ts +2 -2
  232. package/out/recipes/sources.js +3 -3
  233. package/out/recipes/sources.js.map +1 -1
  234. package/out/recipes/sub/venue/render.d.ts +1 -1
  235. package/out/recipes/sub/venue/render.js +1 -1
  236. package/out/recipes/sub/venue/sources.d.ts +4 -3
  237. package/out/recipes/sub/venue/sources.d.ts.map +1 -1
  238. package/out/recipes/sub/venue/sources.js +4 -3
  239. package/out/recipes/sub/venue/sources.js.map +1 -1
  240. package/out/recipes/sub/venue.d.ts +3 -2
  241. package/out/recipes/sub/venue.d.ts.map +1 -1
  242. package/out/recipes/sub/venue.js +2 -2
  243. package/out/recipes/sub/venue.js.map +1 -1
  244. package/out/recipes/venue.d.ts +7 -6
  245. package/out/recipes/venue.d.ts.map +1 -1
  246. package/out/recipes/venue.js +11 -10
  247. package/out/recipes/venue.js.map +1 -1
  248. package/out/registers.d.ts +7 -0
  249. package/out/registers.d.ts.map +1 -1
  250. package/out/registers.js +7 -0
  251. package/out/registers.js.map +1 -1
  252. package/out/ro/adapters/retea-scolara/adapter.d.ts +2 -2
  253. package/out/ro/adapters/retea-scolara/adapter.js +2 -2
  254. package/out/runner.d.ts +3 -3
  255. package/out/runner.js +2 -2
  256. package/out/sk/tools/fetch/inspire.d.ts +3 -3
  257. package/out/sk/tools/fetch/inspire.js +1 -1
  258. package/out/source-register/effective-manifest.d.ts +11 -5
  259. package/out/source-register/effective-manifest.d.ts.map +1 -1
  260. package/out/source-register/effective-manifest.js +9 -4
  261. package/out/source-register/effective-manifest.js.map +1 -1
  262. package/out/source-register/training-manifest.d.ts +27 -0
  263. package/out/source-register/training-manifest.d.ts.map +1 -1
  264. package/out/source-register/training-manifest.js +13 -1
  265. package/out/source-register/training-manifest.js.map +1 -1
  266. package/out/source-register/types.d.ts +2 -2
  267. package/out/source-register.d.ts +1 -1
  268. package/out/source-register.d.ts.map +1 -1
  269. package/out/source-register.js +1 -1
  270. package/out/source-register.js.map +1 -1
  271. package/out/subvenue/harvest.d.ts +4 -3
  272. package/out/subvenue/harvest.d.ts.map +1 -1
  273. package/out/subvenue/harvest.js +6 -5
  274. package/out/subvenue/harvest.js.map +1 -1
  275. package/out/subvenue/promotions.d.ts +2 -2
  276. package/out/subvenue/promotions.js +3 -3
  277. package/out/subvenue/promotions.js.map +1 -1
  278. package/out/subvenue/surfaces.d.ts +1 -1
  279. package/out/subvenue/table.d.ts +6 -6
  280. package/out/subvenue/table.js +1 -1
  281. package/out/surfaces/locale.d.ts +1 -1
  282. package/out/surfaces/locale.js +1 -1
  283. package/out/synthesizers/boundary-stress.d.ts.map +1 -1
  284. package/out/synthesizers/boundary-stress.js +2 -1
  285. package/out/synthesizers/boundary-stress.js.map +1 -1
  286. package/out/synthesizers/tail.d.ts +2 -2
  287. package/out/synthesizers/tail.js +2 -2
  288. package/out/test-kit/corpus-recipe.d.ts +1 -1
  289. package/out/test-kit/corpus-recipe.js +1 -1
  290. package/out/tools/align-canonical.d.ts.map +1 -1
  291. package/out/tools/align-canonical.js +3 -2
  292. package/out/tools/align-canonical.js.map +1 -1
  293. package/out/tools/audit.js +1 -1
  294. package/out/tools/corpus-stats.d.ts +1 -1
  295. package/out/tools/fetch/download/network.d.ts +1 -1
  296. package/out/tools/fetch/download/network.js +1 -1
  297. package/out/tools/fetch/geonames/dump.d.ts +1 -1
  298. package/out/tools/fetch/geonames/dump.js +1 -1
  299. package/out/tools/fetch/gleif.d.ts +2 -2
  300. package/out/tools/fetch/gleif.js +1 -1
  301. package/out/tools/fetch/header.d.ts +1 -1
  302. package/out/tools/fetch/header.js +1 -1
  303. package/out/tools/fetch/inspire-addresses.d.ts +1 -1
  304. package/out/tools/fetch/vlaanderen-ad.d.ts +1 -1
  305. package/out/tools/fetch/wfs-harvest.d.ts +3 -3
  306. package/out/tools/fetch/wfs-harvest.js +4 -4
  307. package/out/tools/fetch/wfs-harvest.js.map +1 -1
  308. package/out/tools/fetch/zip-archive.d.ts +2 -2
  309. package/out/tools/fetch/zip-archive.js +2 -2
  310. package/out/tools/fetch.d.ts +2 -0
  311. package/out/tools/fetch.d.ts.map +1 -1
  312. package/out/tools/fetch.js +2 -0
  313. package/out/tools/fetch.js.map +1 -1
  314. package/out/tools/golden/expand.js +1 -1
  315. package/out/tools/golden/expand.js.map +1 -1
  316. package/out/tools/golden/promote.d.ts +1 -1
  317. package/out/tools/golden/promote.js +3 -3
  318. package/out/tools/golden/promote.js.map +1 -1
  319. package/out/tools/golden/relabel-street/directory.d.ts +2 -1
  320. package/out/tools/golden/relabel-street/directory.d.ts.map +1 -1
  321. package/out/tools/golden/relabel-street/directory.js.map +1 -1
  322. package/out/tools/lint/recipe-output/vocab.js +2 -2
  323. package/out/tools/lint/recipe-output.d.ts +1 -1
  324. package/out/tools/migrate/source-names.d.ts +1 -1
  325. package/out/tools/overlay/manifest.d.ts +15 -2
  326. package/out/tools/overlay/manifest.d.ts.map +1 -1
  327. package/out/tools/overlay/manifest.js +41 -10
  328. package/out/tools/overlay/manifest.js.map +1 -1
  329. package/out/tools/overlay/split-slice.d.ts.map +1 -1
  330. package/out/tools/overlay/split-slice.js +2 -9
  331. package/out/tools/overlay/split-slice.js.map +1 -1
  332. package/out/tools/postcode-triples.d.ts +1 -1
  333. package/out/tools/postcode-triples.js +2 -2
  334. package/out/tools/postcode-triples.js.map +1 -1
  335. package/out/tools/sub/venue/head-nouns.d.ts +1 -1
  336. package/out/tools/sub/venue/head-nouns.d.ts.map +1 -1
  337. package/out/tools/sub/venue/head-nouns.js +4 -3
  338. package/out/tools/sub/venue/head-nouns.js.map +1 -1
  339. package/out/types.d.ts +3 -3
  340. package/out/types.js +1 -1
  341. package/out/us/adapters/state/tx-notaries/adapter.d.ts +1 -1
  342. package/out/us/adapters/state/tx-notaries/adapter.js +1 -1
  343. package/out/us/adapters/usgov/irs-bmf/adapter.d.ts +1 -1
  344. package/out/us/adapters/usgov/irs-bmf/adapter.js +2 -2
  345. package/out/us/adapters/usgov/nad/adapter.d.ts +1 -1
  346. package/out/us/adapters/usgov/nad/adapter.js +1 -1
  347. package/out/us/adapters/usgov/nppes/adapter.d.ts +1 -1
  348. package/out/us/adapters/usgov/nppes/adapter.js +1 -1
  349. package/out/us/synthesizers/intersection.js +4 -4
  350. package/out/us/synthesizers/intersection.js.map +1 -1
  351. package/out/us/synthesizers/street.js +3 -3
  352. package/out/us/synthesizers/street.js.map +1 -1
  353. package/out/us/tools/fetch/nad.d.ts +2 -2
  354. package/out/us/tools/fetch/nad.js +2 -2
  355. package/out/utils/split.d.ts +21 -0
  356. package/out/utils/split.d.ts.map +1 -1
  357. package/out/utils/split.js +49 -5
  358. package/out/utils/split.js.map +1 -1
  359. package/out/utils/wof-json.d.ts +1 -1
  360. package/out/utils/wof-json.js +1 -1
  361. package/package.json +15 -14
@@ -71,9 +71,10 @@ export interface SubVenueHarvestRow {
71
71
  /**
72
72
  * Classify an OSM `ref` into an {@link IdentifierShape} class.
73
73
  *
74
- * The classes are the ones Berlin's gates actually produced, plus the two aviation forms
74
+ * The classes are the ones Berlin's airport gates produced, plus the two aviation forms
75
75
  * the corpus task names (`Terminal 2F` is digit-letter, `Concourse B` is letter).
76
- * `range` covers both separators OSM uses for a gate serving more than one stand: `16-18` and `0/1`.
76
+ * `range` covers both separators OSM uses for a boarding gate serving more
77
+ * than one stand: `16-18` and `0/1`.
77
78
  */
78
79
  export function classifyIdentifier(ref: string): string {
79
80
  const value = ref.trim()
@@ -92,10 +93,10 @@ export function classifyIdentifier(ref: string): string {
92
93
  }
93
94
 
94
95
  /**
95
- * How many real `ref` values each {@link IdentifierShape} keeps.
96
+ * The number of real `ref` values each {@link IdentifierShape} keeps.
96
97
  *
97
98
  * Eight rather than "all" and not one.
98
- * The field exists so a recipe author can see what a class actually contains.
99
+ * The field exists so a recipe author can see what a class contains.
99
100
  *
100
101
  * GB's `other` class turned out to be semicolon multi-values (`1.2.3`, `13.14`),
101
102
  * which one example would have hidden and which the class name does not say.
@@ -128,7 +129,7 @@ export interface HarvestOptions {
128
129
  /**
129
130
  * Harvest attested phrases and identifier shapes out of a source's rows.
130
131
  *
131
- * `index` gates the name harvest.
132
+ * `index` decides whether the name harvest runs.
132
133
  * A name contributes only when it contains a phrase already in the table.
133
134
  *
134
135
  * That filter is the whole reason this function is safe to run over raw OSM:
@@ -60,8 +60,8 @@ export interface SubVenuePromotion {
60
60
  * This ledger is the only input that sets `curated: true` on a machine-derived surface.
61
61
  * Rejections stay in the ledger so the system can reject duplicate proposals until new evidence arrives.
62
62
  *
63
- * The span proposer in `packages/neural/lib/venue/structure.ts` ships its English designators
64
- * without a locale gate, so a rejection here guides recipes only and does not stop the proposer.
63
+ * The span proposer in `packages/neural/lib/venue/structure.ts` ships its English designators without
64
+ * a locale restriction, so a rejection here guides recipes only and does not stop the proposer.
65
65
  */
66
66
  export const SUBVENUE_PROMOTIONS: readonly SubVenuePromotion[] = [
67
67
  // wing
@@ -171,7 +171,7 @@ export const SUBVENUE_PROMOTIONS: readonly SubVenuePromotion[] = [
171
171
  census: "france.jsonl 2026-08-05 (251,260 rows), 40 hits; all 19 shape hits enumerated",
172
172
  },
173
173
 
174
- // gate: the English surface ships without modifier eligibility.
174
+ // The airport gate designator: the English surface ships without modifier eligibility.
175
175
  // These entries cover its localized forms.
176
176
  {
177
177
  designatorID: "gate",
@@ -42,7 +42,7 @@ export function normalizeSurface(text: string): string {
42
42
  }
43
43
 
44
44
  /**
45
- * A phrase-to-record index keyed by the normalized phrase.
45
+ * The normalized phrase keys this phrase-to-record index.
46
46
  * It attributes a matched phrase to its record.
47
47
  */
48
48
  export type SurfaceIndex = ReadonlyMap<string, { recordID: string; recordKind: "designator" | "modifier" }>
@@ -104,13 +104,13 @@ export interface SubVenueSurface {
104
104
  /**
105
105
  * Whether a human has approved this surface for parsing use in its region.
106
106
  *
107
- * Machine-derived entries start `false`; only a matching {@link SubVenuePromotion}
108
- * flips it, so a consumer that gates a parse must filter on this.
107
+ * Machine-derived entries start `false`; only a matching {@link SubVenuePromotion} flips it,
108
+ * so a consumer that accepts or refuses a parse by this table must filter on this.
109
109
  */
110
110
  curated: boolean
111
111
  /**
112
- * How many source features attested this exact phrase when the source counts (OSM, Overture);
113
- * Vocabulary sources attest phrase existence and use a count of `0`.
112
+ * The number of source features that attested this exact phrase when the source counts
113
+ * (OSM, Overture); Vocabulary sources attest phrase existence and use a count of `0`.
114
114
  * OSM and Overture sources count occurrences.
115
115
  */
116
116
  observations: number
@@ -138,7 +138,7 @@ export interface IdentifierShape {
138
138
  /**
139
139
  * ISO 3166-1 alpha-2 of the extract this distribution was measured in.
140
140
  *
141
- * It is per-region because the shapes differ (GB gates are 70% bare digits, Japanese platform
141
+ * It is per-region because the shapes differ (GB airport gates are 70% bare digits, Japanese platform
142
142
  * refs a different range), so a recipe for a French address should sample France's distribution.
143
143
  */
144
144
  region: string
@@ -190,7 +190,7 @@ export interface SubVenueLexiconTable {
190
190
  * below for the module docstring's dependency-direction reason.
191
191
  *
192
192
  * `tier` is added here: the seven WOF placetypes plus `terminal`/`gate` are all venue-interior,
193
- * while `campus` and `building` name a whole venue as often as a part of one
193
+ * while `campus` and `building` name a whole venue about as frequently as a part of one
194
194
  * but are marked `subvenue` because that is the role the span proposer uses them in.
195
195
  */
196
196
  export const SHIPPED_DESIGNATOR_SEED: ReadonlyArray<{
@@ -156,7 +156,7 @@ function tokenPresent(raw: string, value: string): boolean {
156
156
  * Render one tuple into a locale-ordered `{raw, components}` row via OpenCage templates,
157
157
  * with light variation (house number and postcode may be dropped).
158
158
  *
159
- * Returns `null` when the tuple is too thin or a component would not align cleanly.
159
+ * Returns `null` when the tuple is too thin or a component would not align to an exact span.
160
160
  *
161
161
  * Region handling depends on order:
162
162
  * - native omits region for alignment reliability
@@ -16,7 +16,7 @@ import { sample } from "@mailwoman/core/random"
16
16
  /* oxlint-disable sister-software/no-unnamed-threshold -- the bare decimals below are weighted-sampler
17
17
  cutoffs rather than thresholds: `const r = random()` followed by a cascade of `r < 0.4` branches defines the
18
18
  output distribution. The cascade shows each weight from top to bottom. A separate constant for each cutoff
19
- would hide the distribution behind a wall of identifiers. Genuine thresholds in these files are
19
+ would hide the distribution behind a wall of identifiers. Decision thresholds in these files are
20
20
  extracted as constants above. */
21
21
 
22
22
  /**
@@ -20,7 +20,7 @@ import type { CanonicalRow } from "#types"
20
20
  /* oxlint-disable sister-software/no-unnamed-threshold -- the bare decimals below are weighted-sampler
21
21
  cutoffs rather than thresholds: `const r = random()` followed by a cascade of `r < 0.4` branches is the
22
22
  output distribution. The cascade shows each weight from top to bottom. A separate constant for each cutoff
23
- would hide the distribution behind identifiers. The genuine thresholds in these files are
23
+ would hide the distribution behind identifiers. The decision thresholds in these files are
24
24
  extracted as constants above. */
25
25
 
26
26
  /**
@@ -348,7 +348,8 @@ export function synthesizeBoundaryStressRow(
348
348
 
349
349
  // The layout renders the tail in the country's order: `75005 Paris` for FR
350
350
  // and `Boston, MA 02116` for the US.
351
- // Dropping the layout's commas is a surface perturbation that applies to any country.
351
+ // The synthesizer drops the layout's commas.
352
+ // That is a surface perturbation that applies to any country.
352
353
  const tail = layoutAdminTail(b.country, {
353
354
  locality: b.locality,
354
355
  region: b.region,
@@ -21,7 +21,7 @@ import type { CanonicalRow } from "#types"
21
21
  /* oxlint-disable sister-software/no-unnamed-threshold -- the bare decimals below are weighted-sampler
22
22
  cutoffs rather than thresholds: `const r = random()` followed by a cascade of `r < 0.4` branches defines the
23
23
  output distribution. The cascade shows each weight from top to bottom. A separate constant for each cutoff
24
- would hide the distribution behind a wall of identifiers. Genuine thresholds in these files are
24
+ would hide the distribution behind a wall of identifiers. Decision thresholds in these files are
25
25
  extracted as constants above. */
26
26
 
27
27
  export interface NoStreetBaseTuple {
@@ -21,7 +21,7 @@ const MIN_DIGITS_FOR_HYPHEN_GROUPING = 3
21
21
  /* oxlint-disable sister-software/no-unnamed-threshold -- the bare decimals below are weighted-sampler
22
22
  cutoffs rather than thresholds. `const r = random()` followed by `r < 0.4` branches defines the
23
23
  output distribution. The branches show each weight from top to bottom. A separate constant for each cutoff
24
- would hide the distribution behind identifiers. Genuine thresholds in these files are
24
+ would hide the distribution behind identifiers. Decision thresholds in these files are
25
25
  constants defined above. */
26
26
 
27
27
  /**
@@ -7,9 +7,9 @@
7
7
  * synthesizer writes `Chicago, IL 60613`, `75005 Paris`, `London, EC3N 1DE` or
8
8
  * `Maracaibo 4001, Zulia` from one call rather than from a template per country.
9
9
  *
10
- * The layout decides which parts print: the FR layout prints no region, so a FR tail carries none
10
+ * The layout decides which parts print: the FR layout prints no region, so a FR tail holds none
11
11
  * and the returned components say so. The lines the layout writes are joined with the country's
12
- * line join, which is the one-line form the corpus rows take.
12
+ * line join. The result is the one-line form the corpus rows take.
13
13
  */
14
14
 
15
15
  import { formatAddressRow } from "@mailwoman/codex/address/format"
@@ -70,7 +70,7 @@ export async function scratch(prefix: string, tuples: object[], surfaces: string
70
70
  export const TEST_REGISTER = "test-harness"
71
71
 
72
72
  /**
73
- * Bind a recipe and its seed to a runner the tests call with just the tuples and reserved surfaces.
73
+ * Bind a recipe and its seed to a runner the tests call with only the tuples and reserved surfaces.
74
74
  *
75
75
  * The seed is required because these suites assert on generated distributions.
76
76
  */
@@ -7,8 +7,9 @@ import { createNewlineWriter, JSONSpliterator } from "spliterator"
7
7
  *
8
8
  * Most synthetic recipe outputs are generated on demand by a recipe (parametrized by --count),
9
9
  * so re-emitting them in a new label format is a re-run.
10
- * A few are fixed corpora with a hand/DeepSeek-authored canonical source that is never regenerated,
11
- * notably `deepseek-kryptonite` (the adversarial hard-case set) and the `deepseek-translit-*` variants.
10
+ * A few are fixed corpora with a hand/DeepSeek-authored canonical source that is never regenerated.
11
+ *
12
+ * They include `deepseek-kryptonite` (the adversarial hard-case set) and the `deepseek-translit-*` variants.
12
13
  *
13
14
  * Their committed parquets use the label format that was current when they were first built.
14
15
  *
@@ -130,7 +130,7 @@ async function scanParquetFiles(corpusDir: PathBuilderLike, sampleCount: number)
130
130
  }
131
131
 
132
132
  function inferSourceFromFilename(filename: string): string {
133
- // Many corpus builds write part-<source>-<n>.parquet or part-<n>.parquet.
133
+ // Some corpus builds write part-<source>-<n>.parquet or part-<n>.parquet.
134
134
  // The latter (current build at corpus-v0.3.0) gives no source signal in the filename.
135
135
  // See manifestScan() for the authoritative path.
136
136
  // Return "<unknown>" so the caller flags this case.
@@ -57,7 +57,7 @@ export interface CorpusStatsOptions {
57
57
  parquetPath: string
58
58
  outputPath: string
59
59
  /**
60
- * Read at most this many rows per parquet file.
60
+ * Read at most this number of rows per parquet file.
61
61
  */
62
62
  limitPerFile?: number
63
63
  }
@@ -82,7 +82,7 @@ export interface SourceManifest {
82
82
  }
83
83
 
84
84
  /**
85
- * A status worth retrying: rate limiting or a server-side failure.
85
+ * A status the download retries: rate limiting or a server-side failure.
86
86
  */
87
87
  /**
88
88
  * An HTTP failure that includes its status, so callers branch on `error.status`
@@ -116,7 +116,7 @@ const GAZETTEER_DUMP_COLUMNS = 19
116
116
  * The capitals builder already holds each country dump as a string.
117
117
  *
118
118
  * A byte-oriented spliterator would UTF-8 encode that input and allocate up to
119
- * another 350 MB just to inspect the first non-empty line.
119
+ * another 350 MB only to inspect the first non-empty line.
120
120
  */
121
121
  export function looksLikeGazetteerDump(text: string): boolean {
122
122
  let start = 0
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * 1. **The API's `size` is the archive's length.** It stated 507,006,221 and the host answered a
16
16
  * `HEAD` with `content-length` 507,006,221 and a one-byte range with `Content-Range: bytes
17
- * 0-9/507006221`. The transfer is checked against the API's figure, so a body cut short or a
17
+ * 0-9/507006221`. The transfer is checked against the API's figure, so a truncated body or a
18
18
  * publish replaced mid-transfer is refused rather than recorded.
19
19
  * 2. **The host honors `Range`.** An interrupted transfer resumes through
20
20
  * {@linkcode resumableDownload}. The request names `accept-encoding: identity`, because the host
@@ -79,7 +79,7 @@ export interface GLEIFPublishedFile {
79
79
  }
80
80
 
81
81
  /**
82
- * The receipt written beside the archive.
82
+ * The receipt that sits beside the archive.
83
83
  */
84
84
  export interface GLEIFGoldenCopyReceipt {
85
85
  source: string
@@ -6,7 +6,7 @@
6
6
  * This module reads a delimited file's header line and refuses one that drops a column an adapter
7
7
  * indexes.
8
8
  *
9
- * A fetch module checks the file it just wrote against the columns its adapter reads by name,
9
+ * A fetch module checks the file it has written against the columns its adapter reads by name,
10
10
  * because a renamed column reaches the adapter as an empty string on every row rather than as an
11
11
  * error. The check belongs here rather than in each publisher's module: every caller needs the same
12
12
  * two steps, and each one that wrote them again would also be a second answer to how the file is
@@ -131,7 +131,7 @@ export interface WFSCapabilities {
131
131
  /**
132
132
  * A value a GeoServer JSON response uses for a voidable property that holds no value.
133
133
  *
134
- * INSPIRE marks many attributes voidable.
134
+ * The INSPIRE schema marks a set of attributes voidable.
135
135
  * A voidable property must be present with either a value or a void with a reason.
136
136
  *
137
137
  * GeoServer encodes that void as an object rather than as `null`, so `String(value)`
@@ -146,7 +146,7 @@ export interface HarvestVlaanderenOptions {
146
146
  */
147
147
  outputDir: PathBuilderLike
148
148
  /**
149
- * Stop after this many address pages, for a probe rather than a full harvest.
149
+ * Stop after this number of address pages, for a probe rather than a full harvest.
150
150
  */
151
151
  maxPages?: number
152
152
  /**
@@ -3,7 +3,7 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * The paging, manifest and resumption machinery a paged WFS harvest shares.
6
+ * The paging, manifest and resumption code that every paged WFS harvest shares.
7
7
  *
8
8
  * `./inspire-addresses.ts` owns what an INSPIRE Addresses service exposes — its capabilities, its
9
9
  * feature-type names and its GeoJSON pages. This module owns what a harvest of any paged WFS does
@@ -75,7 +75,7 @@ export interface MarkupRoot {
75
75
  */
76
76
  attributes: string
77
77
  /**
78
- * The offset just past the opening tag.
78
+ * The offset of the first character after the opening tag.
79
79
  */
80
80
  contentStart: number
81
81
  }
@@ -158,7 +158,7 @@ export function rootContent(body: string, root: MarkupRoot, context: string): st
158
158
  /**
159
159
  * The attributes a page states about itself rather than about the feature type.
160
160
  *
161
- * A harvest assembles many pages under one root element, so the first page's counts
161
+ * A harvest assembles several pages under one root element, so the first page's counts
162
162
  * and timestamp would describe the assembled file wrongly.
163
163
  * They are dropped and the manifest records them per page.
164
164
  */
@@ -420,7 +420,7 @@ export interface PagedWFSHarvestOptions {
420
420
  featureCount: FeatureCountStatement
421
421
  pageSize: number
422
422
  /**
423
- * Stop once the manifest holds this many pages, for a probe rather than a full harvest.
423
+ * Stop once the manifest holds this number of pages, for a probe rather than a full harvest.
424
424
  *
425
425
  * A cap on the manifest rather than on the run, so a second run under the same cap makes no request.
426
426
  */
@@ -437,7 +437,7 @@ export interface PagedWFSHarvestOptions {
437
437
  * Does a recorded manifest describe the request being made now?
438
438
  *
439
439
  * A harvest taken at another page size, ordering, format or type cannot be continued:
440
- * its pages were cut at different boundaries, and appending to them would leave
440
+ * its pages were split at different boundaries, and appending to them would leave
441
441
  * a file whose manifest describes neither half.
442
442
  */
443
443
  function describesSameRequest(manifest: WFSHarvestManifest, options: PagedWFSHarvestOptions): boolean {
@@ -932,7 +932,7 @@ export async function runWFSHarvest(
932
932
  if (options.client) {
933
933
  manifest = await options.harvest(options.client)
934
934
  } else {
935
- // Bounded retry, because a harvest is many requests and one 5xx would otherwise end the run.
935
+ // Bounded retry, because a harvest is a long series of requests and one 5xx would otherwise end the run.
936
936
  await using client = new APIClient({
937
937
  displayName: options.displayName,
938
938
  minRequestIntervalMs: options.minRequestIntervalMs,
@@ -4,8 +4,8 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  * @file Downloads one zip archive through an {@linkcode APIClient} and answers what arrived.
6
6
  *
7
- * An INSPIRE ATOM service that publishes one archive per area is harvested through many small
8
- * requests rather than through one large transfer, so the transfer runs on the client the harvest
7
+ * An INSPIRE ATOM service that publishes one archive per area is harvested through one small
8
+ * request per area rather than through one large transfer, so the transfer runs on the client the harvest
9
9
  * already paces and a test can drive it over a stubbed transport. `downloadToFile` runs on global
10
10
  * `fetch`, which a unit test cannot intercept.
11
11
  *
@@ -19,6 +19,7 @@ import { fetchRyhti } from "#fi/tools/fetch/ryhti"
19
19
  import { fetchAnnuaireEducationOverseas } from "#fr/tools/fetch/annuaire-education"
20
20
  import { fetchBan } from "#fr/tools/fetch/ban"
21
21
  import { fetchFinessOverseas } from "#fr/tools/fetch/finess"
22
+ import { fetchGBOSOpen } from "#gb/tools/fetch/os-open"
22
23
  import { fetchITANAC } from "#it/tools/fetch/anac"
23
24
  import { fetchHoujinJP } from "#jp/tools/fetch/houjin"
24
25
  import { fetchJusoKR } from "#kr/tools/fetch/juso"
@@ -67,6 +68,7 @@ export const FETCH_SOURCES = {
67
68
  "es-navarra": fetchESNavarra,
68
69
  "fr-annuaire-education-overseas": fetchAnnuaireEducationOverseas,
69
70
  "fr-finess-overseas": fetchFinessOverseas,
71
+ "gb-os-open": fetchGBOSOpen,
70
72
  "gcis-tw": fetchGCISTW,
71
73
  gleif: fetchGLEIF,
72
74
  "houjin-jp": fetchHoujinJP,
@@ -231,7 +231,7 @@ async function loadSeeds(
231
231
 
232
232
  // Stratified sampling: read all rows from all parquet files, group by source.
233
233
  // Bounded by per-source reservoir: keep at most max(2*count, 5000) rows per source
234
- // so we don't blow memory on train parquet files (1M rows × many files).
234
+ // so we don't blow memory on train parquet files (1M rows per file, across every file).
235
235
  // Later samples are uniform within each pool.
236
236
  const bySource = new Map<string, Seed[]>()
237
237
  const PER_SOURCE_CAP = Math.max(2 * count, 5000)
@@ -13,7 +13,7 @@
13
13
  * 3. Applies filters that drop candidates unlikely to be human-typed:
14
14
  *
15
15
  * - Components-glued-without-commas (5+ components but <2 separators → freeform jumble)
16
- * - Postcode-at-start with many other components (US/UK conventions put postcode last. FR puts it
16
+ * - Postcode-at-start with several other components (US/UK conventions put postcode last. FR puts it
17
17
  * before locality only)
18
18
  * - Suspicious-token signals (unmatched brackets, control chars, etc.)
19
19
  * 4. Dedupes by normalized raw (case-insensitive, whitespace-collapsed)
@@ -47,7 +47,7 @@ import type { GoldenCandidateEntry as GoldenEntry } from "#utils/golden"
47
47
  // ── Types ──────────────────────────────────────────────────────────────────
48
48
 
49
49
  /**
50
- * Components a candidate needs before it is rich enough to be worth a golden-set slot.
50
+ * Components a candidate needs before it qualifies for a golden-set slot.
51
51
  */
52
52
  const MIN_GOLDEN_COMPONENTS = 5
53
53
 
@@ -153,7 +153,7 @@ function isSuspicious(entry: GoldenEntry): boolean {
153
153
 
154
154
  if (raw.split('"').length > MAX_QUOTE_SEGMENTS) return true
155
155
 
156
- // too many quote marks
156
+ // more quote marks than MAX_QUOTE_SEGMENTS admits
157
157
  return false
158
158
  }
159
159
 
@@ -36,7 +36,8 @@ import {
36
36
  export type GoldenRelabelCounts = Record<GoldenRelabelClass, number>
37
37
 
38
38
  /**
39
- * One line of the review deck: what a changed (or notably unchanged) row looked like before and after.
39
+ * One line of the review deck: what a changed row, or an unchanged row the deck
40
+ * lists for review, looked like before and after.
40
41
  */
41
42
  export interface GoldenRelabelDeckEntry {
42
43
  file: string
@@ -47,7 +47,7 @@ function stripBIO(label: string): string {
47
47
  *
48
48
  * Pure-numeric tokens (house_number and postcode) are context-determined
49
49
  * rather than lexical vocab, so they are excluded.
50
- * `\p{Nd}` covers the decimal digits these address corpora actually contain.
50
+ * `\p{Nd}` covers the decimal digits these address corpora contain.
51
51
  */
52
52
  function isDigit(token: string): boolean {
53
53
  return token.length > 0 && /^\p{Nd}+$/u.test(token)
@@ -148,7 +148,7 @@ async function readRows(con: DuckDBConnection, path: string): Promise<CorpusRow[
148
148
  }
149
149
 
150
150
  /**
151
- * Read just the first row's `source` value, used to group base parts for a proportional sample.
151
+ * Read only the first row's `source` value, used to group base parts for a proportional sample.
152
152
  */
153
153
  async function readSource(con: DuckDBConnection, path: string): Promise<string> {
154
154
  const result = await con.runAndReadAll(`SELECT source FROM read_parquet('${path}') LIMIT 1`)
@@ -168,7 +168,7 @@ function majorityLabel(distribution: Map<string, number> | Record<string, number
168
168
  }
169
169
 
170
170
  /**
171
- * One lint flag emitted by a check.
171
+ * A check emits this lint flag.
172
172
  */
173
173
  export interface LintFlag {
174
174
  check: string
@@ -36,7 +36,7 @@ export interface SourceNameRewrite {
36
36
  parquet: string
37
37
  rows: number
38
38
  /**
39
- * The mapping applied, keyed by the retired spelling.
39
+ * The migration applied this mapping, keyed by the retired spelling.
40
40
  *
41
41
  * A file whose rows already use current spellings is reported with an empty map and left untouched.
42
42
  */
@@ -3,7 +3,8 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * Assembles a corpus manifest that keeps every file of a base corpus and adds new parquet files.
6
+ * Assembles a corpus manifest from a base corpus's files and new parquet files.
7
+ * A replaced source's base files are dropped.
7
8
  */
8
9
 
9
10
  import { dataRootPath } from "@mailwoman/core/data-root"
@@ -203,6 +204,10 @@ export interface OverlayPlan {
203
204
  * The filename was the only place that showed this (#2359).
204
205
  */
205
206
  files: Array<{ parquet: string; source: string; split: SplitName; route: SplitRoute }>
207
+ /**
208
+ * The base sources whose files this overlay dropped.
209
+ */
210
+ replacedSources: string[]
206
211
  }
207
212
 
208
213
  /**
@@ -240,6 +245,12 @@ export interface OverlayManifestOptions {
240
245
  */
241
246
  files: readonly OverlayFile[]
242
247
  note: string
248
+ /**
249
+ * Base sources whose files the overlay drops, so the added files take their place.
250
+ *
251
+ * Each source in this list must appear in the base, and its rows leave the split counts.
252
+ */
253
+ replaceSources?: readonly string[]
243
254
  }
244
255
 
245
256
  /**
@@ -269,7 +280,9 @@ export function localManifestFilePath(path: string): string {
269
280
  }
270
281
 
271
282
  /**
272
- * Writes a corpus manifest that keeps every file of `args.base` and appends `args.files`.
283
+ * Writes a corpus manifest that keeps the files of `args.base` and appends `args.files`.
284
+ *
285
+ * A base file whose source `args.replaceSources` lists is dropped.
273
286
  *
274
287
  * A `val` or `test` file must use the `.<split>.parquet` suffix written by `splitOverlaySlice`.
275
288
  * The suffix shows that the holdout policy chose its rows.
@@ -291,8 +304,16 @@ export async function assembleOverlayManifest(args: OverlayManifestOptions): Pro
291
304
  )
292
305
  }
293
306
 
307
+ const replaced = new Set(args.replaceSources)
308
+
309
+ for (const source of replaced) {
310
+ if (!baseFiles.some((s) => s.source === source)) {
311
+ throw new Error(`${args.base} holds no file with source '${source}', so there is nothing for it to replace.`)
312
+ }
313
+ }
314
+
294
315
  for (const file of args.files) {
295
- if (baseFiles.some((s) => s.source === file.source)) {
316
+ if (!replaced.has(file.source) && baseFiles.some((s) => s.source === file.source)) {
296
317
  console.log(`WARN: base already contains source '${file.source}' — is this the right base?`)
297
318
  }
298
319
  }
@@ -318,7 +339,24 @@ export async function assembleOverlayManifest(args: OverlayManifestOptions): Pro
318
339
  )
319
340
  }
320
341
 
321
- const kept = baseFiles.map((s) => ({ ...s, path: rerootBaseFilePath(s.path, args.base) }))
342
+ const dropped = baseFiles.filter((s) => s.source !== undefined && replaced.has(s.source))
343
+ const droppedRows: Record<SplitName, number> = { train: 0, val: 0, test: 0 }
344
+
345
+ for (const file of dropped) {
346
+ const split = file["split"] as SplitName
347
+ const rows = file["rows"]
348
+
349
+ if (typeof rows !== "number" || !(split in droppedRows)) {
350
+ throw new Error(`${file.path} records no row count or split, so dropping it would leave the counts unknown.`)
351
+ }
352
+
353
+ droppedRows[split] += rows
354
+ }
355
+
356
+ const kept = baseFiles
357
+ .filter((s) => !dropped.includes(s))
358
+ .map((s) => ({ ...s, path: rerootBaseFilePath(s.path, args.base) }))
359
+
322
360
  const added: ParquetFileDescriptor[] = []
323
361
 
324
362
  const newDir = PathBuilder.from(args.newDir)
@@ -345,20 +383,31 @@ export async function assembleOverlayManifest(args: OverlayManifestOptions): Pro
345
383
 
346
384
  const sources = args.files.map((file) => file.source).join(", ")
347
385
 
386
+ const defaultNote = replaced.size
387
+ ? `${base.corpus_version} files without ${[...replaced].join(", ")} + ${sources}.`
388
+ : `${base.corpus_version} files (all kept verbatim) + ${sources}. Pure overlay add.`
389
+
348
390
  const manifest = {
349
391
  corpus_version: args.version,
350
392
  overlay_base: base.corpus_version ?? null,
351
- note: args.note || `${base.corpus_version} files (all kept verbatim) + ${sources}. Pure overlay add.`,
393
+ note: args.note || defaultNote,
352
394
  schema: base.schema,
353
395
  rows_per_slice: baseRowsPerFile(base),
354
396
  row_group_size: base.row_group_size,
355
397
  slices: [...kept, ...added],
356
398
  counts: {
357
- train: base.counts.train + addedRows.train,
358
- val: base.counts.val + addedRows.val,
359
- test: base.counts.test + addedRows.test,
399
+ train: base.counts.train - droppedRows.train + addedRows.train,
400
+ val: base.counts.val - droppedRows.val + addedRows.val,
401
+ test: base.counts.test - droppedRows.test + addedRows.test,
360
402
  },
361
- total_rows: base.total_rows + addedRows.train + addedRows.val + addedRows.test,
403
+ total_rows:
404
+ base.total_rows -
405
+ droppedRows.train -
406
+ droppedRows.val -
407
+ droppedRows.test +
408
+ addedRows.train +
409
+ addedRows.val +
410
+ addedRows.test,
362
411
  // An overlay corpus's manifest reads no row's `license` column, so it states no license set.
363
412
  // This value in the artifact prevents a reader from deriving an attribution table
364
413
  // from `slices` and treating the base build's set as the whole.
@@ -389,6 +438,7 @@ export async function assembleOverlayManifest(args: OverlayManifestOptions): Pro
389
438
  split: file.split ?? "train",
390
439
  route: splitRouteFor(file.parquet),
391
440
  })),
441
+ replacedSources: [...replaced],
392
442
  }
393
443
 
394
444
  const planPath = newDir(OVERLAY_PLAN_FILE)
@@ -397,7 +447,9 @@ export async function assembleOverlayManifest(args: OverlayManifestOptions): Pro
397
447
 
398
448
  console.log(`wrote ${planPath}`)
399
449
  console.log(`wrote ${out}`)
400
- console.log(` files: ${manifest.slices.length} (${kept.length} base kept, +${added.length} added)`)
450
+ console.log(
451
+ ` files: ${manifest.slices.length} (${kept.length} base kept, ${dropped.length} base dropped, +${added.length} added)`
452
+ )
401
453
  console.log(` counts: ${stringifyJSON(manifest.counts)} total: ${manifest.total_rows}`)
402
454
 
403
455
  // The route prints beside the split, because a hand-placed split is otherwise visible only in a filename.
@@ -15,15 +15,7 @@ import type { ParquetRow } from "#parquet/schema"
15
15
  import { openParquetRowStream } from "#parquet/streams"
16
16
  import { writeParquetFile } from "#parquet/writers"
17
17
  import type { CanonicalRow } from "#types"
18
- import { type CountryHoldout, defaultHoldouts, type SplitName, splitForRow } from "#utils/split"
19
-
20
- /**
21
- * The component tags the holdout policy reads.
22
- *
23
- * `splitForRow` consults `region`, `postcode` and `locality` and no other field,
24
- * so reconstructing the rest would cost a string slice per span for no decision.
25
- */
26
- const HOLDOUT_TAGS = new Set<string>(["region", "postcode", "locality"])
18
+ import { type CountryHoldout, defaultHoldouts, HOLDOUT_COMPONENT_TAGS, type SplitName, splitForRow } from "#utils/split"
27
19
 
28
20
  /**
29
21
  * The fields {@linkcode holdoutComponents} reads, with the span triple optional
@@ -63,7 +55,7 @@ export function holdoutComponents(row: SpannedRow, index: number, input: string)
63
55
  const components: Record<string, string> = {}
64
56
 
65
57
  for (const [span, tag] of tags.entries()) {
66
- if (!HOLDOUT_TAGS.has(tag) || components[tag]) continue
58
+ if (!HOLDOUT_COMPONENT_TAGS.has(tag) || components[tag]) continue
67
59
 
68
60
  components[tag] = raw.slice(starts[span]!, ends[span]!)
69
61
  }