@mailwoman/corpus 10.6.0 → 10.7.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/data/PROVENANCE.md +24 -0
- package/data/address-source-register.json +18948 -8757
- package/data/corpus-source-inputs.json +4756 -0
- package/data/license-decisions.json +7260 -0
- package/data/licenses/whosonfirst-licenses.md +3 -2
- package/data/reviewed-ve-postcode-tuples.json +5 -72
- package/data/source-additions.json +2450 -0
- package/data/source-resolutions.json +87 -0
- package/data/training-manifests/v0.7.10-gb-county.effective.json +2335 -0
- package/data/training-manifests/v0.7.10-gb-county.json +1132 -0
- package/data/training-manifests/v0.7.2-address-systems.effective.json +3 -2
- package/data/training-manifests/v0.7.7-no-ppd.json +550 -0
- package/data/training-manifests/v0.7.8-rights-clean.json +538 -0
- package/data/training-manifests/v0.7.9-country-gap.json +1132 -0
- package/data/us-situs-sources.json +128 -0
- package/lib/adapters/cordis/adapter.ts +1 -1
- package/lib/adapters/libpostal-dictionary.ts +23 -3
- package/lib/adapters/osm/adapter.ts +42 -11
- package/lib/adapters.ts +14 -0
- package/lib/az/adapters/opendata/adapter.ts +389 -0
- package/lib/bs/adapters/gbpa/adapter.ts +248 -0
- package/lib/build/types.ts +16 -0
- package/lib/build.ts +21 -0
- package/lib/ee/adapters/ads/adapter.ts +18 -0
- package/lib/fo/adapters/adressur/adapter.ts +242 -0
- package/lib/gb/adapters/os-open/adapter.ts +12 -0
- package/lib/gb/county.ts +59 -0
- package/lib/gb/os-open/open/names.ts +41 -0
- package/lib/gu/adapters/bsp/adapter.ts +434 -0
- package/lib/im/adapters/land-registry/adapter.ts +340 -0
- package/lib/international/recipes/locale.ts +164 -19
- package/lib/li/adapters/llv/adapter.ts +246 -0
- package/lib/recipes/bare/postcode.ts +10 -34
- package/lib/recipes/country-balanced.ts +95 -78
- package/lib/recipes/intersection.ts +22 -30
- package/lib/recipes/po/box/cedex/recipe.ts +8 -5
- package/lib/recipes/po/box/cedex/sources.ts +2 -23
- package/lib/recipes/scaffold.ts +28 -3
- package/lib/recipes/sources.ts +112 -1
- package/lib/recipes/street/affix.ts +17 -29
- package/lib/recipes/sub/venue/context.ts +33 -9
- package/lib/recipes/sub/venue/sources.ts +54 -13
- package/lib/recipes/sub/venue.ts +68 -23
- package/lib/recipes/unit.ts +20 -7
- package/lib/registers.ts +33 -0
- package/lib/source-register/corpus-inputs.ts +528 -0
- package/lib/source-register/effective-manifest.ts +37 -5
- package/lib/source-register/types.ts +20 -0
- package/lib/south-asia/recipes/register.ts +1 -1
- package/lib/tools/overlay/manifest.ts +109 -1
- package/lib/tools/source-register/build.ts +47 -1
- package/lib/tools/variety-sample.ts +633 -0
- package/lib/us/adapters/usgov/nad/adapter.ts +39 -10
- package/lib/us/recipes/skeleton-sources.ts +156 -0
- package/lib/us/tools/nad-skeletons.ts +395 -0
- package/lib/uy/adapters/ide/adapter.ts +275 -0
- package/lib/ve/recipes/reviewed-postcode-tail.ts +29 -6
- package/out/adapters/cordis/adapter.d.ts +1 -1
- package/out/adapters/cordis/adapter.js +1 -1
- package/out/adapters/libpostal-dictionary.d.ts +9 -0
- package/out/adapters/libpostal-dictionary.d.ts.map +1 -1
- package/out/adapters/libpostal-dictionary.js +19 -3
- package/out/adapters/libpostal-dictionary.js.map +1 -1
- package/out/adapters/osm/adapter.d.ts +12 -1
- package/out/adapters/osm/adapter.d.ts.map +1 -1
- package/out/adapters/osm/adapter.js +30 -13
- package/out/adapters/osm/adapter.js.map +1 -1
- package/out/adapters.d.ts.map +1 -1
- package/out/adapters.js +14 -0
- package/out/adapters.js.map +1 -1
- package/out/az/adapters/opendata/adapter.d.ts +88 -0
- package/out/az/adapters/opendata/adapter.d.ts.map +1 -0
- package/out/az/adapters/opendata/adapter.js +282 -0
- package/out/az/adapters/opendata/adapter.js.map +1 -0
- package/out/bs/adapters/gbpa/adapter.d.ts +64 -0
- package/out/bs/adapters/gbpa/adapter.d.ts.map +1 -0
- package/out/bs/adapters/gbpa/adapter.js +187 -0
- package/out/bs/adapters/gbpa/adapter.js.map +1 -0
- package/out/build/types.d.ts +15 -0
- package/out/build/types.d.ts.map +1 -1
- package/out/build/types.js.map +1 -1
- package/out/build.d.ts.map +1 -1
- package/out/build.js +17 -0
- package/out/build.js.map +1 -1
- package/out/ee/adapters/ads/adapter.d.ts +6 -0
- package/out/ee/adapters/ads/adapter.d.ts.map +1 -1
- package/out/ee/adapters/ads/adapter.js +16 -0
- package/out/ee/adapters/ads/adapter.js.map +1 -1
- package/out/fo/adapters/adressur/adapter.d.ts +63 -0
- package/out/fo/adapters/adressur/adapter.d.ts.map +1 -0
- package/out/fo/adapters/adressur/adapter.js +177 -0
- package/out/fo/adapters/adressur/adapter.js.map +1 -0
- package/out/gb/adapters/os-open/adapter.d.ts +8 -0
- package/out/gb/adapters/os-open/adapter.d.ts.map +1 -1
- package/out/gb/adapters/os-open/adapter.js +11 -0
- package/out/gb/adapters/os-open/adapter.js.map +1 -1
- package/out/gb/county.d.ts +30 -0
- package/out/gb/county.d.ts.map +1 -0
- package/out/gb/county.js +51 -0
- package/out/gb/county.js.map +1 -0
- package/out/gb/os-open/open/names.d.ts +17 -0
- package/out/gb/os-open/open/names.d.ts.map +1 -1
- package/out/gb/os-open/open/names.js +19 -0
- package/out/gb/os-open/open/names.js.map +1 -1
- package/out/gu/adapters/bsp/adapter.d.ts +83 -0
- package/out/gu/adapters/bsp/adapter.d.ts.map +1 -0
- package/out/gu/adapters/bsp/adapter.js +346 -0
- package/out/gu/adapters/bsp/adapter.js.map +1 -0
- package/out/im/adapters/land-registry/adapter.d.ts +68 -0
- package/out/im/adapters/land-registry/adapter.d.ts.map +1 -0
- package/out/im/adapters/land-registry/adapter.js +261 -0
- package/out/im/adapters/land-registry/adapter.js.map +1 -0
- package/out/international/recipes/locale.d.ts +24 -4
- package/out/international/recipes/locale.d.ts.map +1 -1
- package/out/international/recipes/locale.js +107 -15
- package/out/international/recipes/locale.js.map +1 -1
- package/out/li/adapters/llv/adapter.d.ts +69 -0
- package/out/li/adapters/llv/adapter.d.ts.map +1 -0
- package/out/li/adapters/llv/adapter.js +181 -0
- package/out/li/adapters/llv/adapter.js.map +1 -0
- package/out/recipes/bare/postcode.d.ts.map +1 -1
- package/out/recipes/bare/postcode.js +9 -30
- package/out/recipes/bare/postcode.js.map +1 -1
- package/out/recipes/country-balanced.d.ts.map +1 -1
- package/out/recipes/country-balanced.js +82 -73
- package/out/recipes/country-balanced.js.map +1 -1
- package/out/recipes/intersection.d.ts +3 -2
- package/out/recipes/intersection.d.ts.map +1 -1
- package/out/recipes/intersection.js +20 -26
- package/out/recipes/intersection.js.map +1 -1
- package/out/recipes/po/box/cedex/recipe.d.ts.map +1 -1
- package/out/recipes/po/box/cedex/recipe.js +5 -4
- package/out/recipes/po/box/cedex/recipe.js.map +1 -1
- package/out/recipes/po/box/cedex/sources.d.ts +2 -45
- package/out/recipes/po/box/cedex/sources.d.ts.map +1 -1
- package/out/recipes/po/box/cedex/sources.js +0 -20
- package/out/recipes/po/box/cedex/sources.js.map +1 -1
- package/out/recipes/scaffold.d.ts +15 -2
- package/out/recipes/scaffold.d.ts.map +1 -1
- package/out/recipes/scaffold.js +21 -1
- package/out/recipes/scaffold.js.map +1 -1
- package/out/recipes/sources.d.ts.map +1 -1
- package/out/recipes/sources.js +112 -1
- package/out/recipes/sources.js.map +1 -1
- package/out/recipes/street/affix.d.ts.map +1 -1
- package/out/recipes/street/affix.js +8 -20
- package/out/recipes/street/affix.js.map +1 -1
- package/out/recipes/sub/venue/context.d.ts +14 -3
- package/out/recipes/sub/venue/context.d.ts.map +1 -1
- package/out/recipes/sub/venue/context.js +29 -9
- package/out/recipes/sub/venue/context.js.map +1 -1
- package/out/recipes/sub/venue/sources.d.ts +41 -8
- package/out/recipes/sub/venue/sources.d.ts.map +1 -1
- package/out/recipes/sub/venue/sources.js +26 -7
- package/out/recipes/sub/venue/sources.js.map +1 -1
- package/out/recipes/sub/venue.d.ts.map +1 -1
- package/out/recipes/sub/venue.js +35 -14
- package/out/recipes/sub/venue.js.map +1 -1
- package/out/recipes/unit.d.ts +4 -0
- package/out/recipes/unit.d.ts.map +1 -1
- package/out/recipes/unit.js +7 -6
- package/out/recipes/unit.js.map +1 -1
- package/out/registers.d.ts +33 -0
- package/out/registers.d.ts.map +1 -1
- package/out/registers.js +33 -0
- package/out/registers.js.map +1 -1
- package/out/source-register/corpus-inputs.d.ts +240 -0
- package/out/source-register/corpus-inputs.d.ts.map +1 -0
- package/out/source-register/corpus-inputs.js +275 -0
- package/out/source-register/corpus-inputs.js.map +1 -0
- package/out/source-register/effective-manifest.d.ts +2 -0
- package/out/source-register/effective-manifest.d.ts.map +1 -1
- package/out/source-register/effective-manifest.js +33 -4
- package/out/source-register/effective-manifest.js.map +1 -1
- package/out/source-register/types.d.ts +21 -1
- package/out/source-register/types.d.ts.map +1 -1
- package/out/source-register/types.js +20 -0
- package/out/source-register/types.js.map +1 -1
- package/out/south-asia/recipes/register.js +1 -1
- package/out/south-asia/recipes/register.js.map +1 -1
- package/out/tools/overlay/manifest.d.ts +7 -0
- package/out/tools/overlay/manifest.d.ts.map +1 -1
- package/out/tools/overlay/manifest.js +72 -1
- package/out/tools/overlay/manifest.js.map +1 -1
- package/out/tools/source-register/build.d.ts +9 -0
- package/out/tools/source-register/build.d.ts.map +1 -1
- package/out/tools/source-register/build.js +25 -3
- package/out/tools/source-register/build.js.map +1 -1
- package/out/tools/variety-sample.d.ts +196 -0
- package/out/tools/variety-sample.d.ts.map +1 -0
- package/out/tools/variety-sample.js +417 -0
- package/out/tools/variety-sample.js.map +1 -0
- package/out/us/adapters/usgov/nad/adapter.d.ts +80 -0
- package/out/us/adapters/usgov/nad/adapter.d.ts.map +1 -1
- package/out/us/adapters/usgov/nad/adapter.js +27 -8
- package/out/us/adapters/usgov/nad/adapter.js.map +1 -1
- package/out/us/recipes/skeleton-sources.d.ts +92 -0
- package/out/us/recipes/skeleton-sources.d.ts.map +1 -0
- package/out/us/recipes/skeleton-sources.js +121 -0
- package/out/us/recipes/skeleton-sources.js.map +1 -0
- package/out/us/tools/nad-skeletons.d.ts +120 -0
- package/out/us/tools/nad-skeletons.d.ts.map +1 -0
- package/out/us/tools/nad-skeletons.js +262 -0
- package/out/us/tools/nad-skeletons.js.map +1 -0
- package/out/uy/adapters/ide/adapter.d.ts +64 -0
- package/out/uy/adapters/ide/adapter.d.ts.map +1 -0
- package/out/uy/adapters/ide/adapter.js +206 -0
- package/out/uy/adapters/ide/adapter.js.map +1 -0
- package/out/ve/recipes/reviewed-postcode-tail.d.ts +6 -2
- package/out/ve/recipes/reviewed-postcode-tail.d.ts.map +1 -1
- package/out/ve/recipes/reviewed-postcode-tail.js +16 -5
- package/out/ve/recipes/reviewed-postcode-tail.js.map +1 -1
- package/package.json +14 -14
- package/lib/recipes/unit/sources.ts +0 -44
- package/lib/tools/fetch/ppd.ts +0 -121
- package/out/recipes/unit/sources.d.ts +0 -28
- package/out/recipes/unit/sources.d.ts.map +0 -1
- package/out/recipes/unit/sources.js +0 -30
- package/out/recipes/unit/sources.js.map +0 -1
- package/out/tools/fetch/ppd.d.ts +0 -15
- package/out/tools/fetch/ppd.d.ts.map +0 -1
- package/out/tools/fetch/ppd.js +0 -86
- package/out/tools/fetch/ppd.js.map +0 -1
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
|
|
10
10
|
import { dataRootPath } from "@mailwoman/core/data-root"
|
|
11
11
|
import { readLocalBuffer, readLocalJSONFile } from "@mailwoman/core/fs/readers"
|
|
12
|
-
import { statPath } from "@mailwoman/core/fs/readers/stat"
|
|
12
|
+
import { pathExists, statPath } from "@mailwoman/core/fs/readers/stat"
|
|
13
13
|
import { writeLocalJSONFile } from "@mailwoman/core/fs/writers"
|
|
14
14
|
import { sha256Hex } from "@mailwoman/core/hash"
|
|
15
15
|
import { stringifyJSON } from "@mailwoman/core/json"
|
|
16
16
|
import { basename, dirname, PathBuilder, type PathBuilderLike } from "path-ts"
|
|
17
17
|
|
|
18
18
|
import { escapeSQLString, openDuckDB } from "#parquet/duckdb"
|
|
19
|
+
import { openSourceRightsAdmission, type SourceRightsRecord } from "#source-register/corpus-inputs"
|
|
19
20
|
import type { SplitName } from "#utils/split"
|
|
20
21
|
|
|
21
22
|
interface ParquetFileDescriptor {
|
|
@@ -53,6 +54,10 @@ interface BaseManifest {
|
|
|
53
54
|
total_rows: number
|
|
54
55
|
[PRE_RENAME_FILES_KEY]?: ManifestFile[]
|
|
55
56
|
[PRE_RENAME_ROWS_PER_FILE_KEY]?: unknown
|
|
57
|
+
/**
|
|
58
|
+
* The source-rights record a build or assembly wrote when it checked its sources.
|
|
59
|
+
*/
|
|
60
|
+
source_rights?: SourceRightsRecord
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
/**
|
|
@@ -251,6 +256,106 @@ export interface OverlayManifestOptions {
|
|
|
251
256
|
* Each source in this list must appear in the base, and its rows leave the split counts.
|
|
252
257
|
*/
|
|
253
258
|
replaceSources?: readonly string[]
|
|
259
|
+
/**
|
|
260
|
+
* Why a local experiment admits sources whose register rows lack an elected
|
|
261
|
+
* decision permitting `transform` and `train`.
|
|
262
|
+
*
|
|
263
|
+
* Without it the assembly refuses them, and with it the manifest records the corpus as not shippable.
|
|
264
|
+
*/
|
|
265
|
+
unshippableReason?: string | null
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Reads the distinct `source` values of base files whose manifest entries name no source.
|
|
270
|
+
*
|
|
271
|
+
* Every file must be present under the local data root.
|
|
272
|
+
* When one is absent, or a row's `source` is null, `sources` is null and `unreadable` names the cause.
|
|
273
|
+
*
|
|
274
|
+
* The caller therefore cannot mistake a partial read for the whole base.
|
|
275
|
+
*/
|
|
276
|
+
async function readBaseFileSources(
|
|
277
|
+
files: readonly ManifestFile[]
|
|
278
|
+
): Promise<{ sources: string[]; unreadable: null } | { sources: null; unreadable: string }> {
|
|
279
|
+
const paths = files.map((file) => localManifestFilePath(file.path))
|
|
280
|
+
const absent: string[] = []
|
|
281
|
+
|
|
282
|
+
for (const path of paths) {
|
|
283
|
+
if (!(await pathExists(path))) {
|
|
284
|
+
absent.push(path)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (absent.length) {
|
|
289
|
+
return { sources: null, unreadable: `${absent.length} of them are absent locally (first: ${absent[0]})` }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
using db = await openDuckDB()
|
|
293
|
+
|
|
294
|
+
const list = paths.map((path) => `'${escapeSQLString(path)}'`).join(", ")
|
|
295
|
+
const result = await db.runAndReadAll(`SELECT DISTINCT source FROM read_parquet([${list}])`)
|
|
296
|
+
const values = result.getRowObjects().map((row) => row.source as string | null)
|
|
297
|
+
|
|
298
|
+
if (values.some((value) => value == null)) {
|
|
299
|
+
return { sources: null, unreadable: "a row among them carries a null source" }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return { sources: values as string[], unreadable: null }
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Checks every corpus source the assembled manifest will hold, before the manifest or plan is written.
|
|
307
|
+
*
|
|
308
|
+
* The base manifest's own record covers a base file that records no source, when the base has a record.
|
|
309
|
+
* A base without a record is checked by reading its files' `source` column.
|
|
310
|
+
*
|
|
311
|
+
* When a file cannot be read, the check refuses the base rather than reading the absence as clear.
|
|
312
|
+
*/
|
|
313
|
+
async function checkOverlaySources(
|
|
314
|
+
args: OverlayManifestOptions,
|
|
315
|
+
base: BaseManifest,
|
|
316
|
+
kept: readonly ManifestFile[]
|
|
317
|
+
): Promise<SourceRightsRecord> {
|
|
318
|
+
const rights = await openSourceRightsAdmission({
|
|
319
|
+
context: `overlay assembly ${args.version}`,
|
|
320
|
+
unshippableReason: args.unshippableReason ?? null,
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
const named = new Set<string>(args.files.map((file) => file.source))
|
|
324
|
+
const unnamed = kept.filter((file) => file.source === undefined)
|
|
325
|
+
|
|
326
|
+
for (const file of kept) {
|
|
327
|
+
if (file.source !== undefined) {
|
|
328
|
+
named.add(file.source)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (unnamed.length && base.source_rights) {
|
|
333
|
+
for (const source of base.source_rights.sources_checked) {
|
|
334
|
+
named.add(source)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// A base without a record is read instead: its files' own `source` column is what the loader groups rows by.
|
|
339
|
+
const read = unnamed.length && !base.source_rights ? await readBaseFileSources(unnamed) : null
|
|
340
|
+
|
|
341
|
+
for (const source of read?.sources ?? []) {
|
|
342
|
+
named.add(source)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
rights.admitAll(named)
|
|
346
|
+
|
|
347
|
+
if (unnamed.length && !base.source_rights && read?.unreadable) {
|
|
348
|
+
rights.admitUnverified(
|
|
349
|
+
`${unnamed.length} file(s) of base ${args.base}, which name no source while the base manifest carries no ` +
|
|
350
|
+
`\`source_rights\` record, and ${read.unreadable}`
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (base.source_rights && !base.source_rights.shippable) {
|
|
355
|
+
rights.admitUnverified(`base ${args.base}, whose own record reads not shippable`)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return rights.record()
|
|
254
359
|
}
|
|
255
360
|
|
|
256
361
|
/**
|
|
@@ -357,6 +462,8 @@ export async function assembleOverlayManifest(args: OverlayManifestOptions): Pro
|
|
|
357
462
|
.filter((s) => !dropped.includes(s))
|
|
358
463
|
.map((s) => ({ ...s, path: rerootBaseFilePath(s.path, args.base) }))
|
|
359
464
|
|
|
465
|
+
const sourceRights = await checkOverlaySources(args, base, kept)
|
|
466
|
+
|
|
360
467
|
const added: ParquetFileDescriptor[] = []
|
|
361
468
|
|
|
362
469
|
const newDir = PathBuilder.from(args.newDir)
|
|
@@ -416,6 +523,7 @@ export async function assembleOverlayManifest(args: OverlayManifestOptions): Pro
|
|
|
416
523
|
licenses_cover:
|
|
417
524
|
`no license set is measured here. The base build's MANIFEST.json covers the rows it aligned, and ` +
|
|
418
525
|
`these ${added.length} added file(s) carry their own: ${[...new Set(args.files.map((file) => file.source))].toSorted().join(", ")}.`,
|
|
526
|
+
source_rights: sourceRights,
|
|
419
527
|
}
|
|
420
528
|
|
|
421
529
|
const out = newDir("MANIFEST.json")
|
|
@@ -89,7 +89,9 @@ const RESEARCH_PASS_BY_ORIGIN: Readonly<Record<string, ResearchPass>> = {
|
|
|
89
89
|
"2026-09-30_rights_review": ResearchPass.RightsReview,
|
|
90
90
|
"2026-10-01_rights_review": ResearchPass.RightsReview,
|
|
91
91
|
"2026-10-02_rights_review": ResearchPass.RightsReview,
|
|
92
|
+
"2026-10-17_rights_review": ResearchPass.RightsReview,
|
|
92
93
|
"2026-10-03_decoder_exposure": ResearchPass.DecoderExposureSurvey,
|
|
94
|
+
"2026-10-17_corpus_input_mapping": ResearchPass.CorpusInputMapping,
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
const GEOMETRY_BY_NAME: Readonly<Record<string, SourceGeometry>> = {
|
|
@@ -169,6 +171,12 @@ const LICENSE_DECISIONS: ReadonlyArray<readonly [statement: string, licenseID: s
|
|
|
169
171
|
"unchecked-access-licensed-or-query",
|
|
170
172
|
"An access label: licensed, or answerable per query. It states no terms.",
|
|
171
173
|
],
|
|
174
|
+
[
|
|
175
|
+
"CORPUS INPUT, TERMS UNREAD",
|
|
176
|
+
"unchecked-corpus-input",
|
|
177
|
+
"A corpus source reads this publication, and the row exists so that its terms show as unread rather than absent. " +
|
|
178
|
+
"No reviewer has recorded a reading of them.",
|
|
179
|
+
],
|
|
172
180
|
]
|
|
173
181
|
|
|
174
182
|
/**
|
|
@@ -252,6 +260,15 @@ export interface BuildSourceRegisterOptions {
|
|
|
252
260
|
* A path that does not exist is read as no resolutions recorded.
|
|
253
261
|
*/
|
|
254
262
|
resolutionsPath?: PathBuilderLike
|
|
263
|
+
/**
|
|
264
|
+
* Source rows a later pass added in the research CSV's own columns, read after the CSV's rows.
|
|
265
|
+
*
|
|
266
|
+
* `source-additions.json` holds the publications a corpus source reads that no research pass recorded.
|
|
267
|
+
* They sit in this repository because the corpus-source table that names them does,
|
|
268
|
+
* and they are read as CSV rows so their ids and license scopes come from the same derivation.
|
|
269
|
+
* A path that does not exist is read as no additions.
|
|
270
|
+
*/
|
|
271
|
+
additionsPath?: PathBuilderLike
|
|
255
272
|
version: string
|
|
256
273
|
/**
|
|
257
274
|
* ISO 8601 calendar date the research pass was taken, `yyyy-MM-DD`.
|
|
@@ -435,8 +452,36 @@ function scopedLicenseID(
|
|
|
435
452
|
return licenseID
|
|
436
453
|
}
|
|
437
454
|
|
|
455
|
+
/**
|
|
456
|
+
* The shape of `source-additions.json`: rows in the research CSV's column names.
|
|
457
|
+
*/
|
|
458
|
+
interface SourceAdditionsFile {
|
|
459
|
+
rows?: ReadonlyArray<Readonly<Record<string, string>>>
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* The research CSV's rows followed by the recorded additions.
|
|
464
|
+
*
|
|
465
|
+
* An absent additions file contributes zero rows.
|
|
466
|
+
* A file that exists and cannot be parsed raises, because reading it as empty
|
|
467
|
+
* would drop rows without saying so.
|
|
468
|
+
*/
|
|
469
|
+
async function* researchRows(
|
|
470
|
+
sourcesPath: PathBuilderLike,
|
|
471
|
+
additionsPath: PathBuilderLike | undefined
|
|
472
|
+
): AsyncGenerator<Readonly<Record<string, string | undefined>>> {
|
|
473
|
+
yield* readCSVRecords(sourcesPath)
|
|
474
|
+
|
|
475
|
+
if (!additionsPath || !(await pathExists(additionsPath))) return
|
|
476
|
+
|
|
477
|
+
const file = await readLocalJSONFile<SourceAdditionsFile>(additionsPath)
|
|
478
|
+
|
|
479
|
+
yield* file.rows ?? []
|
|
480
|
+
}
|
|
481
|
+
|
|
438
482
|
async function readSources(
|
|
439
483
|
sourcesPath: PathBuilderLike,
|
|
484
|
+
additionsPath: PathBuilderLike | undefined,
|
|
440
485
|
licenseByStatement: ReadonlyMap<string, string>,
|
|
441
486
|
fired: Set<string>,
|
|
442
487
|
scopes: Map<string, LicenseScope>
|
|
@@ -446,7 +491,7 @@ async function readSources(
|
|
|
446
491
|
let discoveryRailRowsDropped = 0
|
|
447
492
|
let row = 1
|
|
448
493
|
|
|
449
|
-
for await (const record of
|
|
494
|
+
for await (const record of researchRows(sourcesPath, additionsPath)) {
|
|
450
495
|
row++
|
|
451
496
|
|
|
452
497
|
const origin = required(record["origin"], "origin", row)
|
|
@@ -733,6 +778,7 @@ export async function buildSourceRegister(options: BuildSourceRegisterOptions):
|
|
|
733
778
|
|
|
734
779
|
const { sources: derived, discoveryRailRowsDropped } = await readSources(
|
|
735
780
|
options.sourcesPath,
|
|
781
|
+
options.additionsPath,
|
|
736
782
|
licenseByStatement,
|
|
737
783
|
fired,
|
|
738
784
|
scopes
|