@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.
- package/README.md +17 -17
- package/data/PROVENANCE.md +273 -273
- package/data/address-source-register.json +517 -23
- package/data/license-decisions.json +120 -0
- package/data/licenses/PROVENANCE.md +5 -5
- package/data/licenses/cdla-permissive-2.0.md +6 -6
- package/data/licenses/geonames-publication.md +5 -5
- package/data/licenses/licence-ouverte-2.0.md +3 -3
- package/data/licenses/national-address-database.md +12 -12
- package/data/licenses/nlod-2.0.md +3 -3
- package/data/licenses/sirene-publication.md +13 -6
- package/data/licenses/whosonfirst-licenses.md +4 -4
- package/data/source-resolutions.json +11 -0
- package/lib/adapters/cordis/adapter.ts +8 -8
- package/lib/adapters/gleif/adapter.ts +7 -8
- package/lib/adapters/po-box/adapter.ts +1 -1
- package/lib/adapters/source-id.ts +1 -1
- package/lib/adapters.ts +2 -2
- package/lib/au/adapters/gnaf/adapter.ts +1 -1
- package/lib/be/adapters/brussels/adapter.ts +2 -2
- package/lib/be/adapters/vlaanderen/adapter.ts +1 -1
- package/lib/build.ts +1 -1
- package/lib/cz/adapters/cuzk/adapter.ts +1 -1
- package/lib/cz/tools/fetch/cuzk.ts +4 -4
- package/lib/de/recipes/locale.ts +1 -1
- package/lib/dk/adapters/inspire/adapter.ts +1 -1
- package/lib/ee/tools/fetch/ads.ts +2 -2
- package/lib/es/adapters/bizkaia/adapter.ts +1 -1
- package/lib/es/adapters/navarra/adapter.ts +2 -2
- package/lib/es/cadastre.ts +2 -2
- package/lib/es/tools/fetch/bizkaia.ts +7 -7
- package/lib/es/tools/fetch/catastro.ts +6 -6
- package/lib/es/tools/fetch/navarra.ts +4 -4
- package/lib/fingerprints.ts +3 -3
- package/lib/fr/adapters/finess/adapter.ts +6 -5
- package/lib/fr/adapters/finess/voie-type.ts +1 -1
- package/lib/fr/tools/fetch/annuaire-education.ts +3 -3
- package/lib/gb/constituent-country.ts +52 -0
- package/lib/inspire/address.ts +3 -3
- package/lib/international/recipes/locale.ts +41 -7
- package/lib/it/adapters/anac/adapter.ts +4 -3
- package/lib/jp/adapters/wof-admin/adapter.ts +2 -2
- package/lib/kr/adapters/juso/label-rows.ts +1 -1
- package/lib/kr/adapters/localdata/key-index.ts +2 -1
- package/lib/lu/adapters/bd-adresses/adapter.ts +1 -1
- package/lib/nl/adapters/kadaster/adapter.ts +2 -2
- package/lib/nl/tools/fetch/kadaster.ts +1 -1
- package/lib/parquet/shuffle.ts +2 -2
- package/lib/parquet/span-sql.ts +8 -6
- package/lib/parquet/streams.ts +1 -1
- package/lib/parquet/writers.ts +2 -2
- package/lib/pl/tools/fetch/emuia.ts +2 -2
- package/lib/recipes/scaffold.ts +2 -2
- package/lib/recipes/sources.ts +3 -3
- package/lib/recipes/sub/venue/render.ts +1 -1
- package/lib/recipes/sub/venue/sources.ts +4 -3
- package/lib/recipes/sub/venue.ts +4 -3
- package/lib/recipes/venue.ts +11 -11
- package/lib/ro/adapters/retea-scolara/adapter.ts +2 -2
- package/lib/runner.ts +3 -3
- package/lib/sk/tools/fetch/inspire.ts +3 -3
- package/lib/source-register/effective-manifest.ts +2 -2
- package/lib/source-register/types.ts +2 -2
- package/lib/subvenue/harvest.ts +6 -5
- package/lib/subvenue/promotions.ts +3 -3
- package/lib/subvenue/table.ts +6 -6
- package/lib/surfaces/locale.ts +1 -1
- package/lib/synthesizers/anchor-absorption.ts +1 -1
- package/lib/synthesizers/boundary-stress.ts +3 -2
- package/lib/synthesizers/no-street.ts +1 -1
- package/lib/synthesizers/po-box.ts +1 -1
- package/lib/synthesizers/tail.ts +2 -2
- package/lib/test-kit/corpus-recipe.ts +1 -1
- package/lib/tools/align-canonical.ts +3 -2
- package/lib/tools/audit.ts +1 -1
- package/lib/tools/corpus-stats.ts +1 -1
- package/lib/tools/fetch/download/network.ts +1 -1
- package/lib/tools/fetch/geonames/dump.ts +1 -1
- package/lib/tools/fetch/gleif.ts +1 -1
- package/lib/tools/fetch/header.ts +1 -1
- package/lib/tools/fetch/inspire-addresses.ts +1 -1
- package/lib/tools/fetch/vlaanderen-ad.ts +1 -1
- package/lib/tools/fetch/wfs-harvest.ts +6 -6
- package/lib/tools/fetch/zip-archive.ts +2 -2
- package/lib/tools/golden/expand.ts +1 -1
- package/lib/tools/golden/promote.ts +3 -3
- package/lib/tools/golden/relabel-street/directory.ts +2 -1
- package/lib/tools/lint/recipe-output/vocab.ts +2 -2
- package/lib/tools/overlay/manifest.ts +62 -10
- package/lib/tools/overlay/split-slice.ts +2 -10
- package/lib/tools/postcode-triples.ts +2 -2
- package/lib/tools/sub/venue/head-nouns.ts +4 -3
- package/lib/types.ts +3 -3
- package/lib/us/adapters/state/tx-notaries/adapter.ts +1 -1
- package/lib/us/adapters/usgov/irs-bmf/adapter.ts +2 -2
- package/lib/us/adapters/usgov/nppes/adapter.ts +1 -1
- package/lib/us/synthesizers/intersection.ts +4 -4
- package/lib/us/synthesizers/street.ts +3 -3
- package/lib/us/tools/fetch/nad.ts +2 -2
- package/lib/utils/split.ts +59 -6
- package/lib/utils/wof-json.ts +1 -1
- package/out/adapters/cordis/adapter.d.ts +7 -7
- package/out/adapters/cordis/adapter.js +8 -8
- package/out/adapters/cordis/adapter.js.map +1 -1
- package/out/adapters/gleif/adapter.d.ts +7 -8
- package/out/adapters/gleif/adapter.d.ts.map +1 -1
- package/out/adapters/gleif/adapter.js +7 -8
- package/out/adapters/gleif/adapter.js.map +1 -1
- package/out/adapters/po-box/adapter.d.ts +1 -1
- package/out/adapters/source-id.d.ts +1 -1
- package/out/adapters/source-id.js +1 -1
- package/out/adapters.d.ts +2 -2
- package/out/adapters.js +2 -2
- package/out/au/adapters/gnaf/adapter.js +1 -1
- package/out/be/adapters/brussels/adapter.d.ts +1 -1
- package/out/be/adapters/brussels/adapter.js +2 -2
- package/out/be/adapters/vlaanderen/adapter.d.ts +1 -1
- package/out/build.js +1 -1
- package/out/build.js.map +1 -1
- package/out/cz/adapters/cuzk/adapter.d.ts +1 -1
- package/out/cz/adapters/cuzk/adapter.js +1 -1
- package/out/cz/tools/fetch/cuzk.d.ts +2 -2
- package/out/cz/tools/fetch/cuzk.js +2 -2
- package/out/de/recipes/locale.d.ts +1 -1
- package/out/de/recipes/locale.js +1 -1
- package/out/dk/adapters/inspire/adapter.js +1 -1
- package/out/ee/tools/fetch/ads.d.ts +2 -2
- package/out/ee/tools/fetch/ads.js +1 -1
- package/out/es/adapters/bizkaia/adapter.js +1 -1
- package/out/es/adapters/navarra/adapter.d.ts +2 -2
- package/out/es/adapters/navarra/adapter.js +2 -2
- package/out/es/cadastre.d.ts +2 -2
- package/out/es/cadastre.js +2 -2
- package/out/es/tools/fetch/bizkaia.d.ts +5 -5
- package/out/es/tools/fetch/bizkaia.js +5 -5
- package/out/es/tools/fetch/catastro.d.ts +4 -4
- package/out/es/tools/fetch/catastro.js +2 -2
- package/out/es/tools/fetch/navarra.d.ts +2 -2
- package/out/es/tools/fetch/navarra.js +2 -2
- package/out/fingerprints.d.ts +3 -3
- package/out/fingerprints.js +3 -3
- package/out/fr/adapters/finess/adapter.d.ts +6 -5
- package/out/fr/adapters/finess/adapter.d.ts.map +1 -1
- package/out/fr/adapters/finess/adapter.js +6 -5
- package/out/fr/adapters/finess/adapter.js.map +1 -1
- package/out/fr/adapters/finess/voie-type.d.ts +1 -1
- package/out/fr/adapters/finess/voie-type.js +1 -1
- package/out/fr/tools/fetch/annuaire-education.d.ts +3 -3
- package/out/fr/tools/fetch/annuaire-education.js +3 -3
- package/out/gb/constituent-country.d.ts +36 -0
- package/out/gb/constituent-country.d.ts.map +1 -0
- package/out/gb/constituent-country.js +30 -0
- package/out/gb/constituent-country.js.map +1 -0
- package/out/inspire/address.d.ts +3 -3
- package/out/inspire/address.js +3 -3
- package/out/international/recipes/locale.d.ts +6 -0
- package/out/international/recipes/locale.d.ts.map +1 -1
- package/out/international/recipes/locale.js +29 -7
- package/out/international/recipes/locale.js.map +1 -1
- package/out/it/adapters/anac/adapter.d.ts +1 -1
- package/out/it/adapters/anac/adapter.d.ts.map +1 -1
- package/out/it/adapters/anac/adapter.js +4 -3
- package/out/it/adapters/anac/adapter.js.map +1 -1
- package/out/jp/adapters/wof-admin/adapter.d.ts +2 -2
- package/out/jp/adapters/wof-admin/adapter.js +2 -2
- package/out/kr/adapters/juso/label-rows.d.ts +1 -1
- package/out/kr/adapters/localdata/key-index.d.ts +2 -1
- package/out/kr/adapters/localdata/key-index.d.ts.map +1 -1
- package/out/kr/adapters/localdata/key-index.js +2 -1
- package/out/kr/adapters/localdata/key-index.js.map +1 -1
- package/out/lu/adapters/bd-adresses/adapter.d.ts +1 -1
- package/out/lu/adapters/bd-adresses/adapter.js +1 -1
- package/out/nl/adapters/kadaster/adapter.js +2 -2
- package/out/nl/tools/fetch/kadaster.d.ts +1 -1
- package/out/nl/tools/fetch/kadaster.js +1 -1
- package/out/parquet/shuffle.d.ts +2 -2
- package/out/parquet/shuffle.js +2 -2
- package/out/parquet/span-sql.d.ts +8 -6
- package/out/parquet/span-sql.d.ts.map +1 -1
- package/out/parquet/span-sql.js +8 -6
- package/out/parquet/span-sql.js.map +1 -1
- package/out/parquet/streams.d.ts +1 -1
- package/out/parquet/writers.d.ts +2 -2
- package/out/parquet/writers.js +1 -1
- package/out/pl/tools/fetch/emuia.d.ts +2 -2
- package/out/recipes/scaffold.d.ts +2 -2
- package/out/recipes/sources.d.ts +2 -2
- package/out/recipes/sources.js +3 -3
- package/out/recipes/sources.js.map +1 -1
- package/out/recipes/sub/venue/render.d.ts +1 -1
- package/out/recipes/sub/venue/render.js +1 -1
- package/out/recipes/sub/venue/sources.d.ts +4 -3
- package/out/recipes/sub/venue/sources.d.ts.map +1 -1
- package/out/recipes/sub/venue/sources.js +4 -3
- package/out/recipes/sub/venue/sources.js.map +1 -1
- package/out/recipes/sub/venue.d.ts +3 -2
- package/out/recipes/sub/venue.d.ts.map +1 -1
- package/out/recipes/sub/venue.js +2 -2
- package/out/recipes/sub/venue.js.map +1 -1
- package/out/recipes/venue.d.ts +7 -6
- package/out/recipes/venue.d.ts.map +1 -1
- package/out/recipes/venue.js +11 -10
- package/out/recipes/venue.js.map +1 -1
- package/out/ro/adapters/retea-scolara/adapter.d.ts +2 -2
- package/out/ro/adapters/retea-scolara/adapter.js +2 -2
- package/out/runner.d.ts +3 -3
- package/out/runner.js +2 -2
- package/out/sk/tools/fetch/inspire.d.ts +3 -3
- package/out/sk/tools/fetch/inspire.js +1 -1
- package/out/source-register/effective-manifest.d.ts +2 -2
- package/out/source-register/types.d.ts +2 -2
- package/out/subvenue/harvest.d.ts +4 -3
- package/out/subvenue/harvest.d.ts.map +1 -1
- package/out/subvenue/harvest.js +6 -5
- package/out/subvenue/harvest.js.map +1 -1
- package/out/subvenue/promotions.d.ts +2 -2
- package/out/subvenue/promotions.js +3 -3
- package/out/subvenue/promotions.js.map +1 -1
- package/out/subvenue/table.d.ts +6 -6
- package/out/subvenue/table.js +1 -1
- package/out/surfaces/locale.d.ts +1 -1
- package/out/surfaces/locale.js +1 -1
- package/out/synthesizers/boundary-stress.d.ts.map +1 -1
- package/out/synthesizers/boundary-stress.js +2 -1
- package/out/synthesizers/boundary-stress.js.map +1 -1
- package/out/synthesizers/tail.d.ts +2 -2
- package/out/synthesizers/tail.js +2 -2
- package/out/test-kit/corpus-recipe.d.ts +1 -1
- package/out/test-kit/corpus-recipe.js +1 -1
- package/out/tools/align-canonical.d.ts.map +1 -1
- package/out/tools/align-canonical.js +3 -2
- package/out/tools/align-canonical.js.map +1 -1
- package/out/tools/audit.js +1 -1
- package/out/tools/corpus-stats.d.ts +1 -1
- package/out/tools/fetch/download/network.d.ts +1 -1
- package/out/tools/fetch/download/network.js +1 -1
- package/out/tools/fetch/geonames/dump.d.ts +1 -1
- package/out/tools/fetch/geonames/dump.js +1 -1
- package/out/tools/fetch/gleif.d.ts +1 -1
- package/out/tools/fetch/gleif.js +1 -1
- package/out/tools/fetch/header.d.ts +1 -1
- package/out/tools/fetch/header.js +1 -1
- package/out/tools/fetch/inspire-addresses.d.ts +1 -1
- package/out/tools/fetch/vlaanderen-ad.d.ts +1 -1
- package/out/tools/fetch/wfs-harvest.d.ts +3 -3
- package/out/tools/fetch/wfs-harvest.js +4 -4
- package/out/tools/fetch/wfs-harvest.js.map +1 -1
- package/out/tools/fetch/zip-archive.d.ts +2 -2
- package/out/tools/fetch/zip-archive.js +2 -2
- package/out/tools/golden/expand.js +1 -1
- package/out/tools/golden/expand.js.map +1 -1
- package/out/tools/golden/promote.d.ts +1 -1
- package/out/tools/golden/promote.js +3 -3
- package/out/tools/golden/promote.js.map +1 -1
- package/out/tools/golden/relabel-street/directory.d.ts +2 -1
- package/out/tools/golden/relabel-street/directory.d.ts.map +1 -1
- package/out/tools/golden/relabel-street/directory.js.map +1 -1
- package/out/tools/lint/recipe-output/vocab.js +2 -2
- package/out/tools/overlay/manifest.d.ts +15 -2
- package/out/tools/overlay/manifest.d.ts.map +1 -1
- package/out/tools/overlay/manifest.js +41 -10
- package/out/tools/overlay/manifest.js.map +1 -1
- package/out/tools/overlay/split-slice.d.ts.map +1 -1
- package/out/tools/overlay/split-slice.js +2 -9
- package/out/tools/overlay/split-slice.js.map +1 -1
- package/out/tools/postcode-triples.d.ts +1 -1
- package/out/tools/postcode-triples.js +2 -2
- package/out/tools/postcode-triples.js.map +1 -1
- package/out/tools/sub/venue/head-nouns.d.ts +1 -1
- package/out/tools/sub/venue/head-nouns.d.ts.map +1 -1
- package/out/tools/sub/venue/head-nouns.js +4 -3
- package/out/tools/sub/venue/head-nouns.js.map +1 -1
- package/out/types.d.ts +3 -3
- package/out/types.js +1 -1
- package/out/us/adapters/state/tx-notaries/adapter.d.ts +1 -1
- package/out/us/adapters/state/tx-notaries/adapter.js +1 -1
- package/out/us/adapters/usgov/irs-bmf/adapter.d.ts +1 -1
- package/out/us/adapters/usgov/irs-bmf/adapter.js +2 -2
- package/out/us/synthesizers/intersection.js +4 -4
- package/out/us/synthesizers/intersection.js.map +1 -1
- package/out/us/synthesizers/street.js +3 -3
- package/out/us/synthesizers/street.js.map +1 -1
- package/out/us/tools/fetch/nad.d.ts +2 -2
- package/out/us/tools/fetch/nad.js +2 -2
- package/out/utils/split.d.ts +21 -0
- package/out/utils/split.d.ts.map +1 -1
- package/out/utils/split.js +49 -5
- package/out/utils/split.js.map +1 -1
- package/out/utils/wof-json.d.ts +1 -1
- package/out/utils/wof-json.js +1 -1
- package/package.json +14 -14
|
@@ -146,7 +146,7 @@ export interface HarvestVlaanderenOptions {
|
|
|
146
146
|
*/
|
|
147
147
|
outputDir: PathBuilderLike
|
|
148
148
|
/**
|
|
149
|
-
* Stop after this
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ||
|
|
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:
|
|
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(
|
|
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 (!
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
* -
|
|
13
|
-
* - Built-in fetch with gzip/brotli decompression (no curl
|
|
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
|
package/lib/utils/split.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
package/lib/utils/wof-json.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
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
|
|
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
|
*
|