@mailwoman/corpus 10.2.2 → 10.4.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 (291) hide show
  1. package/README.md +17 -17
  2. package/data/PROVENANCE.md +273 -273
  3. package/data/address-source-register.json +517 -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 +11 -0
  14. package/lib/adapters/cordis/adapter.ts +8 -8
  15. package/lib/adapters/gleif/adapter.ts +7 -8
  16. package/lib/adapters/po-box/adapter.ts +1 -1
  17. package/lib/adapters/source-id.ts +1 -1
  18. package/lib/adapters.ts +2 -2
  19. package/lib/au/adapters/gnaf/adapter.ts +1 -1
  20. package/lib/be/adapters/brussels/adapter.ts +2 -2
  21. package/lib/be/adapters/vlaanderen/adapter.ts +1 -1
  22. package/lib/build.ts +1 -1
  23. package/lib/cz/adapters/cuzk/adapter.ts +1 -1
  24. package/lib/cz/tools/fetch/cuzk.ts +4 -4
  25. package/lib/de/recipes/locale.ts +1 -1
  26. package/lib/dk/adapters/inspire/adapter.ts +1 -1
  27. package/lib/ee/tools/fetch/ads.ts +2 -2
  28. package/lib/es/adapters/bizkaia/adapter.ts +1 -1
  29. package/lib/es/adapters/navarra/adapter.ts +2 -2
  30. package/lib/es/cadastre.ts +2 -2
  31. package/lib/es/tools/fetch/bizkaia.ts +7 -7
  32. package/lib/es/tools/fetch/catastro.ts +6 -6
  33. package/lib/es/tools/fetch/navarra.ts +4 -4
  34. package/lib/fingerprints.ts +3 -3
  35. package/lib/fr/adapters/finess/adapter.ts +6 -5
  36. package/lib/fr/adapters/finess/voie-type.ts +1 -1
  37. package/lib/fr/tools/fetch/annuaire-education.ts +3 -3
  38. package/lib/gb/constituent-country.ts +52 -0
  39. package/lib/inspire/address.ts +3 -3
  40. package/lib/international/recipes/locale.ts +41 -7
  41. package/lib/it/adapters/anac/adapter.ts +4 -3
  42. package/lib/jp/adapters/wof-admin/adapter.ts +2 -2
  43. package/lib/kr/adapters/juso/label-rows.ts +1 -1
  44. package/lib/kr/adapters/localdata/key-index.ts +2 -1
  45. package/lib/lu/adapters/bd-adresses/adapter.ts +1 -1
  46. package/lib/nl/adapters/kadaster/adapter.ts +2 -2
  47. package/lib/nl/tools/fetch/kadaster.ts +1 -1
  48. package/lib/parquet/shuffle.ts +2 -2
  49. package/lib/parquet/span-sql.ts +8 -6
  50. package/lib/parquet/streams.ts +1 -1
  51. package/lib/parquet/writers.ts +2 -2
  52. package/lib/pl/tools/fetch/emuia.ts +2 -2
  53. package/lib/recipes/scaffold.ts +2 -2
  54. package/lib/recipes/sources.ts +3 -3
  55. package/lib/recipes/sub/venue/render.ts +1 -1
  56. package/lib/recipes/sub/venue/sources.ts +4 -3
  57. package/lib/recipes/sub/venue.ts +4 -3
  58. package/lib/recipes/venue.ts +11 -11
  59. package/lib/ro/adapters/retea-scolara/adapter.ts +2 -2
  60. package/lib/runner.ts +3 -3
  61. package/lib/sk/tools/fetch/inspire.ts +3 -3
  62. package/lib/source-register/effective-manifest.ts +2 -2
  63. package/lib/source-register/types.ts +2 -2
  64. package/lib/subvenue/harvest.ts +6 -5
  65. package/lib/subvenue/promotions.ts +3 -3
  66. package/lib/subvenue/table.ts +6 -6
  67. package/lib/surfaces/locale.ts +1 -1
  68. package/lib/synthesizers/anchor-absorption.ts +1 -1
  69. package/lib/synthesizers/boundary-stress.ts +3 -2
  70. package/lib/synthesizers/no-street.ts +1 -1
  71. package/lib/synthesizers/po-box.ts +1 -1
  72. package/lib/synthesizers/tail.ts +2 -2
  73. package/lib/test-kit/corpus-recipe.ts +1 -1
  74. package/lib/tools/align-canonical.ts +3 -2
  75. package/lib/tools/audit.ts +1 -1
  76. package/lib/tools/corpus-stats.ts +1 -1
  77. package/lib/tools/fetch/download/network.ts +1 -1
  78. package/lib/tools/fetch/geonames/dump.ts +1 -1
  79. package/lib/tools/fetch/gleif.ts +1 -1
  80. package/lib/tools/fetch/header.ts +1 -1
  81. package/lib/tools/fetch/inspire-addresses.ts +1 -1
  82. package/lib/tools/fetch/vlaanderen-ad.ts +1 -1
  83. package/lib/tools/fetch/wfs-harvest.ts +6 -6
  84. package/lib/tools/fetch/zip-archive.ts +2 -2
  85. package/lib/tools/golden/expand.ts +1 -1
  86. package/lib/tools/golden/promote.ts +3 -3
  87. package/lib/tools/golden/relabel-street/directory.ts +2 -1
  88. package/lib/tools/lint/recipe-output/vocab.ts +2 -2
  89. package/lib/tools/overlay/manifest.ts +62 -10
  90. package/lib/tools/overlay/split-slice.ts +2 -10
  91. package/lib/tools/postcode-triples.ts +2 -2
  92. package/lib/tools/sub/venue/head-nouns.ts +4 -3
  93. package/lib/types.ts +3 -3
  94. package/lib/us/adapters/state/tx-notaries/adapter.ts +1 -1
  95. package/lib/us/adapters/usgov/irs-bmf/adapter.ts +2 -2
  96. package/lib/us/adapters/usgov/nppes/adapter.ts +1 -1
  97. package/lib/us/synthesizers/intersection.ts +4 -4
  98. package/lib/us/synthesizers/street.ts +3 -3
  99. package/lib/us/tools/fetch/nad.ts +2 -2
  100. package/lib/utils/split.ts +59 -6
  101. package/lib/utils/wof-json.ts +1 -1
  102. package/out/adapters/cordis/adapter.d.ts +7 -7
  103. package/out/adapters/cordis/adapter.js +8 -8
  104. package/out/adapters/cordis/adapter.js.map +1 -1
  105. package/out/adapters/gleif/adapter.d.ts +7 -8
  106. package/out/adapters/gleif/adapter.d.ts.map +1 -1
  107. package/out/adapters/gleif/adapter.js +7 -8
  108. package/out/adapters/gleif/adapter.js.map +1 -1
  109. package/out/adapters/po-box/adapter.d.ts +1 -1
  110. package/out/adapters/source-id.d.ts +1 -1
  111. package/out/adapters/source-id.js +1 -1
  112. package/out/adapters.d.ts +2 -2
  113. package/out/adapters.js +2 -2
  114. package/out/au/adapters/gnaf/adapter.js +1 -1
  115. package/out/be/adapters/brussels/adapter.d.ts +1 -1
  116. package/out/be/adapters/brussels/adapter.js +2 -2
  117. package/out/be/adapters/vlaanderen/adapter.d.ts +1 -1
  118. package/out/build.js +1 -1
  119. package/out/build.js.map +1 -1
  120. package/out/cz/adapters/cuzk/adapter.d.ts +1 -1
  121. package/out/cz/adapters/cuzk/adapter.js +1 -1
  122. package/out/cz/tools/fetch/cuzk.d.ts +2 -2
  123. package/out/cz/tools/fetch/cuzk.js +2 -2
  124. package/out/de/recipes/locale.d.ts +1 -1
  125. package/out/de/recipes/locale.js +1 -1
  126. package/out/dk/adapters/inspire/adapter.js +1 -1
  127. package/out/ee/tools/fetch/ads.d.ts +2 -2
  128. package/out/ee/tools/fetch/ads.js +1 -1
  129. package/out/es/adapters/bizkaia/adapter.js +1 -1
  130. package/out/es/adapters/navarra/adapter.d.ts +2 -2
  131. package/out/es/adapters/navarra/adapter.js +2 -2
  132. package/out/es/cadastre.d.ts +2 -2
  133. package/out/es/cadastre.js +2 -2
  134. package/out/es/tools/fetch/bizkaia.d.ts +5 -5
  135. package/out/es/tools/fetch/bizkaia.js +5 -5
  136. package/out/es/tools/fetch/catastro.d.ts +4 -4
  137. package/out/es/tools/fetch/catastro.js +2 -2
  138. package/out/es/tools/fetch/navarra.d.ts +2 -2
  139. package/out/es/tools/fetch/navarra.js +2 -2
  140. package/out/fingerprints.d.ts +3 -3
  141. package/out/fingerprints.js +3 -3
  142. package/out/fr/adapters/finess/adapter.d.ts +6 -5
  143. package/out/fr/adapters/finess/adapter.d.ts.map +1 -1
  144. package/out/fr/adapters/finess/adapter.js +6 -5
  145. package/out/fr/adapters/finess/adapter.js.map +1 -1
  146. package/out/fr/adapters/finess/voie-type.d.ts +1 -1
  147. package/out/fr/adapters/finess/voie-type.js +1 -1
  148. package/out/fr/tools/fetch/annuaire-education.d.ts +3 -3
  149. package/out/fr/tools/fetch/annuaire-education.js +3 -3
  150. package/out/gb/constituent-country.d.ts +36 -0
  151. package/out/gb/constituent-country.d.ts.map +1 -0
  152. package/out/gb/constituent-country.js +30 -0
  153. package/out/gb/constituent-country.js.map +1 -0
  154. package/out/inspire/address.d.ts +3 -3
  155. package/out/inspire/address.js +3 -3
  156. package/out/international/recipes/locale.d.ts +6 -0
  157. package/out/international/recipes/locale.d.ts.map +1 -1
  158. package/out/international/recipes/locale.js +29 -7
  159. package/out/international/recipes/locale.js.map +1 -1
  160. package/out/it/adapters/anac/adapter.d.ts +1 -1
  161. package/out/it/adapters/anac/adapter.d.ts.map +1 -1
  162. package/out/it/adapters/anac/adapter.js +4 -3
  163. package/out/it/adapters/anac/adapter.js.map +1 -1
  164. package/out/jp/adapters/wof-admin/adapter.d.ts +2 -2
  165. package/out/jp/adapters/wof-admin/adapter.js +2 -2
  166. package/out/kr/adapters/juso/label-rows.d.ts +1 -1
  167. package/out/kr/adapters/localdata/key-index.d.ts +2 -1
  168. package/out/kr/adapters/localdata/key-index.d.ts.map +1 -1
  169. package/out/kr/adapters/localdata/key-index.js +2 -1
  170. package/out/kr/adapters/localdata/key-index.js.map +1 -1
  171. package/out/lu/adapters/bd-adresses/adapter.d.ts +1 -1
  172. package/out/lu/adapters/bd-adresses/adapter.js +1 -1
  173. package/out/nl/adapters/kadaster/adapter.js +2 -2
  174. package/out/nl/tools/fetch/kadaster.d.ts +1 -1
  175. package/out/nl/tools/fetch/kadaster.js +1 -1
  176. package/out/parquet/shuffle.d.ts +2 -2
  177. package/out/parquet/shuffle.js +2 -2
  178. package/out/parquet/span-sql.d.ts +8 -6
  179. package/out/parquet/span-sql.d.ts.map +1 -1
  180. package/out/parquet/span-sql.js +8 -6
  181. package/out/parquet/span-sql.js.map +1 -1
  182. package/out/parquet/streams.d.ts +1 -1
  183. package/out/parquet/writers.d.ts +2 -2
  184. package/out/parquet/writers.js +1 -1
  185. package/out/pl/tools/fetch/emuia.d.ts +2 -2
  186. package/out/recipes/scaffold.d.ts +2 -2
  187. package/out/recipes/sources.d.ts +2 -2
  188. package/out/recipes/sources.js +3 -3
  189. package/out/recipes/sources.js.map +1 -1
  190. package/out/recipes/sub/venue/render.d.ts +1 -1
  191. package/out/recipes/sub/venue/render.js +1 -1
  192. package/out/recipes/sub/venue/sources.d.ts +4 -3
  193. package/out/recipes/sub/venue/sources.d.ts.map +1 -1
  194. package/out/recipes/sub/venue/sources.js +4 -3
  195. package/out/recipes/sub/venue/sources.js.map +1 -1
  196. package/out/recipes/sub/venue.d.ts +3 -2
  197. package/out/recipes/sub/venue.d.ts.map +1 -1
  198. package/out/recipes/sub/venue.js +2 -2
  199. package/out/recipes/sub/venue.js.map +1 -1
  200. package/out/recipes/venue.d.ts +7 -6
  201. package/out/recipes/venue.d.ts.map +1 -1
  202. package/out/recipes/venue.js +11 -10
  203. package/out/recipes/venue.js.map +1 -1
  204. package/out/ro/adapters/retea-scolara/adapter.d.ts +2 -2
  205. package/out/ro/adapters/retea-scolara/adapter.js +2 -2
  206. package/out/runner.d.ts +3 -3
  207. package/out/runner.js +2 -2
  208. package/out/sk/tools/fetch/inspire.d.ts +3 -3
  209. package/out/sk/tools/fetch/inspire.js +1 -1
  210. package/out/source-register/effective-manifest.d.ts +2 -2
  211. package/out/source-register/types.d.ts +2 -2
  212. package/out/subvenue/harvest.d.ts +4 -3
  213. package/out/subvenue/harvest.d.ts.map +1 -1
  214. package/out/subvenue/harvest.js +6 -5
  215. package/out/subvenue/harvest.js.map +1 -1
  216. package/out/subvenue/promotions.d.ts +2 -2
  217. package/out/subvenue/promotions.js +3 -3
  218. package/out/subvenue/promotions.js.map +1 -1
  219. package/out/subvenue/table.d.ts +6 -6
  220. package/out/subvenue/table.js +1 -1
  221. package/out/surfaces/locale.d.ts +1 -1
  222. package/out/surfaces/locale.js +1 -1
  223. package/out/synthesizers/boundary-stress.d.ts.map +1 -1
  224. package/out/synthesizers/boundary-stress.js +2 -1
  225. package/out/synthesizers/boundary-stress.js.map +1 -1
  226. package/out/synthesizers/tail.d.ts +2 -2
  227. package/out/synthesizers/tail.js +2 -2
  228. package/out/test-kit/corpus-recipe.d.ts +1 -1
  229. package/out/test-kit/corpus-recipe.js +1 -1
  230. package/out/tools/align-canonical.d.ts.map +1 -1
  231. package/out/tools/align-canonical.js +3 -2
  232. package/out/tools/align-canonical.js.map +1 -1
  233. package/out/tools/audit.js +1 -1
  234. package/out/tools/corpus-stats.d.ts +1 -1
  235. package/out/tools/fetch/download/network.d.ts +1 -1
  236. package/out/tools/fetch/download/network.js +1 -1
  237. package/out/tools/fetch/geonames/dump.d.ts +1 -1
  238. package/out/tools/fetch/geonames/dump.js +1 -1
  239. package/out/tools/fetch/gleif.d.ts +1 -1
  240. package/out/tools/fetch/gleif.js +1 -1
  241. package/out/tools/fetch/header.d.ts +1 -1
  242. package/out/tools/fetch/header.js +1 -1
  243. package/out/tools/fetch/inspire-addresses.d.ts +1 -1
  244. package/out/tools/fetch/vlaanderen-ad.d.ts +1 -1
  245. package/out/tools/fetch/wfs-harvest.d.ts +3 -3
  246. package/out/tools/fetch/wfs-harvest.js +4 -4
  247. package/out/tools/fetch/wfs-harvest.js.map +1 -1
  248. package/out/tools/fetch/zip-archive.d.ts +2 -2
  249. package/out/tools/fetch/zip-archive.js +2 -2
  250. package/out/tools/golden/expand.js +1 -1
  251. package/out/tools/golden/expand.js.map +1 -1
  252. package/out/tools/golden/promote.d.ts +1 -1
  253. package/out/tools/golden/promote.js +3 -3
  254. package/out/tools/golden/promote.js.map +1 -1
  255. package/out/tools/golden/relabel-street/directory.d.ts +2 -1
  256. package/out/tools/golden/relabel-street/directory.d.ts.map +1 -1
  257. package/out/tools/golden/relabel-street/directory.js.map +1 -1
  258. package/out/tools/lint/recipe-output/vocab.js +2 -2
  259. package/out/tools/overlay/manifest.d.ts +15 -2
  260. package/out/tools/overlay/manifest.d.ts.map +1 -1
  261. package/out/tools/overlay/manifest.js +41 -10
  262. package/out/tools/overlay/manifest.js.map +1 -1
  263. package/out/tools/overlay/split-slice.d.ts.map +1 -1
  264. package/out/tools/overlay/split-slice.js +2 -9
  265. package/out/tools/overlay/split-slice.js.map +1 -1
  266. package/out/tools/postcode-triples.d.ts +1 -1
  267. package/out/tools/postcode-triples.js +2 -2
  268. package/out/tools/postcode-triples.js.map +1 -1
  269. package/out/tools/sub/venue/head-nouns.d.ts +1 -1
  270. package/out/tools/sub/venue/head-nouns.d.ts.map +1 -1
  271. package/out/tools/sub/venue/head-nouns.js +4 -3
  272. package/out/tools/sub/venue/head-nouns.js.map +1 -1
  273. package/out/types.d.ts +3 -3
  274. package/out/types.js +1 -1
  275. package/out/us/adapters/state/tx-notaries/adapter.d.ts +1 -1
  276. package/out/us/adapters/state/tx-notaries/adapter.js +1 -1
  277. package/out/us/adapters/usgov/irs-bmf/adapter.d.ts +1 -1
  278. package/out/us/adapters/usgov/irs-bmf/adapter.js +2 -2
  279. package/out/us/synthesizers/intersection.js +4 -4
  280. package/out/us/synthesizers/intersection.js.map +1 -1
  281. package/out/us/synthesizers/street.js +3 -3
  282. package/out/us/synthesizers/street.js.map +1 -1
  283. package/out/us/tools/fetch/nad.d.ts +2 -2
  284. package/out/us/tools/fetch/nad.js +2 -2
  285. package/out/utils/split.d.ts +21 -0
  286. package/out/utils/split.d.ts.map +1 -1
  287. package/out/utils/split.js +49 -5
  288. package/out/utils/split.js.map +1 -1
  289. package/out/utils/wof-json.d.ts +1 -1
  290. package/out/utils/wof-json.js +1 -1
  291. package/package.json +14 -14
@@ -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
  *
@@ -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`)
@@ -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
  }
@@ -62,7 +62,7 @@ export const POSTCODE_CONVENTIONS: ReadonlyMap<
62
62
  { placement: PostcodePlacement; locale: string; localityColumn?: GeonamesLocalityColumn }
63
63
  > = new Map([
64
64
  // `Rue de l'Église, 3, 29217 Plougonvelin, Bretagne, France` and its siblings in
65
- // the structured slices attest this leading placement.
65
+ // the structured recipe outputs attest this leading placement.
66
66
  ["FR", { placement: "leading", locale: "fr-FR" }],
67
67
  ["DE", { placement: "leading", locale: "de-DE" }],
68
68
  ["ES", { placement: "leading", locale: "es-ES" }],
@@ -85,7 +85,7 @@ export const POSTCODE_CONVENTIONS: ReadonlyMap<
85
85
  ])
86
86
 
87
87
  /**
88
- * How many postcodes one locality may contribute.
88
+ * The number of postcodes one locality may contribute.
89
89
  *
90
90
  * A quota rather than a cutoff: it bounds repetition without deleting a locality.
91
91
  */
@@ -28,7 +28,8 @@ function asciiFold(text: string): string {
28
28
  }
29
29
 
30
30
  /**
31
- * How many leading characters two ascii-folded forms must share for one to count as the other's inflection.
31
+ * The number of leading characters two ascii-folded forms must share for one
32
+ * to count as the other's inflection.
32
33
  *
33
34
  * Five, or the id's own length when shorter.
34
35
  * A limit of six loses Spanish `satélite`.
@@ -44,7 +45,7 @@ const HEAD_NOUN_PREFIX_FLOOR = 5
44
45
  const NON_LATIN_HEAD_MIN_LENGTH = 2
45
46
 
46
47
  /**
47
- * How many head-noun candidates one non-Latin record+language group may contribute.
48
+ * The number of head-noun candidates one non-Latin record+language group may contribute.
48
49
  *
49
50
  * Six, capped because the substring lattice of a nine-character label is large
50
51
  * and no entry past the sixth attests more than two surfaces.
@@ -67,7 +68,7 @@ const SHARED_SUBSTRING_SCRIPT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Kat
67
68
 
68
69
  /**
69
70
  * Derive the head noun of every multi-part surface, so `terminal aeroportuaria`
70
- * contributes the form anyone actually writes on an envelope.
71
+ * contributes the form a sender writes on an envelope.
71
72
  *
72
73
  * Latin script uses a cognate test against the designator's own canonical id.
73
74
  * Han and Kana use a shared substring, because a token split finds no boundary there.
package/lib/types.ts CHANGED
@@ -15,7 +15,7 @@ import type { PathBuilderLike } from "path-ts"
15
15
  * The values are wire values.
16
16
  *
17
17
  * Roles have different grammars.
18
- * For example, mailing addresses often use post-office boxes, so mixing roles in
18
+ * For example, some mailing addresses use post-office boxes, so mixing roles in
19
19
  * a corpus shifts what the premise parser learns.
20
20
  */
21
21
  export const AddressRole = {
@@ -277,7 +277,7 @@ export interface CanonicalRow extends SourceProvenance {
277
277
  /**
278
278
  * A row after alignment, with tokens, BIO labels and character spans.
279
279
  *
280
- * The span arrays are the source of truth.
280
+ * The span arrays are authoritative when they disagree with any other field.
281
281
  * Consumers can derive token labels from them.
282
282
  */
283
283
  export interface LabeledRow extends CanonicalRow {
@@ -399,7 +399,7 @@ export function countDropped(opts: Pick<AdapterOptions, "dropped">, reason: stri
399
399
  /**
400
400
  * The interface that every data source implements.
401
401
  *
402
- * Adapters stream rows, because many sources hold tens of millions of rows.
402
+ * Adapters stream rows, because some sources hold tens of millions of rows.
403
403
  */
404
404
  export interface CorpusAdapter {
405
405
  /**
@@ -6,7 +6,7 @@
6
6
  * `state-tx-notaries`: Texas Notary Public Commissions CSV consumer.
7
7
  *
8
8
  * The Texas Secretary of State publishes a registry of commissioned notaries public. Each row
9
- * optionally includes a mailing address in free-form text (often multi-line with embedded
9
+ * optionally includes a mailing address in free-form text (in some rows multi-line with embedded
10
10
  * city/state/zip). Address fill rate is ~5-10%.
11
11
  *
12
12
  * The adapter parses the embedded `Address` field for city/state/zip using a trailing `"city, ST
@@ -10,7 +10,7 @@
10
10
  * `https://www.irs.gov/charities-non-profits/exempt-organizations-business-master-file-extract-eo-bmf`
11
11
  * (`eo1.csv`..`eo4.csv`, `eo_pr.csv`, `eo_xx.csv`). Each row includes an organization name plus
12
12
  * its mailing address. It complements `usgov-nppes` with a different venue population
13
- * (non-profits vs healthcare providers) and, notably, a high share of PO-box addresses — useful
13
+ * (non-profits vs healthcare providers) and a high share of PO-box addresses — useful
14
14
  * `po_box`-tag signal (a tag with historically low recall).
15
15
  *
16
16
  * Output: one row per record with a usable city + postcode. name → `venue`; the street line becomes
@@ -40,7 +40,7 @@ export const USGOV_IRS_BMF_ADAPTER_ID = "usgov-irs-bmf"
40
40
  export const USGOV_IRS_BMF_DEFAULT_LICENSE = "Public Domain"
41
41
 
42
42
  /**
43
- * PO box in its many written forms: "PO BOX 12", "P.O.
43
+ * PO box in each of its written forms: "PO BOX 12", "P.O.
44
44
  *
45
45
  * BOX 12", "P O BOX 12", "POB 12", "BOX 12".
46
46
  */
@@ -38,7 +38,7 @@ interface NPPESRow {
38
38
  "Provider Second Line Business Practice Location Address": string
39
39
  "Provider Business Practice Location Address City Name": string
40
40
  "Provider Business Practice Location Address State Name": string
41
- // This is the publisher's exact column name, so it must keep the words "Postal Code".
41
+ // This is the publisher's exact column name, so the string keeps the publisher's spelling.
42
42
  "Provider Business Practice Location Address Postal Code": string
43
43
  }
44
44
 
@@ -26,7 +26,7 @@ import { layoutAdminTail } from "#synthesizers/tail"
26
26
  import type { CanonicalRow } from "#types"
27
27
 
28
28
  /**
29
- * Street name cores (no suffix) — proper-noun streets that often appear bare.
29
+ * Street name cores (no suffix) — proper-noun streets that can appear bare.
30
30
  */
31
31
 
32
32
  /**
@@ -37,7 +37,7 @@ const MAX_DISTINCT_STREET_TRIES = 8
37
37
  /* oxlint-disable sister-software/no-unnamed-threshold -- the bare decimals below are weighted-sampler
38
38
  cutoffs rather than thresholds: `const r = random()` followed by a cascade of `r < 0.4` branches is the
39
39
  output distribution. The cascade shows each weight from top to bottom. A separate constant for each cutoff
40
- would hide the distribution behind identifiers. The genuine thresholds in these files are
40
+ would hide the distribution behind identifiers. The decision thresholds in these files are
41
41
  extracted as constants above. */
42
42
 
43
43
  const STREET_CORES = [
@@ -153,8 +153,8 @@ export function synthesizeIntersectionRow(
153
153
  ): SynthesizedIntersectionRow | null {
154
154
  const random = opts.random ?? Math.random
155
155
 
156
- // The junction connectors and the street vocabulary are US English,
157
- // which is why this synthesizer sits under `us/`.
156
+ // The junction connectors and the street vocabulary are US English.
157
+ // This synthesizer therefore sits under `us/`.
158
158
  // The admin tail renders through the country's layout.
159
159
  if (base.country !== "US") return null
160
160
 
@@ -33,7 +33,7 @@ import { decomposeStreet } from "#us/adapters/tiger/street-decompose"
33
33
  /* oxlint-disable sister-software/no-unnamed-threshold -- the bare decimals below are weighted-sampler
34
34
  cutoffs rather than thresholds: `const r = random()` followed by a cascade of `r < 0.4` branches is the
35
35
  output distribution. The cascade shows each weight from top to bottom. A separate constant for each cutoff
36
- would hide the distribution behind a wall of identifiers. Genuine thresholds in these files are
36
+ would hide the distribution behind a wall of identifiers. Decision thresholds in these files are
37
37
  extracted as constants above. */
38
38
 
39
39
  const STREET_NAMES = [
@@ -221,8 +221,8 @@ export function synthesizeStreetRow(
221
221
  const random = opts.random ?? Math.random
222
222
  const includeHN = opts.includeHouseNumberProb ?? 0.85
223
223
 
224
- // The street vocabulary (`STREET_NAMES`, `STREET_SUFFIXES`, the directionals) is
225
- // US English, which is why this synthesizer sits under `us/`.
224
+ // The street vocabulary (`STREET_NAMES`, `STREET_SUFFIXES`, the directionals) is US English.
225
+ // This synthesizer therefore sits under `us/`.
226
226
  // The admin tail renders through the country's layout.
227
227
  if (base.country !== "US") return null
228
228
 
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * - Bounded per-chunk page concurrency (4× speedup at safe pressure)
11
11
  * - 5× larger page size (5 000 vs. the old bash fetcher's 2 000) — fewer round-trips per chunk
12
- * - Honest `complete: true` flag: only set when every page in the chunk fetched cleanly
13
- * - Built-in fetch with gzip/brotli decompression (no curl + Python subprocess tax)
12
+ * - A `complete: true` flag that is set only when every page in the chunk fetched without error
13
+ * - Built-in fetch with gzip/brotli decompression (no curl or Python subprocess per page)
14
14
  * - Per-chunk manifest with sha256 + record count + error count
15
15
  *
16
16
  * ## Source layout
@@ -110,7 +110,7 @@ export function defaultHoldouts(): Record<string, CountryHoldout> {
110
110
  regions: ["Cornwall"],
111
111
  },
112
112
  // The eight countries below enter training through OpenStreetMap rows. Most hold no
113
- // region and many no postcode, so each holdout matches its place three ways: the region an
113
+ // region and some no postcode, so each holdout matches its place three ways: the region an
114
114
  // admin row holds, the postcode prefix, and the city as the OSM rows spell it. The spellings
115
115
  // are the ones the 2026-10-02 extracts write.
116
116
  CN: {
@@ -204,6 +204,48 @@ export function defaultHoldouts(): Record<string, CountryHoldout> {
204
204
  }
205
205
  }
206
206
 
207
+ /**
208
+ * The component tags {@link splitForRow} reads.
209
+ *
210
+ * A reader that rebuilds components from spans needs only these.
211
+ */
212
+ export const HOLDOUT_COMPONENT_TAGS: ReadonlySet<string> = new Set(["venue", "region", "postcode", "locality"])
213
+
214
+ /**
215
+ * One place in this number is held out in a country without a {@link defaultHoldouts} entry.
216
+ */
217
+ export const HASHED_PLACE_HOLDOUT_DIVISOR = 50
218
+
219
+ /**
220
+ * The key a row is held out by under the hashed holdout: its venue, else its locality,
221
+ * else the first two characters of its postcode, else its region.
222
+ *
223
+ * A venue name keys before its place because the venue recipes write one name both with a locality and bare.
224
+ * A locality key would hold out the first form and train on the second.
225
+ *
226
+ * Returns null for a row with none of the four.
227
+ * That row stays in train.
228
+ */
229
+ export function hashedHoldoutPlace(
230
+ components: Partial<Record<"venue" | "locality" | "postcode" | "region", string>>
231
+ ): string | null {
232
+ const venue = components.venue?.trim().toLowerCase()
233
+
234
+ if (venue) return `venue:${venue}`
235
+
236
+ const locality = components.locality?.trim().toLowerCase()
237
+
238
+ if (locality) return `locality:${locality}`
239
+
240
+ const postcode = components.postcode?.replaceAll(/\s+/g, "").toUpperCase()
241
+
242
+ if (postcode && postcode.length >= 2) return `postcode:${postcode.slice(0, 2)}`
243
+
244
+ const region = components.region?.trim().toLowerCase()
245
+
246
+ return region ? `region:${region}` : null
247
+ }
248
+
207
249
  /**
208
250
  * Returns the matchers for one country, reading a bare array as a region list.
209
251
  */
@@ -225,13 +267,24 @@ export function splitForRow(
225
267
  row: Pick<SplitInputRow, "source_id" | "country" | "components">,
226
268
  holdouts: Record<string, CountryHoldout> = defaultHoldouts()
227
269
  ): SplitName {
228
- const policy = holdoutPolicyFor(holdouts[row.country] ?? null)
270
+ const declared = holdouts[row.country]
229
271
  const { region, postcode, locality } = row.components
272
+ let isHeldOut: boolean
273
+
274
+ if (declared) {
275
+ const policy = holdoutPolicyFor(declared)
230
276
 
231
- const isHeldOut =
232
- (region !== undefined && (policy.regions?.includes(region) ?? false)) ||
233
- (postcode !== undefined && (policy.postcodePrefixes?.some((prefix) => postcode.startsWith(prefix)) ?? false)) ||
234
- (locality !== undefined && (policy.localities?.includes(locality) ?? false))
277
+ isHeldOut =
278
+ (region !== undefined && (policy.regions?.includes(region) ?? false)) ||
279
+ (postcode !== undefined && (policy.postcodePrefixes?.some((prefix) => postcode.startsWith(prefix)) ?? false)) ||
280
+ (locality !== undefined && (policy.localities?.includes(locality) ?? false))
281
+ } else {
282
+ // A country without a declared holdout holds out whole places chosen by hash,
283
+ // so every country in the corpus reaches val and test.
284
+ const place = hashedHoldoutPlace(row.components)
285
+
286
+ isHeldOut = place !== null && hashBucket(`${row.country}|${place}`, HASHED_PLACE_HOLDOUT_DIVISOR) === 0
287
+ }
235
288
 
236
289
  if (!isHeldOut) return "train"
237
290
 
@@ -75,7 +75,7 @@ export function isCurrentFeature(props: Record<string, unknown>): boolean {
75
75
  /**
76
76
  * Pull `name:*` localized variants off a WOF feature's properties, lifting the first
77
77
  * non-empty string from the array WOF stores them in (`["Saint Petersburg"]`);
78
- * Multiple-value variants are rare and usually historical aliases.
78
+ * Multiple-value variants are rare, and most of them are historical aliases.
79
79
  *
80
80
  * This function keeps them together.
81
81
  * An adapter that needs separate values can iterate the underlying array.
@@ -71,7 +71,7 @@
71
71
  *
72
72
  * ## The street line
73
73
  *
74
- * `street` holds the whole street line as the participant typed it, usually but not always in the
74
+ * `street` holds the whole street line as the participant typed it, in most records but not all in the
75
75
  * country's own order: `THE WELLCOME TRUST LIMITED` writes `EUSTON ROAD 215 GIBBS BUILDING`, a
76
76
  * number-last line in a number-first country. The house number is therefore split off only on the
77
77
  * side the country's order puts it, and {@linkcode streetOrderForCountry} reads that side from codex's
@@ -110,11 +110,11 @@
110
110
  *
111
111
  * ## License
112
112
  *
113
- * The CORDIS legal notice at `https://cordis.europa.eu/about/legal`, read 2026-10-03, states:
114
- * "Unless otherwise noted (e.g. in individual copyright notices), the reuse of the editorial content on
115
- * this website owned by the EU is authorized under the Creative Commons Attribution 4.0 International
116
- * (CC BY 4.0) licence." The same notice records that "The Commission's reuse policy is implemented by
117
- * Commission Decision 2011/833/EU of 12 December 2011 on the reuse of Commission documents", and the
113
+ * The CORDIS legal notice at `https://cordis.europa.eu/about/legal`, read 2026-10-03, authorizes reuse
114
+ * of the EU-owned editorial content on the site under Creative Commons Attribution 4.0 International
115
+ * (CC BY 4.0), except where an individual copyright notice states otherwise. The same notice states
116
+ * that Commission Decision 2011/833/EU of 12 December 2011, on the reuse of Commission documents,
117
+ * implements the Commission's reuse policy, and the
118
118
  * data.europa.eu records for `cordisfp7projects`, `cordish2020projects` and
119
119
  * `cordis-eu-research-projects-under-horizon-europe-2021-2027` label each CSV distribution with that
120
120
  * decision (`COM_REUSE`). CC BY requires credit and a statement that changes were made, so every row
@@ -307,7 +307,7 @@ export interface CORDISRecordReading {
307
307
  /**
308
308
  * Every distinct published record under `inputPath`, read once each.
309
309
  *
310
- * A record is distinct on the five fields that reach a row, so an organization that took part in many
310
+ * A record is distinct on the five fields that reach a row, so an organization that took part in several
311
311
  * projects with one address is read once, and one whose address changed is read once per version.
312
312
  * `onDuplicate` is called once for every participation row a record already read covers.
313
313
  *